Index: gdk/x11/gdkkeys-x11.c
===================================================================
RCS file: /cvs/gnome/gtk+/gdk/x11/gdkkeys-x11.c,v
retrieving revision 1.39
diff -u -p -r1.39 gdkkeys-x11.c
--- gdk/x11/gdkkeys-x11.c	6 Mar 2004 03:37:18 -0000	1.39
+++ gdk/x11/gdkkeys-x11.c	30 Mar 2004 06:58:49 -0000
@@ -58,6 +58,14 @@ typedef struct _GdkKeymapX11 GdkKeymapX1
 #define GDK_KEYMAP_X11(object)       (G_TYPE_CHECK_INSTANCE_CAST ((object), GDK_TYPE_KEYMAP_X11, GdkKeymapX11))
 #define GDK_IS_KEYMAP_X11(object)    (G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_KEYMAP_X11))
 
+typedef struct _DirectionCacheEntry DirectionCacheEntry;
+
+struct _DirectionCacheEntry {
+  guint serial;
+  gchar *group_name;
+  PangoDirection direction;
+};
+
 struct _GdkKeymapX11
 {
   GdkKeymap     parent_instance;
@@ -77,6 +85,16 @@ struct _GdkKeymapX11
   
 #ifdef HAVE_XKB
   XkbDescPtr xkb_desc;
+  /* We cache the directions */
+  char *current_group_name;
+  guint current_cache_serial;
+  /* A cache of size four should be more than enough, people usually
+   * have two groups around, and the xkb limit is four.  It still
+   * works correct for more than four groups.  It's just the
+   * cache.
+   */
+  DirectionCacheEntry group_direction_cache[4];
+
 #endif
 };
 
@@ -129,12 +147,14 @@ gdk_keymap_x11_init (GdkKeymapX11 *keyma
   keymap->group_switch_mask = 0;
   keymap->lock_keysym = GDK_Caps_Lock;
   keymap->have_direction = FALSE;
-  
+  keymap->current_serial = 0;
+
 #ifdef HAVE_XKB
   keymap->xkb_desc = NULL;
+  keymap->current_group_name = NULL;
+  keymap->current_cache_serial = 0;
 #endif
 
-  keymap->current_serial = 0;
 }
 
 static inline void
@@ -380,32 +400,138 @@ get_keymap (GdkKeymapX11 *keymap_x11)
 
 #if HAVE_XKB
 static PangoDirection
-get_direction (GdkKeymapX11 *keymap_x11)
+get_direction (XkbDescRec *xkb, gint group)
+{
+  KeyCode code = xkb->min_key_code;
+
+  gint rtl_minus_ltr = 0; /* total number of RTL keysyms minus LTR ones */
+
+  /* We use an infinite loop here instead of 'for' since max_key_code
+   * is often the maximum value of KeyCode (=CARD8), and a for loop
+   *  ends up overflowing and running endlessly.
+   */
+  for (;;)
+    {
+      gint width = XkbKeyGroupWidth (xkb, code, group);
+      gint level;
+      for (level = 0; level < width; level++)
+	{
+	  KeySym sym = XkbKeySymEntry (xkb, code, level, group);
+	  PangoDirection dir = pango_unichar_direction
+			       (gdk_keyval_to_unicode (sym));
+
+	  switch (dir) {
+	    case PANGO_DIRECTION_RTL:
+	      rtl_minus_ltr++;
+	      break;
+	    case PANGO_DIRECTION_LTR:
+	      rtl_minus_ltr--;
+	      break;
+	    default:
+	      break;
+	  }
+	}
+      if (code == xkb->max_key_code)
+	break;
+      code++;
+    }
+    
+  if (rtl_minus_ltr > 0)
+    return PANGO_DIRECTION_RTL;
+  else
+    return PANGO_DIRECTION_LTR;
+}
+
+static void
+find_direction (GdkKeymapX11 *keymap_x11)
 {
   XkbDescRec *xkb = get_xkb (keymap_x11);
-  const char *name;
   XkbStateRec state_rec;
-  PangoDirection result;
-
   GdkDisplay *display = GDK_KEYMAP (keymap_x11)->display;
+  gint group;
+  gchar *group_name;
 
   XkbGetState (GDK_DISPLAY_XDISPLAY (display), XkbUseCoreKbd, &state_rec);
-  
-  if (xkb->names->groups[state_rec.locked_group] == None)
-    result = PANGO_DIRECTION_LTR;
-  else
+  group = XkbGroupLock (&state_rec);
+  /* As per documentation we assume the string returned by
+   * gdk_x11_get_xatom_name_for_display is persistent and we
+   * don't copy it.
+   */
+  group_name = (gchar *) gdk_x11_get_xatom_name_for_display (display, xkb->names->groups[group]);
+
+  /* a group change? */
+  if (!keymap_x11->have_direction ||
+      strcmp (keymap_x11->current_group_name, group_name))
     {
-      name = gdk_x11_get_xatom_name_for_display (display, xkb->names->groups[state_rec.locked_group]);
+      gboolean cache_hit = FALSE;
+      DirectionCacheEntry *cache = keymap_x11->group_direction_cache;
 
-      if (g_ascii_strcasecmp (name, "arabic") == 0 ||
-	  g_ascii_strcasecmp (name, "hebrew") == 0 ||
-	  g_ascii_strcasecmp (name, "israelian") == 0)
-	result = PANGO_DIRECTION_RTL;
+      PangoDirection direction = PANGO_DIRECTION_NEUTRAL;
+      gint i;
+
+      if (keymap_x11->have_direction)
+	{
+          /* lookup in cache */
+	  for (i = 0; i < G_N_ELEMENTS (keymap_x11->group_direction_cache); i++)
+	  {
+	    /* do we really need to do strcmp?  if
+	     * gdk_x11_get_xatom_name_for_display returns the
+	     * same pointer for the same string across multiple
+	     * calls, then we can simply compare pointers.  it's
+	     * seems to be highly probable, but the current code
+	     * is safe.
+	     */
+	    if (cache[i].group_name == group_name || !strcmp (cache[i].group_name, group_name))
+	      {
+		cache_hit = TRUE;
+		cache[i].serial = keymap_x11->current_cache_serial++; /* freshen */
+		direction = cache[i].direction;
+		group_name = cache[i].group_name;
+		break;
+	      }
+	  }
+	}
       else
-	result = PANGO_DIRECTION_LTR;
+	{
+          /* initialize cache */
+	  for (i = 0; i < G_N_ELEMENTS (keymap_x11->group_direction_cache); i++)
+	    {
+	      cache[i].group_name = "";
+	      cache[i].serial = keymap_x11->current_cache_serial;
+	    }
+	  keymap_x11->current_cache_serial++;
+	}
+
+      /* insert in cache */
+      if (!cache_hit)
+	{
+	  gint oldest = 0;
+
+	  /* g_message ("finding keymap direction for %s", group_name); */
+	  direction = get_direction (xkb, group);
+
+	  /* remove the oldest entry */
+	  for (i = 0; i < G_N_ELEMENTS (keymap_x11->group_direction_cache); i++)
+	    {
+	      if (cache[i].serial < cache[oldest].serial)
+	        oldest = i;
+	    }
+	  
+	  cache[oldest].group_name = group_name;
+	  cache[oldest].direction = direction;
+	  cache[oldest].serial = keymap_x11->current_cache_serial++;
+	}
+
+      /* notify and voila */
+      if (!keymap_x11->have_direction ||
+	  direction != keymap_x11->current_direction)
+	{
+          keymap_x11->have_direction = TRUE;
+          keymap_x11->current_direction = direction;
+          keymap_x11->current_group_name = group_name;
+	  g_signal_emit_by_name (keymap_x11, "direction_changed");
+	}
     }
-    
-  return result;
 }
 
 void
@@ -417,14 +543,7 @@ _gdk_keymap_state_changed (GdkDisplay *d
     {
       GdkKeymapX11 *keymap_x11 = GDK_KEYMAP_X11 (display_x11->keymap);
       
-      PangoDirection new_direction = get_direction (keymap_x11);
-      
-      if (!keymap_x11->have_direction || new_direction != keymap_x11->current_direction)
-	{
-	  keymap_x11->have_direction = TRUE;
-	  keymap_x11->current_direction = new_direction;
-	  g_signal_emit_by_name (keymap_x11, "direction_changed");
-	}
+      find_direction (keymap_x11);
     }
 }
 
@@ -458,16 +577,13 @@ gdk_keymap_get_direction (GdkKeymap *key
       GdkKeymapX11 *keymap_x11 = GDK_KEYMAP_X11 (keymap);
       
       if (!keymap_x11->have_direction)
-	{
-	  keymap_x11->current_direction = get_direction (keymap_x11);
-	  keymap_x11->have_direction = TRUE;
-	}
+	find_direction (keymap_x11);
   
       return keymap_x11->current_direction;
     }
   else
 #endif /* HAVE_XKB */
-    return PANGO_DIRECTION_LTR;
+    return PANGO_DIRECTION_NEUTRAL;
 }
 
 /**
