Blob Blame History Raw
/* GAIL - The GNOME Accessibility Implementation Library
 * Copyright 2001, 2002, 2003 Sun Microsystems Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <string.h>
#include <gtk/gtk.h>
#include "gailnotebookpage.h"
#include <libgail-util/gailmisc.h>
#include "gail-private-macros.h"

static void      gail_notebook_page_class_init      (GailNotebookPageClass     *klass);
static void                  gail_notebook_page_init           (GailNotebookPage *page);
static void                  gail_notebook_page_finalize       (GObject   *object);
static void                  gail_notebook_page_label_map_gtk  (GtkWidget *widget,
                                                                gpointer  data);

static const gchar*          gail_notebook_page_get_name       (AtkObject *accessible);
static AtkObject*            gail_notebook_page_get_parent     (AtkObject *accessible);
static gint                  gail_notebook_page_get_n_children (AtkObject *accessible);
static AtkObject*            gail_notebook_page_ref_child      (AtkObject *accessible,
                                                                gint      i); 
static gint                  gail_notebook_page_get_index_in_parent
                                                               (AtkObject *accessible);
static AtkStateSet*          gail_notebook_page_ref_state_set  (AtkObject *accessible);

static gint                  gail_notebook_page_notify          (GObject   *obj,
                                                                 GParamSpec *pspec,
                                                                 gpointer   user_data);
static void                  gail_notebook_page_init_textutil   (GailNotebookPage      *notebook_page,
                                                                 GtkWidget             *label);

static void                  atk_component_interface_init      (AtkComponentIface *iface);

static AtkObject*            gail_notebook_page_ref_accessible_at_point 
                                                               (AtkComponent *component,
                                                                gint         x,
                                                                gint         y,
                                                                AtkCoordType coord_type);

static void                  gail_notebook_page_get_extents    (AtkComponent *component,
                                                                gint         *x,
                                                                gint         *y,
                                                                gint         *width,
                                                                gint         *height,
                                                                AtkCoordType coord_type);

static AtkObject*            _gail_notebook_page_get_tab_label (GailNotebookPage *page);

/* atktext.h */ 
static void	  atk_text_interface_init	   (AtkTextIface	*iface);

static gchar*	  gail_notebook_page_get_text	   (AtkText	      *text,
                                                    gint	      start_pos,
						    gint	      end_pos);
static gunichar	  gail_notebook_page_get_character_at_offset
                                                   (AtkText	      *text,
						    gint	      offset);
static gchar*     gail_notebook_page_get_text_before_offset
                                                   (AtkText	      *text,
 						    gint	      offset,
						    AtkTextBoundary   boundary_type,
						    gint	      *start_offset,
						    gint	      *end_offset);
static gchar*     gail_notebook_page_get_text_at_offset
                                                   (AtkText	      *text,
 						    gint	      offset,
						    AtkTextBoundary   boundary_type,
						    gint	      *start_offset,
						    gint	      *end_offset);
static gchar*     gail_notebook_page_get_text_after_offset
                                                   (AtkText	      *text,
 						    gint	      offset,
						    AtkTextBoundary   boundary_type,
						    gint	      *start_offset,
						    gint	      *end_offset);
static gint	  gail_notebook_page_get_character_count (AtkText	      *text);
static void       gail_notebook_page_get_character_extents
                                                   (AtkText	      *text,
						    gint 	      offset,
		                                    gint 	      *x,
                    		   	            gint 	      *y,
                                		    gint 	      *width,
                                     		    gint 	      *height,
			        		    AtkCoordType      coords);
static gint      gail_notebook_page_get_offset_at_point
                                                   (AtkText           *text,
                                                    gint              x,
                                                    gint              y,
			                            AtkCoordType      coords);
static AtkAttributeSet* gail_notebook_page_get_run_attributes 
                                                   (AtkText           *text,
              					    gint 	      offset,
                                                    gint 	      *start_offset,
					            gint	      *end_offset);
static AtkAttributeSet* gail_notebook_page_get_default_attributes
                                                   (AtkText           *text);
static GtkWidget* get_label_from_notebook_page     (GailNotebookPage  *page);
static GtkWidget* find_label_child (GtkContainer *container);

/* FIXME: not GAIL_TYPE_OBJECT? */
G_DEFINE_TYPE_WITH_CODE (GailNotebookPage, gail_notebook_page, ATK_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_COMPONENT, atk_component_interface_init)
                         G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init))

static void
gail_notebook_page_class_init (GailNotebookPageClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  AtkObjectClass *class = ATK_OBJECT_CLASS (klass);
  
  class->get_name = gail_notebook_page_get_name;
  class->get_parent = gail_notebook_page_get_parent;
  class->get_n_children = gail_notebook_page_get_n_children;
  class->ref_child = gail_notebook_page_ref_child;
  class->ref_state_set = gail_notebook_page_ref_state_set;
  class->get_index_in_parent = gail_notebook_page_get_index_in_parent;

  gobject_class->finalize = gail_notebook_page_finalize;
}

static void
gail_notebook_page_init (GailNotebookPage *page)
{
}

static gint
notify_child_added (gpointer data)
{
  GailNotebookPage *page;
  AtkObject *atk_object, *atk_parent;

  g_return_val_if_fail (GAIL_IS_NOTEBOOK_PAGE (data), FALSE);
  page = GAIL_NOTEBOOK_PAGE (data);
  atk_object = ATK_OBJECT (data);

  page->notify_child_added_id = 0;

  /* The widget page->notebook may be deleted before this handler is called */
  if (page->notebook != NULL)
    {
      atk_parent = gtk_widget_get_accessible (GTK_WIDGET (page->notebook));
      atk_object_set_parent (atk_object, atk_parent);
      g_signal_emit_by_name (atk_parent, "children_changed::add", page->index, atk_object, NULL);
    }
  
  return FALSE;
}

AtkObject*
gail_notebook_page_new (GtkNotebook *notebook, 
                        gint        pagenum)
{
  GObject *object;
  AtkObject *atk_object;
  GailNotebookPage *page;
  GtkWidget *child;
  GtkWidget *label;
  GList *list;
  
  g_return_val_if_fail (GTK_IS_NOTEBOOK (notebook), NULL);

  child = gtk_notebook_get_nth_page (notebook, pagenum);

  if (!child)
    return NULL;

  object = g_object_new (GAIL_TYPE_NOTEBOOK_PAGE, NULL);
  g_return_val_if_fail (object != NULL, NULL);

  page = GAIL_NOTEBOOK_PAGE (object);
  page->notebook = notebook;
  g_object_add_weak_pointer (G_OBJECT (page->notebook), (gpointer *)&page->notebook);
  page->index = pagenum;
  list = g_list_nth (notebook->children, pagenum);
  page->page = list->data;
  page->textutil = NULL;
  
  atk_object = ATK_OBJECT (page);
  atk_object->role = ATK_ROLE_PAGE_TAB;
  atk_object->layer = ATK_LAYER_WIDGET;

  page->notify_child_added_id = gdk_threads_add_idle (notify_child_added, atk_object);
  /*
   * We get notified of changes to the label
   */
  label = get_label_from_notebook_page (page);
  if (GTK_IS_LABEL (label))
    {
      if (gtk_widget_get_mapped (label))
        gail_notebook_page_init_textutil (page, label);
      else
        g_signal_connect (label,
                          "map",
                          G_CALLBACK (gail_notebook_page_label_map_gtk),
                          page);
    }

  return atk_object;
}

static void
gail_notebook_page_label_map_gtk (GtkWidget *widget,
                                  gpointer data)
{
  GailNotebookPage *page;

  page = GAIL_NOTEBOOK_PAGE (data);
  gail_notebook_page_init_textutil (page, widget);
}

static void
gail_notebook_page_init_textutil (GailNotebookPage *page,
                                  GtkWidget        *label)
{
  const gchar *label_text;

  if (page->textutil == NULL)
    {
      page->textutil = gail_text_util_new ();
      g_signal_connect (label,
                        "notify",
                        (GCallback) gail_notebook_page_notify,
                        page);     
    }
  label_text = gtk_label_get_text (GTK_LABEL (label));
  gail_text_util_text_setup (page->textutil, label_text);
}

static gint
gail_notebook_page_notify (GObject    *obj, 
                           GParamSpec *pspec,
                           gpointer   user_data)
{
  AtkObject *atk_obj = ATK_OBJECT (user_data);
  GtkLabel *label;
  GailNotebookPage *page;

  if (strcmp (pspec->name, "label") == 0)
    {
      const gchar* label_text;

      label = GTK_LABEL (obj);

      label_text = gtk_label_get_text (label);

      page = GAIL_NOTEBOOK_PAGE (atk_obj);
      gail_text_util_text_setup (page->textutil, label_text);

      if (atk_obj->name == NULL)
      {
        /*
         * The label has changed so notify a change in accessible-name
         */
        g_object_notify (G_OBJECT (atk_obj), "accessible-name");
      }
      /*
       * The label is the only property which can be changed
       */
      g_signal_emit_by_name (atk_obj, "visible_data_changed");
    }
  return 1;
}

static void
gail_notebook_page_finalize (GObject *object)
{
  GailNotebookPage *page = GAIL_NOTEBOOK_PAGE (object);

  if (page->notebook)
    g_object_remove_weak_pointer (G_OBJECT (page->notebook), (gpointer *)&page->notebook);

  if (page->textutil)
    g_object_unref (page->textutil);

  if (page->notify_child_added_id)
    g_source_remove (page->notify_child_added_id);

  G_OBJECT_CLASS (gail_notebook_page_parent_class)->finalize (object);
}

static const gchar*
gail_notebook_page_get_name (AtkObject *accessible)
{
  g_return_val_if_fail (GAIL_IS_NOTEBOOK_PAGE (accessible), NULL);
  
  if (accessible->name != NULL)
    return accessible->name;
  else
    {
      GtkWidget *label;

      label = get_label_from_notebook_page (GAIL_NOTEBOOK_PAGE (accessible));
      if (GTK_IS_LABEL (label))
        return gtk_label_get_text (GTK_LABEL (label));
      else
        return NULL;
    }
}

static AtkObject*
gail_notebook_page_get_parent (AtkObject *accessible)
{
  GailNotebookPage *page;
  
  g_return_val_if_fail (GAIL_IS_NOTEBOOK_PAGE (accessible), NULL);
  
  page = GAIL_NOTEBOOK_PAGE (accessible);

  if (!page->notebook)
    return NULL;

  return gtk_widget_get_accessible (GTK_WIDGET (page->notebook));
}

static gint
gail_notebook_page_get_n_children (AtkObject *accessible)
{
  /* Notebook page has only one child */
  g_return_val_if_fail (GAIL_IS_NOTEBOOK_PAGE (accessible), 0);

  return 1;
}

static AtkObject*
gail_notebook_page_ref_child (AtkObject *accessible,
                              gint i)
{
  GtkWidget *child;
  AtkObject *child_obj;
  GailNotebookPage *page = NULL;
   
  g_return_val_if_fail (GAIL_IS_NOTEBOOK_PAGE (accessible), NULL);
  if (i != 0)
    return NULL;
   
  page = GAIL_NOTEBOOK_PAGE (accessible);
  if (!page->notebook)
    return NULL;

  child = gtk_notebook_get_nth_page (page->notebook, page->index);
  gail_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
   
  child_obj = gtk_widget_get_accessible (child);
  g_object_ref (child_obj);
  return child_obj;
}

static gint
gail_notebook_page_get_index_in_parent (AtkObject *accessible)
{
  GailNotebookPage *page;

  g_return_val_if_fail (GAIL_IS_NOTEBOOK_PAGE (accessible), -1);
  page = GAIL_NOTEBOOK_PAGE (accessible);

  return page->index;
}

static AtkStateSet*
gail_notebook_page_ref_state_set (AtkObject *accessible)
{
  AtkStateSet *state_set, *label_state_set, *merged_state_set;
  AtkObject *atk_label;

  g_return_val_if_fail (GAIL_NOTEBOOK_PAGE (accessible), NULL);

  state_set = ATK_OBJECT_CLASS (gail_notebook_page_parent_class)->ref_state_set (accessible);

  atk_label = _gail_notebook_page_get_tab_label (GAIL_NOTEBOOK_PAGE (accessible));
  if (atk_label)
    {
      label_state_set = atk_object_ref_state_set (atk_label);
      merged_state_set = atk_state_set_or_sets (state_set, label_state_set);
      g_object_unref (label_state_set);
      g_object_unref (state_set);
    }
  else
    {
      AtkObject *child;

      child = atk_object_ref_accessible_child (accessible, 0);
      gail_return_val_if_fail (child, state_set);

      merged_state_set = state_set;
      state_set = atk_object_ref_state_set (child);
      if (atk_state_set_contains_state (state_set, ATK_STATE_VISIBLE))
        {
          atk_state_set_add_state (merged_state_set, ATK_STATE_VISIBLE);
          if (atk_state_set_contains_state (state_set, ATK_STATE_ENABLED))
              atk_state_set_add_state (merged_state_set, ATK_STATE_ENABLED);
          if (atk_state_set_contains_state (state_set, ATK_STATE_SHOWING))
              atk_state_set_add_state (merged_state_set, ATK_STATE_SHOWING);

        } 
      g_object_unref (state_set);
      g_object_unref (child);
    }
  return merged_state_set;
}


static void
atk_component_interface_init (AtkComponentIface *iface)
{
  /*
   * We use the default implementations for contains, get_position, get_size
   */
  iface->ref_accessible_at_point = gail_notebook_page_ref_accessible_at_point;
  iface->get_extents = gail_notebook_page_get_extents;
}

static AtkObject*
gail_notebook_page_ref_accessible_at_point (AtkComponent *component,
                                            gint         x,
                                            gint         y,
                                            AtkCoordType coord_type)
{
  /*
   * There is only one child so we return it.
   */
  AtkObject* child;

  g_return_val_if_fail (ATK_IS_OBJECT (component), NULL);

  child = atk_object_ref_accessible_child (ATK_OBJECT (component), 0);
  return child;
}

static void
gail_notebook_page_get_extents (AtkComponent *component,
                                gint         *x,
                                gint         *y,
                                gint         *width,
                                gint         *height,
                                AtkCoordType coord_type)
{
  AtkObject *atk_label;

  g_return_if_fail (GAIL_IS_NOTEBOOK_PAGE (component));

  atk_label = _gail_notebook_page_get_tab_label (GAIL_NOTEBOOK_PAGE (component));

  if (!atk_label)
    {
      AtkObject *child;

      *width = 0;
      *height = 0;

      child = atk_object_ref_accessible_child (ATK_OBJECT (component), 0);
      gail_return_if_fail (child);

      atk_component_get_position (ATK_COMPONENT (child), x, y, coord_type);
      g_object_unref (child);
    }
  else
    {
      atk_component_get_extents (ATK_COMPONENT (atk_label), 
                                 x, y, width, height, coord_type);
    }
  return; 
}

static AtkObject*
_gail_notebook_page_get_tab_label (GailNotebookPage *page)
{
  GtkWidget *label;

  label = get_label_from_notebook_page (page);
  if (label)
    return gtk_widget_get_accessible (label);
  else
    return NULL;
}

/* atktext.h */

static void
atk_text_interface_init (AtkTextIface *iface)
{
  iface->get_text = gail_notebook_page_get_text;
  iface->get_character_at_offset = gail_notebook_page_get_character_at_offset;
  iface->get_text_before_offset = gail_notebook_page_get_text_before_offset;
  iface->get_text_at_offset = gail_notebook_page_get_text_at_offset;
  iface->get_text_after_offset = gail_notebook_page_get_text_after_offset;
  iface->get_character_count = gail_notebook_page_get_character_count;
  iface->get_character_extents = gail_notebook_page_get_character_extents;
  iface->get_offset_at_point = gail_notebook_page_get_offset_at_point;
  iface->get_run_attributes = gail_notebook_page_get_run_attributes;
  iface->get_default_attributes = gail_notebook_page_get_default_attributes;
}

static gchar*
gail_notebook_page_get_text (AtkText *text,
                             gint    start_pos,
                             gint    end_pos)
{
  GtkWidget *label;
  GailNotebookPage *notebook_page;
  const gchar *label_text;

  notebook_page = GAIL_NOTEBOOK_PAGE (text);
  label = get_label_from_notebook_page (notebook_page);

  if (!GTK_IS_LABEL (label))
    return NULL;

  if (!notebook_page->textutil) 
    gail_notebook_page_init_textutil (notebook_page, label);

  label_text = gtk_label_get_text (GTK_LABEL (label));

  if (label_text == NULL)
    return NULL;
  else
  {
    return gail_text_util_get_substring (notebook_page->textutil, 
                                         start_pos, end_pos);
  }
}

static gchar*
gail_notebook_page_get_text_before_offset (AtkText         *text,
     				           gint            offset,
				           AtkTextBoundary boundary_type,
				           gint            *start_offset,
				           gint            *end_offset)
{
  GtkWidget *label;
  GailNotebookPage *notebook_page;
  
  notebook_page = GAIL_NOTEBOOK_PAGE (text);
  label = get_label_from_notebook_page (notebook_page);

  if (!GTK_IS_LABEL(label))
    return NULL;

  if (!notebook_page->textutil)
    gail_notebook_page_init_textutil (notebook_page, label);

  return gail_text_util_get_text (notebook_page->textutil,
                           gtk_label_get_layout (GTK_LABEL (label)), GAIL_BEFORE_OFFSET, 
                           boundary_type, offset, start_offset, end_offset); 
}

static gchar*
gail_notebook_page_get_text_at_offset (AtkText         *text,
			               gint            offset,
			               AtkTextBoundary boundary_type,
 			               gint            *start_offset,
			               gint            *end_offset)
{
  GtkWidget *label;
  GailNotebookPage *notebook_page;
 
  notebook_page = GAIL_NOTEBOOK_PAGE (text);
  label = get_label_from_notebook_page (notebook_page);

  if (!GTK_IS_LABEL(label))
    return NULL;

  if (!notebook_page->textutil)
    gail_notebook_page_init_textutil (notebook_page, label);

  return gail_text_util_get_text (notebook_page->textutil,
                              gtk_label_get_layout (GTK_LABEL (label)), GAIL_AT_OFFSET, 
                              boundary_type, offset, start_offset, end_offset);
}

static gchar*
gail_notebook_page_get_text_after_offset (AtkText         *text,
				          gint            offset,
				          AtkTextBoundary boundary_type,
				          gint            *start_offset,
				          gint            *end_offset)
{
  GtkWidget *label;
  GailNotebookPage *notebook_page;

  notebook_page = GAIL_NOTEBOOK_PAGE (text);
  label = get_label_from_notebook_page (notebook_page);

  if (!GTK_IS_LABEL(label))
    return NULL;

  if (!notebook_page->textutil)
    gail_notebook_page_init_textutil (notebook_page, label);

  return gail_text_util_get_text (notebook_page->textutil,
                           gtk_label_get_layout (GTK_LABEL (label)), GAIL_AFTER_OFFSET, 
                           boundary_type, offset, start_offset, end_offset);
}

static gint
gail_notebook_page_get_character_count (AtkText *text)
{
  GtkWidget *label;
  GailNotebookPage *notebook_page;

  notebook_page = GAIL_NOTEBOOK_PAGE (text);
  label = get_label_from_notebook_page (notebook_page);

  if (!GTK_IS_LABEL(label))
    return 0;

  return g_utf8_strlen (gtk_label_get_text (GTK_LABEL (label)), -1);
}

static void
gail_notebook_page_get_character_extents (AtkText      *text,
				          gint         offset,
		                          gint         *x,
                    		          gint         *y,
                                          gint 	       *width,
                                          gint 	       *height,
			                  AtkCoordType coords)
{
  GtkWidget *label;
  GailNotebookPage *notebook_page;
  PangoRectangle char_rect;
  gint index, x_layout, y_layout;
  const gchar *label_text;
 
  notebook_page = GAIL_NOTEBOOK_PAGE (text);
  label = get_label_from_notebook_page (notebook_page);

  if (!GTK_IS_LABEL(label))
    return;
  
  gtk_label_get_layout_offsets (GTK_LABEL (label), &x_layout, &y_layout);
  label_text = gtk_label_get_text (GTK_LABEL (label));
  index = g_utf8_offset_to_pointer (label_text, offset) - label_text;
  pango_layout_index_to_pos (gtk_label_get_layout (GTK_LABEL (label)), index, &char_rect);
  
  gail_misc_get_extents_from_pango_rectangle (label, &char_rect, 
                    x_layout, y_layout, x, y, width, height, coords);
} 

static gint 
gail_notebook_page_get_offset_at_point (AtkText      *text,
                                        gint         x,
                                        gint         y,
	          		        AtkCoordType coords)
{ 
  GtkWidget *label;
  GailNotebookPage *notebook_page;
  gint index, x_layout, y_layout;
  const gchar *label_text;

  notebook_page = GAIL_NOTEBOOK_PAGE (text);
  label = get_label_from_notebook_page (notebook_page);

  if (!GTK_IS_LABEL(label))
    return -1;
  
  gtk_label_get_layout_offsets (GTK_LABEL (label), &x_layout, &y_layout);
  
  index = gail_misc_get_index_at_point_in_layout (label, 
                                              gtk_label_get_layout (GTK_LABEL (label)), 
                                              x_layout, y_layout, x, y, coords);
  label_text = gtk_label_get_text (GTK_LABEL (label));
  if (index == -1)
    {
      if (coords == ATK_XY_WINDOW || coords == ATK_XY_SCREEN)
        return g_utf8_strlen (label_text, -1);

      return index;  
    }
  else
    return g_utf8_pointer_to_offset (label_text, label_text + index);  
}

static AtkAttributeSet*
gail_notebook_page_get_run_attributes (AtkText *text,
                                       gint    offset,
                                       gint    *start_offset,
	                               gint    *end_offset)
{
  GtkWidget *label;
  GailNotebookPage *notebook_page;
  AtkAttributeSet *at_set = NULL;
  GtkJustification justify;
  GtkTextDirection dir;

  notebook_page = GAIL_NOTEBOOK_PAGE (text);
  label = get_label_from_notebook_page (notebook_page);

  if (!GTK_IS_LABEL(label))
    return NULL;
  
  /* Get values set for entire label, if any */
  justify = gtk_label_get_justify (GTK_LABEL (label));
  if (justify != GTK_JUSTIFY_CENTER)
    {
      at_set = gail_misc_add_attribute (at_set, 
                                        ATK_TEXT_ATTR_JUSTIFICATION,
     g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_JUSTIFICATION, justify)));
    }
  dir = gtk_widget_get_direction (label);
  if (dir == GTK_TEXT_DIR_RTL)
    {
      at_set = gail_misc_add_attribute (at_set, 
                                        ATK_TEXT_ATTR_DIRECTION,
     g_strdup (atk_text_attribute_get_value (ATK_TEXT_ATTR_DIRECTION, dir)));
    }

  at_set = gail_misc_layout_get_run_attributes (at_set,
                                                gtk_label_get_layout (GTK_LABEL (label)),
                                                (gchar *) gtk_label_get_text (GTK_LABEL (label)),
                                                offset,
                                                start_offset,
                                                end_offset);
  return at_set;
}

static AtkAttributeSet*
gail_notebook_page_get_default_attributes (AtkText *text)
{
  GtkWidget *label;
  GailNotebookPage *notebook_page;
  AtkAttributeSet *at_set = NULL;

  notebook_page = GAIL_NOTEBOOK_PAGE (text);
  label = get_label_from_notebook_page (notebook_page);

  if (!GTK_IS_LABEL(label))
    return NULL;

  at_set = gail_misc_get_default_attributes (at_set,
                                             gtk_label_get_layout (GTK_LABEL (label)),
                                             label);
  return at_set;
}

static gunichar 
gail_notebook_page_get_character_at_offset (AtkText *text,
                                            gint    offset)
{
  GtkWidget *label;
  GailNotebookPage *notebook_page;
  const gchar *string;
  gchar *index;

  notebook_page = GAIL_NOTEBOOK_PAGE (text);
  label = get_label_from_notebook_page (notebook_page);

  if (!GTK_IS_LABEL(label))
    return '\0';
  string = gtk_label_get_text (GTK_LABEL (label));
  if (offset >= g_utf8_strlen (string, -1))
    return '\0';
  index = g_utf8_offset_to_pointer (string, offset);

  return g_utf8_get_char (index);
}

static GtkWidget*
get_label_from_notebook_page (GailNotebookPage *page)
{
  GtkWidget *child;
  GtkNotebook *notebook;

  notebook = page->notebook;
  if (!notebook)
    return NULL;

  if (!gtk_notebook_get_show_tabs (notebook))
    return NULL;

  child = gtk_notebook_get_nth_page (notebook, page->index);
  if (child == NULL) return NULL;
  g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);

  child = gtk_notebook_get_tab_label (notebook, child);

  if (GTK_IS_LABEL (child))
    return child;

  if (GTK_IS_CONTAINER (child))
    child = find_label_child (GTK_CONTAINER (child));

  return child;
}

static GtkWidget*
find_label_child (GtkContainer *container)
{
  GList *children, *tmp_list;
  GtkWidget *child;

  children = gtk_container_get_children (container);

  child = NULL;
  for (tmp_list = children; tmp_list != NULL; tmp_list = tmp_list->next)
    {
      if (GTK_IS_LABEL (tmp_list->data))
        {
          child = GTK_WIDGET (tmp_list->data);
          break;
        }
      else if (GTK_IS_CONTAINER (tmp_list->data))
        {
          child = find_label_child (GTK_CONTAINER (tmp_list->data));
          if (child)
            break;
        }
    }
  g_list_free (children);
  return child;
}