/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2004 Hiroyuki Ikezoe
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
/*
 * These codes originated in gtkentry.c in GTK+-2.2.4.
 */

#include "kz-entry.h"

#include "gobject-utils.h"
#include "gtk-utils.h"
#include "intl.h"


#define INNER_BORDER     2

typedef enum {
	CURSOR_STANDARD,
	CURSOR_DND
} CursorType;

enum {
	PROP_0,
	PROP_BACKTEXT,
	PROP_STOCK,
	PROP_PIXBUF
};


/* object class */
static void kz_entry_class_init     (KzEntryClass   *klass);
static void kz_entry_init           (KzEntry        *kzentry);
static void kz_entry_finalize       (GObject          *object);
static void kz_entry_set_property   (GObject         *object,
				     guint            prop_id,
				     const GValue    *value,
				     GParamSpec      *pspec);
static void kz_entry_get_property   (GObject         *object,
				     guint            prop_id,
				     GValue          *value,
				     GParamSpec      *pspec);

static void     kz_entry_realize              (GtkWidget        *widget);
static void     kz_entry_unrealize            (GtkWidget        *widget);
static gboolean kz_entry_expose               (GtkWidget        *widget,
					       GdkEventExpose   *event);
static gboolean kz_entry_button_press         (GtkWidget        *widget,
					       GdkEventButton   *event);
static gboolean kz_entry_button_release       (GtkWidget        *widget,
					       GdkEventButton   *event);
static void     kz_entry_size_allocate        (GtkWidget        *widget,
					       GtkAllocation    *allocation);

static void     gtk_entry_update_primary_selection (GtkEntry       *entry);
static void     gtk_entry_adjust_scroll            (GtkEntry       *entry);
static void     get_text_area_size                 (GtkEntry       *entry,
						    gint           *x,
						    gint           *y,
						    gint           *width,
						    gint           *height);
static void     get_widget_window_size             (GtkEntry       *entry,
						    gint           *x,
						    gint           *y,
						    gint           *width,
						    gint           *height);
static GtkEntryClass *parent_class = NULL;

 
KZ_OBJECT_GET_TYPE(kz_entry, "KzEntry", KzEntry,
		   kz_entry_class_init, kz_entry_init,
		   GTK_TYPE_ENTRY)

static void
kz_entry_class_init (KzEntryClass *klass)
{
	GObjectClass *gobject_class;
	GtkWidgetClass *widget_class;

	parent_class = g_type_class_peek_parent (klass);

	gobject_class = (GObjectClass *) klass;

	widget_class  = (GtkWidgetClass *) klass;

	gobject_class->finalize     = kz_entry_finalize;
	gobject_class->set_property = kz_entry_set_property;
	gobject_class->get_property = kz_entry_get_property;

	widget_class->realize              = kz_entry_realize;
	widget_class->unrealize            = kz_entry_unrealize;
	widget_class->expose_event         = kz_entry_expose;
	widget_class->button_press_event   = kz_entry_button_press;
	widget_class->button_release_event = kz_entry_button_release;
	widget_class->size_allocate        = kz_entry_size_allocate;

	g_object_class_install_property (gobject_class,
					 PROP_BACKTEXT,
					 g_param_spec_string ("text",
							      _("Text"),
							      _("The background text of the entry"),
							      "",
							      G_PARAM_READABLE | G_PARAM_WRITABLE));
	g_object_class_install_property (gobject_class,
					 PROP_STOCK,
					 g_param_spec_string ("stock",
							      _("Stock ID"),
							      _("Stock ID for a icon"),
							      NULL,
							      G_PARAM_READWRITE));
	g_object_class_install_property (gobject_class,
					 PROP_PIXBUF,
					 g_param_spec_object ("pixbuf",
							      _("Pixbuf"),
							      _("A GdkPixbuf icon"),
							      GDK_TYPE_PIXBUF,
							      G_PARAM_READWRITE));
}


static void
kz_entry_init (KzEntry *entry)
{
	g_return_if_fail (KZ_IS_ENTRY (entry));

	entry->backtext     = NULL;
	entry->pixbuf       = NULL;
	entry->stock_id     = NULL;
	entry->icon_type    = KZ_ENTRY_ICON_EMPTY;
	entry->icon_width   = 0;
	entry->icon_height  = 0;

	gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, 
			     &entry->icon_width,
			     &entry->icon_height);
}


static void
kz_entry_finalize (GObject *object)
{
	KzEntry *entry = KZ_ENTRY (object);

	if (entry->backtext)
		g_free (entry->backtext);
	entry->backtext = NULL;

	if (entry->stock_id)
		g_free (entry->stock_id);
	entry->stock_id = NULL;
	if (entry->pixbuf)
		g_object_unref (entry->pixbuf);
	entry->pixbuf = NULL;

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


static void
kz_entry_set_property (GObject *object,
		       guint prop_id,
		       const GValue *value,
		       GParamSpec *pspec)
{
	KzEntry *entry = KZ_ENTRY(object);
  
	switch (prop_id)
	{
	 case PROP_BACKTEXT:
		kz_entry_set_backtext (entry, g_value_get_string (value));
		break;
	 case PROP_PIXBUF:
		kz_entry_set_icon_from_pixbuf(entry,
					      g_value_get_object (value));
		break;
	 case PROP_STOCK:
		break;
	 default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}


static void
kz_entry_get_property (GObject *object,
		       guint prop_id,
		       GValue *value,
		       GParamSpec *pspec)
{
	KzEntry *entry = KZ_ENTRY(object);

	switch (prop_id)
	{
	 case PROP_BACKTEXT:
		g_value_set_string (value, kz_entry_get_backtext (entry));
		break;
	 case PROP_PIXBUF:
		g_value_set_object (value,
				    (GObject*) entry->pixbuf);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}


static void
kz_entry_realize (GtkWidget *widget)
{
	GtkEntry *entry;
	GtkEditable *editable;
	GdkWindowAttr attributes;
	gint attributes_mask;

	GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
	entry = GTK_ENTRY (widget);
	editable = GTK_EDITABLE (widget);

	attributes.window_type = GDK_WINDOW_CHILD;
  
	get_widget_window_size (entry, &attributes.x, &attributes.y, &attributes.width, &attributes.height);

	attributes.wclass = GDK_INPUT_OUTPUT;
	attributes.visual = gtk_widget_get_visual (widget);
	attributes.colormap = gtk_widget_get_colormap (widget);
	attributes.event_mask = gtk_widget_get_events (widget);
	attributes.event_mask |= (GDK_EXPOSURE_MASK |
				  GDK_BUTTON_PRESS_MASK |
				  GDK_BUTTON_RELEASE_MASK |
				  GDK_BUTTON1_MOTION_MASK |
				  GDK_BUTTON3_MOTION_MASK |
				  GDK_POINTER_MOTION_HINT_MASK |
				  GDK_POINTER_MOTION_MASK |
				  GDK_ENTER_NOTIFY_MASK |
				  GDK_LEAVE_NOTIFY_MASK);
	attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

	widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
	gdk_window_set_user_data (widget->window, entry);

	get_text_area_size (entry, &attributes.x, &attributes.y, &attributes.width, &attributes.height);

	attributes.cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), GDK_XTERM);
	attributes_mask |= GDK_WA_CURSOR;

	attributes.x += KZ_ENTRY(entry)->icon_width + INNER_BORDER;
	attributes.width -= KZ_ENTRY(entry)->icon_width + INNER_BORDER;
	entry->text_area = gdk_window_new (widget->window, &attributes, attributes_mask);
	gdk_window_set_user_data (entry->text_area, entry);

	attributes.x -= KZ_ENTRY(entry)->icon_width + INNER_BORDER;
	attributes.width = KZ_ENTRY(entry)->icon_width + INNER_BORDER;
	KZ_ENTRY(entry)->icon_area = gdk_window_new (widget->window, &attributes, attributes_mask);
	gdk_window_set_user_data (KZ_ENTRY(entry)->icon_area, entry);

	gdk_cursor_unref (attributes.cursor);

	widget->style = gtk_style_attach (widget->style, widget->window);

	gdk_window_set_background (widget->window, &widget->style->base[GTK_WIDGET_STATE (widget)]);
	gdk_window_set_background (entry->text_area, &widget->style->base[GTK_WIDGET_STATE (widget)]);
	gdk_window_set_background (KZ_ENTRY(entry)->icon_area, &widget->style->base[GTK_WIDGET_STATE (widget)]);

	gdk_window_show (entry->text_area);
	gdk_window_show (KZ_ENTRY(entry)->icon_area);

	gtk_im_context_set_client_window (entry->im_context, entry->text_area);
	
	gtk_entry_adjust_scroll (entry);
	gtk_entry_update_primary_selection (entry);
}

static void
kz_entry_unrealize (GtkWidget *widget)
{
	KzEntry *kzentry = KZ_ENTRY (widget);

	if (kzentry->icon_area)
	{
		gdk_window_set_user_data (kzentry->icon_area, NULL);
		gdk_window_destroy (kzentry->icon_area);
		kzentry->icon_area = NULL;
	}

	if (GTK_WIDGET_CLASS (parent_class)->unrealize)
		(* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}

static void
get_widget_window_size (GtkEntry *entry,
                        gint     *x,
                        gint     *y,
                        gint     *width,
                        gint     *height)
{
	GtkRequisition requisition;
	GtkWidget *widget = GTK_WIDGET (entry);
      
	gtk_widget_get_child_requisition (widget, &requisition);

	if (x)
		*x = widget->allocation.x;

	if (y)
	{
		if (entry->is_cell_renderer)
			*y = widget->allocation.y;
		else
			*y = widget->allocation.y + (widget->allocation.height - requisition.height) / 2;
	}

	if (width)
		*width = widget->allocation.width;

	if (height)
	{
		if (entry->is_cell_renderer)
			*height = widget->allocation.height;
		else
			*height = requisition.height;
	}
}

GtkWidget *
kz_entry_new (void)
{
	KzEntry *kzentry;

	kzentry = KZ_ENTRY(g_object_new (KZ_TYPE_ENTRY, NULL));

	return GTK_WIDGET (kzentry);
}


GtkWidget *
kz_entry_new_with_stock (const gchar *stock_id)
{
	KzEntry *kzentry;

	kzentry = KZ_ENTRY(g_object_new (KZ_TYPE_ENTRY, NULL));

	return GTK_WIDGET (kzentry);
}


static void
get_borders (GtkEntry *entry,
             gint *xborder,
             gint *yborder)
{
	GtkWidget *widget = GTK_WIDGET (entry);
	gint focus_width;
	gboolean interior_focus;

	gtk_widget_style_get (widget,
			      "interior-focus", &interior_focus,
			      "focus-line-width", &focus_width,
			      NULL);

	if (entry->has_frame)
	{
		*xborder = widget->style->xthickness;
		*yborder = widget->style->ythickness;
	}
	else
	{
		*xborder = 0;
		*yborder = 0;
	}

	if (!interior_focus)
	{
		*xborder += focus_width;
		*yborder += focus_width;
	}
}

static void
get_text_area_size (GtkEntry *entry,
                    gint *x,
                    gint *y,
                    gint *width,
                    gint *height)
{
	gint xborder, yborder;
	GtkRequisition requisition;
	GtkWidget *widget = GTK_WIDGET (entry);

	gtk_widget_get_child_requisition (widget, &requisition);

	get_borders (entry, &xborder, &yborder);

	if (x)
		*x = xborder;

	if (y)
		*y = yborder;
  
	if (width)
		*width = GTK_WIDGET (entry)->allocation.width - xborder * 2;

	if (height)
		*height = requisition.height - yborder * 2;
}


static void
append_char (GString *str,
             gunichar ch,
             gint count)
{
	gint i;
	gint char_len;
	gchar buf[7];
  
	char_len = g_unichar_to_utf8 (ch, buf);
  
	i = 0;
	while (i < count)
	{
		g_string_append_len (str, buf, char_len);
		++i;
	}
}

static void
gtk_entry_reset_layout (GtkEntry *entry)
{
	if (entry->cached_layout)
	{
		g_object_unref (entry->cached_layout);
		entry->cached_layout = NULL;
	}
}

static PangoLayout *
gtk_entry_create_layout (GtkEntry *entry,
			 gboolean include_preedit)
{
	PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (entry), NULL);
	PangoAttrList *tmp_attrs = pango_attr_list_new ();
  
	gchar *preedit_string = NULL;
	gint preedit_length = 0;
	PangoAttrList *preedit_attrs = NULL;

	pango_layout_set_single_paragraph_mode (layout, TRUE);
  
	if (include_preedit)
	{
		gtk_im_context_get_preedit_string (entry->im_context,
						   &preedit_string, &preedit_attrs, NULL);
		preedit_length = entry->preedit_length;
	}

	if (preedit_length)
	{
		GString *tmp_string = g_string_new (NULL);
      
		gint cursor_index = g_utf8_offset_to_pointer (entry->text, entry->current_pos) - entry->text;
      
		if (entry->visible)
		{
			g_string_prepend_len (tmp_string, entry->text, entry->n_bytes);
			g_string_insert (tmp_string, cursor_index, preedit_string);
		}
		else
		{
			gint ch_len;
			gint preedit_len_chars;
			gunichar invisible_char;
          
			ch_len = g_utf8_strlen (entry->text, entry->n_bytes);
			preedit_len_chars = g_utf8_strlen (preedit_string, -1);
			ch_len += preedit_len_chars;

			if (entry->invisible_char != 0)
				invisible_char = entry->invisible_char;
			else
				invisible_char = ' '; /* just pick a char */
          
			append_char (tmp_string, invisible_char, ch_len);
          
          /* Fix cursor index to point to invisible char corresponding
           * to the preedit, fix preedit_length to be the length of
           * the invisible chars representing the preedit
           */
			cursor_index =
				g_utf8_offset_to_pointer (tmp_string->str, entry->current_pos) -
				tmp_string->str;
			preedit_length =
				preedit_len_chars *
				g_unichar_to_utf8 (invisible_char, NULL);
		}
      
		pango_layout_set_text (layout, tmp_string->str, tmp_string->len);
      
		pango_attr_list_splice (tmp_attrs, preedit_attrs,
					cursor_index, preedit_length);
      
		g_string_free (tmp_string, TRUE);
	}
	else
	{
		if (entry->visible)
		{
		pango_layout_set_text (layout, entry->text, entry->n_bytes);
		}
		else
		{
			GString *str = g_string_new (NULL);
			gunichar invisible_char;
          
			if (entry->invisible_char != 0)
				invisible_char = entry->invisible_char;
			else
				invisible_char = ' '; /* just pick a char */
          
			append_char (str, invisible_char, entry->text_length);
			pango_layout_set_text (layout, str->str, str->len);
			g_string_free (str, TRUE);
		}
	}
      
	pango_layout_set_attributes (layout, tmp_attrs);

	if (preedit_string)
		g_free (preedit_string);
	if (preedit_attrs)
		pango_attr_list_unref (preedit_attrs);
      
	pango_attr_list_unref (tmp_attrs);

	return layout;
}

static PangoLayout *
gtk_entry_ensure_layout (GtkEntry *entry,
			 gboolean include_preedit)
{
	if (entry->preedit_length > 0 &&
	    !include_preedit != !entry->cache_includes_preedit)
		gtk_entry_reset_layout (entry);

	if (!entry->cached_layout)
	{
		entry->cached_layout = gtk_entry_create_layout (entry, include_preedit);
		entry->cache_includes_preedit = include_preedit;
	}
  
	return entry->cached_layout;
}

static void
get_layout_position (GtkEntry *entry,
                     gint *x,
                     gint *y)
{
	PangoLayout *layout;
	PangoRectangle logical_rect;
	gint area_width, area_height;
	gint y_pos;
	PangoLayoutLine *line;
  
	layout = gtk_entry_ensure_layout (entry, TRUE);

	get_text_area_size (entry, NULL, NULL, &area_width, &area_height);      
      
	area_height = PANGO_SCALE * (area_height - 2 * INNER_BORDER);
  
	line = pango_layout_get_lines (layout)->data;
	pango_layout_line_get_extents (line, NULL, &logical_rect);
  
	/* Align primarily for locale's ascent/descent */
	y_pos = ((area_height - entry->ascent - entry->descent) / 2 + 
		 entry->ascent + logical_rect.y);
  
	/* Now see if we need to adjust to fit in actual drawn string */
	if (logical_rect.height > area_height)
		y_pos = (area_height - logical_rect.height) / 2;
	else if (y_pos < 0)
		y_pos = 0;
	else if (y_pos + logical_rect.height > area_height)
		y_pos = area_height - logical_rect.height;
  
	y_pos = INNER_BORDER + y_pos / PANGO_SCALE;

	if (x)
		*x = INNER_BORDER - entry->scroll_offset;

	if (y)
		*y = y_pos;
}


/*
 * Like gtk_editable_get_chars, but handle not-visible entries
 * correctly.
 */
static char *    
gtk_entry_get_public_chars (GtkEntry *entry,
			    gint start,
			    gint end)
{
	if (end < 0)
		end = entry->text_length;
  
	if (entry->visible)
		return gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
	else if (!entry->invisible_char)
		return g_strdup ("");
	else
	{
		GString *str = g_string_new (NULL);
		append_char (str, entry->invisible_char, end - start);
		return g_string_free (str, FALSE);
	}
}


static void
primary_get_cb (GtkClipboard *clipboard,
		GtkSelectionData *selection_data,
		guint info,
		gpointer data)
{
	GtkEntry *entry = GTK_ENTRY (data);
	gint start, end;
  
	if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end))
	{
		gchar *str = gtk_entry_get_public_chars (entry, start, end);
		gtk_selection_data_set_text (selection_data, str, -1);
		g_free (str);
	}
}

static void
primary_clear_cb (GtkClipboard *clipboard,
		  gpointer data)
{
	GtkEntry *entry = GTK_ENTRY (data);

	gtk_editable_select_region (GTK_EDITABLE (entry), entry->current_pos, entry->current_pos);
}

static void
gtk_entry_update_primary_selection (GtkEntry *entry)
{
	static const GtkTargetEntry targets[] = {
		{ "UTF8_STRING", 0, 0 },
		{ "STRING", 0, 0 },
		{ "TEXT",   0, 0 }, 
		{ "COMPOUND_TEXT", 0, 0 }
	};
  
	GtkClipboard *clipboard;
	gint start, end;

	if (!GTK_WIDGET_REALIZED (entry))
		return;

	clipboard = gtk_widget_get_clipboard (GTK_WIDGET (entry), GDK_SELECTION_PRIMARY);
  
	if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end))
	{
		if (!gtk_clipboard_set_with_owner (clipboard, targets, G_N_ELEMENTS (targets),
						   primary_get_cb, primary_clear_cb, G_OBJECT (entry)))
			primary_clear_cb (clipboard, entry);
	}
	else
	{
		if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (entry))
			gtk_clipboard_clear (clipboard);
	}
}


static void
gtk_entry_get_cursor_locations (GtkEntry *entry,
				CursorType type,
				gint *strong_x,
				gint *weak_x)
{
	if (!entry->visible && !entry->invisible_char)
	{
		if (strong_x)
			*strong_x = 0;
      
		if (weak_x)
			*weak_x = 0;
	}
	else
	{
		PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE);
		const gchar *text = pango_layout_get_text (layout);
		PangoRectangle strong_pos, weak_pos;
		gint index;
  
		if (type == CURSOR_STANDARD)
		{
			index = g_utf8_offset_to_pointer (text, entry->current_pos + entry->preedit_cursor) - text;
		}
		else /* type == CURSOR_DND */
		{
			index = g_utf8_offset_to_pointer (text, entry->dnd_position) - text;

			if (entry->dnd_position > entry->current_pos)
			{
				if (entry->visible)
					index += entry->preedit_length;
				else
				{
					gint preedit_len_chars = g_utf8_strlen (text, -1) - entry->text_length;
					index += preedit_len_chars * g_unichar_to_utf8 (entry->invisible_char, NULL);
				}
			}
		}
      
		pango_layout_get_cursor_pos (layout, index, &strong_pos, &weak_pos);
      
		if (strong_x)
			*strong_x = strong_pos.x / PANGO_SCALE;
      
		if (weak_x)
			*weak_x = weak_pos.x / PANGO_SCALE;
	}
}


#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION > 2)

static void
draw_insertion_cursor (GtkEntry      *entry,
		       GdkRectangle  *cursor_location,
		       gboolean       is_primary,
		       PangoDirection direction,
		       gboolean       draw_arrow)
{
	GtkWidget *widget = GTK_WIDGET (entry);
	GtkTextDirection text_dir;

	if (direction == PANGO_DIRECTION_LTR)
		text_dir = GTK_TEXT_DIR_LTR;
	else
		text_dir = GTK_TEXT_DIR_RTL;

	gtk_draw_insertion_cursor (widget, entry->text_area, NULL,
				   cursor_location,
				   is_primary, text_dir, draw_arrow);
}

static void
gtk_entry_draw_cursor (GtkEntry  *entry,
		       CursorType type)
{
	GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (entry)));
	PangoDirection keymap_direction = gdk_keymap_get_direction (keymap);
  
	if (GTK_WIDGET_DRAWABLE (entry))
	{
		GtkWidget *widget = GTK_WIDGET (entry);
		GdkRectangle cursor_location;
		gboolean split_cursor;

		gint xoffset = INNER_BORDER - entry->scroll_offset;
		gint strong_x, weak_x;
		gint text_area_height;
		PangoDirection dir1 = PANGO_DIRECTION_NEUTRAL;
		PangoDirection dir2 = PANGO_DIRECTION_NEUTRAL;
		gint x1 = 0;
		gint x2 = 0;

		gdk_drawable_get_size (entry->text_area, NULL, &text_area_height);
      
		gtk_entry_get_cursor_locations (entry, type, &strong_x, &weak_x);

		g_object_get (gtk_widget_get_settings (widget),
			      "gtk-split-cursor", &split_cursor,
			      NULL);

		dir1 = entry->resolved_dir;
      
		if (split_cursor)
		{
			x1 = strong_x;

			if (weak_x != strong_x)
			{
				dir2 = (entry->resolved_dir == PANGO_DIRECTION_LTR) ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
				x2 = weak_x;
			}
		}
		else
		{
			if (keymap_direction == entry->resolved_dir)
				x1 = strong_x;
			else
				x1 = weak_x;
		}

		cursor_location.x = xoffset + x1;
		cursor_location.y = INNER_BORDER;
		cursor_location.width = 0;
		cursor_location.height = text_area_height - 2 * INNER_BORDER ;

		draw_insertion_cursor (entry,
				       &cursor_location, TRUE, dir1,
				       dir2 != PANGO_DIRECTION_NEUTRAL);
      
		if (dir2 != PANGO_DIRECTION_NEUTRAL)
		{
			cursor_location.x = xoffset + x2;
			draw_insertion_cursor (entry,
					       &cursor_location, FALSE, dir2,
					       TRUE);
		}
	}
}

#else

static void
gtk_entry_draw_cursor (GtkEntry *entry,
		       CursorType type)
{
	GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (entry)));
	GtkTextDirection keymap_direction =
		(gdk_keymap_get_direction (keymap) == PANGO_DIRECTION_LTR) ?
		GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL;
	GtkTextDirection widget_direction = gtk_widget_get_direction (GTK_WIDGET (entry));
	
	if (GTK_WIDGET_DRAWABLE (entry))
	{
		GtkWidget *widget = GTK_WIDGET (entry);
		GdkRectangle cursor_location;
		gboolean split_cursor;

		gint xoffset = INNER_BORDER - entry->scroll_offset;
		gint strong_x, weak_x;
		gint text_area_height;
		GtkTextDirection dir1 = GTK_TEXT_DIR_NONE;
		GtkTextDirection dir2 = GTK_TEXT_DIR_NONE;
		gint x1 = 0;
		gint x2 = 0;
		GdkGC *gc;

		gdk_drawable_get_size (entry->text_area, NULL, &text_area_height);
      
		gtk_entry_get_cursor_locations (entry, type, &strong_x, &weak_x);

		g_object_get (gtk_widget_get_settings (widget),
			      "gtk-split-cursor", &split_cursor,
			      NULL);

		dir1 = widget_direction;
      
		if (split_cursor)
		{
			x1 = strong_x;

			if (weak_x != strong_x)
			{
				dir2 = (widget_direction == GTK_TEXT_DIR_LTR) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR;
				x2 = weak_x;
			}
		}
		else
		{
			if (keymap_direction == widget_direction)
				x1 = strong_x;
			else
				x1 = weak_x;
		}

		cursor_location.x = xoffset + x1;
		cursor_location.y = INNER_BORDER;
		cursor_location.width = 0;
		cursor_location.height = text_area_height - 2 * INNER_BORDER ;

		gc = _gtk_get_insertion_cursor_gc (widget, TRUE);
		_gtk_draw_insertion_cursor (widget, entry->text_area, gc,
					    &cursor_location, dir1,
					    dir2 != GTK_TEXT_DIR_NONE);
		g_object_unref (gc);
      
		if (dir2 != GTK_TEXT_DIR_NONE)
		{
			cursor_location.x = xoffset + x2;
			gc = _gtk_get_insertion_cursor_gc (widget, FALSE);
			_gtk_draw_insertion_cursor (widget, entry->text_area, gc,
						    &cursor_location, dir2,
						    TRUE);
			g_object_unref (gc);
		}
	}
}
#endif

static void
gtk_entry_draw_text (GtkEntry *entry)
{
	GtkWidget *widget;
	PangoLayoutLine *line;
  
	if (!entry->visible && entry->invisible_char == 0)
		return;

	if (GTK_WIDGET_DRAWABLE (entry))
	{
		PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE);
		gint x, y;
		gint start_pos, end_pos;
      
		widget = GTK_WIDGET (entry);
      
		get_layout_position (entry, &x, &y);

		gdk_draw_layout (entry->text_area, widget->style->text_gc [widget->state],
				 x, y,
				 layout);
      
		if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start_pos, &end_pos))
		{
			gint *ranges;
			gint n_ranges, i;
			PangoRectangle logical_rect;
			const gchar *text = pango_layout_get_text (layout);
			gint start_index = g_utf8_offset_to_pointer (text, start_pos) - text;
			gint end_index = g_utf8_offset_to_pointer (text, end_pos) - text;
			GdkRegion *clip_region = gdk_region_new ();
			GdkGC *text_gc;
			GdkGC *selection_gc;

			line = pango_layout_get_lines (layout)->data;
          
			pango_layout_line_get_x_ranges (line, start_index, end_index, &ranges, &n_ranges);

			pango_layout_get_extents (layout, NULL, &logical_rect);
          
			if (GTK_WIDGET_HAS_FOCUS (entry))
			{
				selection_gc = widget->style->base_gc [GTK_STATE_SELECTED];
				text_gc = widget->style->text_gc [GTK_STATE_SELECTED];
			}
			else
			{
				selection_gc = widget->style->base_gc [GTK_STATE_ACTIVE];
				text_gc = widget->style->text_gc [GTK_STATE_ACTIVE];
			}
	  
			for (i=0; i < n_ranges; i++)
			{
				GdkRectangle rect;

				rect.x = KZ_ENTRY(entry)->icon_width + INNER_BORDER - entry->scroll_offset + ranges[2*i] / PANGO_SCALE;
				rect.y = y;
				rect.width = (ranges[2*i + 1] - ranges[2*i]) / PANGO_SCALE;
				rect.height = logical_rect.height / PANGO_SCALE;

				gdk_draw_rectangle (entry->text_area, selection_gc, TRUE,
						    rect.x, rect.y, rect.width, rect.height);

				gdk_region_union_with_rect (clip_region, &rect);
			}

			gdk_gc_set_clip_region (text_gc, clip_region);
			gdk_draw_layout (entry->text_area, text_gc, 
					 x, y,
					 layout);
			gdk_gc_set_clip_region (text_gc, NULL);
	  
			gdk_region_destroy (clip_region);
			g_free (ranges);
		}
	}
}

static void
gtk_entry_draw_frame (GtkWidget *widget)
{
	gint x = 0, y = 0;
	gint width, height;
	gboolean interior_focus;
	gint focus_width;
  
	gtk_widget_style_get (widget,
			      "interior-focus", &interior_focus,
			      "focus-line-width", &focus_width,
			      NULL);
  
	gdk_drawable_get_size (widget->window, &width, &height);
  
	if (GTK_WIDGET_HAS_FOCUS (widget) && !interior_focus)
	{
		x += focus_width;
		y += focus_width;
		width -= 2 * focus_width;
		height -= 2 * focus_width;
	}

	gtk_paint_shadow (widget->style, widget->window,
			  GTK_STATE_NORMAL, GTK_SHADOW_IN,
			  NULL, widget, "entry",
			  x, y, width, height);

	if (GTK_WIDGET_HAS_FOCUS (widget) && !interior_focus)
	{
		x -= focus_width;
		y -= focus_width;
		width += 2 * focus_width;
		height += 2 * focus_width;
      
		gtk_paint_focus (widget->style, widget->window, GTK_WIDGET_STATE (widget), 
				 NULL, widget, "entry",
				 0, 0, width, height);
	}
}


static void
kz_entry_size_allocate (GtkWidget     *widget,
			GtkAllocation *allocation)
{
	GtkEntry *entry = GTK_ENTRY (widget);
  
	widget->allocation = *allocation;
  
	if (GTK_WIDGET_CLASS(parent_class)->size_allocate)
		GTK_WIDGET_CLASS(parent_class)->size_allocate(widget, allocation);

 	if (GTK_WIDGET_REALIZED (widget))
	{
	      gint x, y, width, height;
	      get_text_area_size (entry, &x, &y, &width, &height);
      
	      gdk_window_move_resize (entry->text_area,
        	                      x + INNER_BORDER + KZ_ENTRY(entry)->icon_width, y, width, height);
    	}
}

static gboolean
kz_entry_expose (GtkWidget *widget,
		 GdkEventExpose *event)
{
	KzEntry *entry = KZ_ENTRY (widget);

	if (widget->window == event->window)
		gtk_entry_draw_frame (widget);
	else if (KZ_ENTRY(entry)->icon_area == event->window)
	{
		GdkGC *gc;
		switch (entry->icon_type)
		{
		 case KZ_ENTRY_ICON_PIXBUF:
			gc = gdk_gc_new(KZ_ENTRY(entry)->icon_area);
			gdk_draw_pixbuf(KZ_ENTRY(entry)->icon_area,
					gc,
					entry->pixbuf,
					0, 0,
					INNER_BORDER, INNER_BORDER,
					-1, -1,
					GDK_RGB_DITHER_NONE,
					0, 0);
			g_object_unref(gc);
			break;
		 case KZ_ENTRY_ICON_STOCK:
			{
			GdkPixbuf *pixbuf;
			GtkIconSet *icon_set;
			gc = gdk_gc_new(KZ_ENTRY(entry)->icon_area);
			icon_set = gtk_style_lookup_icon_set (widget->style,
							      entry->stock_id);
			pixbuf = gtk_icon_set_render_icon (icon_set,
							   widget->style,
							   gtk_widget_get_direction (widget),
							   GTK_WIDGET_STATE (widget),
							   entry->icon_size,
							   widget,
							   "entry icon");
			gdk_draw_pixbuf(KZ_ENTRY(entry)->icon_area,
					gc,
					pixbuf,
					0, 0,
					INNER_BORDER, INNER_BORDER,
					-1, -1,
					GDK_RGB_DITHER_NONE,
					0, 0);
			g_object_unref(gc);
			g_object_unref(pixbuf);
			break;
			}
		 default:
			break;
		}
	}
	else if (GTK_ENTRY(entry)->text_area == event->window)
	{
		gint area_width, area_height;
		get_text_area_size (GTK_ENTRY(entry),
				    NULL, NULL,
				    &area_width, &area_height);

		gtk_paint_flat_box (widget->style,
				    GTK_ENTRY(entry)->text_area, 
				    GTK_WIDGET_STATE(widget), GTK_SHADOW_NONE,
				    NULL, widget, "entry_bg", 
				    0, 0, area_width, area_height);
		
		if (entry->backtext && 
		    !GTK_WIDGET_HAS_FOCUS(widget) &&
		    GTK_ENTRY(entry)->text[0] == '\0')
		{
			PangoLayout *layout;
			layout = gtk_widget_create_pango_layout (widget, 
								 entry->backtext);
			gtk_paint_layout (widget->style, 
					  GTK_ENTRY(entry)->text_area, 
					  GTK_STATE_INSENSITIVE,
					  TRUE,
					  NULL, widget, "entry_bg",
					  INNER_BORDER, INNER_BORDER, 
					  layout);
			g_object_unref(layout);
		}
		if ((GTK_ENTRY(entry)->visible ||
		     GTK_ENTRY(entry)->invisible_char != 0) &&
		    GTK_WIDGET_HAS_FOCUS (widget) &&
		    GTK_ENTRY(entry)->selection_bound == GTK_ENTRY(entry)->current_pos &&
		    GTK_ENTRY(entry)->cursor_visible)
			gtk_entry_draw_cursor (GTK_ENTRY (widget), CURSOR_STANDARD);

		if (GTK_ENTRY(entry)->dnd_position != -1)
			gtk_entry_draw_cursor (GTK_ENTRY (widget), CURSOR_DND);

		gtk_entry_draw_text (GTK_ENTRY (widget));
	}

	return FALSE;
}


static void
gtk_entry_adjust_scroll (GtkEntry *entry)
{
	gint min_offset, max_offset;
	gint text_area_width;
	gint strong_x, weak_x;
	gint strong_xoffset, weak_xoffset;
	PangoLayout *layout;
	PangoLayoutLine *line;
	PangoRectangle logical_rect;
	
	if (!GTK_WIDGET_REALIZED (entry))
		return;

	gdk_drawable_get_size (entry->text_area, &text_area_width, NULL);
	text_area_width -= 2 * INNER_BORDER;

	layout = gtk_entry_ensure_layout (entry, TRUE);
	line = pango_layout_get_lines (layout)->data;

	pango_layout_line_get_extents (line, NULL, &logical_rect);

	/* Display as much text as we can */

	if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_LTR)
	{
		min_offset = 0;
		max_offset = MAX (min_offset, logical_rect.width / PANGO_SCALE - text_area_width);
	}
	else
	{
		max_offset = logical_rect.width / PANGO_SCALE - text_area_width;
		min_offset = MIN (0, max_offset);
	}

	entry->scroll_offset = CLAMP (entry->scroll_offset, min_offset, max_offset);

  /* And make sure cursors are on screen. Note that the cursor is
   * actually drawn one pixel into the INNER_BORDER space on
   * the right, when the scroll is at the utmost right. This
   * looks better to to me than confining the cursor inside the
   * border entirely, though it means that the cursor gets one
   * pixel closer to the the edge of the widget on the right than
   * on the left. This might need changing if one changed
   * INNER_BORDER from 2 to 1, as one would do on a
   * small-screen-real-estate display.
   *
   * We always make sure that the strong cursor is on screen, and
   * put the weak cursor on screen if possible.
   */

	gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, &weak_x);
  
	strong_xoffset = strong_x - entry->scroll_offset;

	if (strong_xoffset < 0)
	{
		entry->scroll_offset += strong_xoffset;
		strong_xoffset = 0;
	}
	else if (strong_xoffset > text_area_width)
	{
		entry->scroll_offset += strong_xoffset - text_area_width;
		strong_xoffset = text_area_width;
	}

	weak_xoffset = weak_x - entry->scroll_offset;

	if (weak_xoffset < 0 && strong_xoffset - weak_xoffset <= text_area_width)
	{
		entry->scroll_offset += weak_xoffset;
	}
	else if (weak_xoffset > text_area_width &&
		 strong_xoffset - (weak_xoffset - text_area_width) >= 0)
	{
		entry->scroll_offset += weak_xoffset - text_area_width;
	}

	g_object_notify (G_OBJECT (entry), "scroll_offset");
}


static gint
gtk_entry_find_position (GtkEntry *entry,
			 gint      x)
{
	PangoLayout *layout;
	PangoLayoutLine *line;
	gint index;
	gint pos;
	gboolean trailing;
	const gchar *text;
	gint cursor_index;
  
	layout = gtk_entry_ensure_layout (entry, TRUE);
	text = pango_layout_get_text (layout);
	cursor_index = g_utf8_offset_to_pointer (text, entry->current_pos) - text;
  
	line = pango_layout_get_lines (layout)->data;
	pango_layout_line_x_to_index (line, x * PANGO_SCALE, &index, &trailing);

	if (index >= cursor_index && entry->preedit_length)
	{
		if (index >= cursor_index + entry->preedit_length)
			index -= entry->preedit_length;
		else
		{
			index = cursor_index;
			trailing = 0;
		}
	}

	pos = g_utf8_pointer_to_offset (text, text + index);
	pos += trailing;

	return pos;
}


static gboolean
kz_entry_button_press (GtkWidget *widget,
		       GdkEventButton *event)
{
	gboolean ret = FALSE;

	if (!GTK_WIDGET_HAS_FOCUS (widget))
		KZ_ENTRY(widget)->from_outside = TRUE;
	else
		KZ_ENTRY(widget)->from_outside = FALSE;

	if (GTK_WIDGET_CLASS(parent_class)->button_press_event)
		ret = GTK_WIDGET_CLASS(parent_class)->button_press_event(widget, event);
	
	return ret;
}


static gboolean
kz_entry_button_release (GtkWidget *widget,
			 GdkEventButton *event)
{
	GtkEntry *entry = GTK_ENTRY (widget);
	if (event->window != entry->text_area || entry->button != event->button)
		return FALSE;

	if (entry->in_drag)
	{
		gint tmp_pos = gtk_entry_find_position (entry,
							entry->drag_start_x);

		gtk_editable_set_position (GTK_EDITABLE (entry), tmp_pos);

		entry->in_drag = 0;
	}
  
	entry->button = 0;
  
	if (KZ_ENTRY(entry)->from_outside)
		gtk_editable_select_region(GTK_EDITABLE(widget), 0, -1);

	gtk_entry_update_primary_selection (entry);
	      
	return TRUE;
}


void
kz_entry_set_backtext (KzEntry *entry,
		       const gchar *text)
{
	g_return_if_fail (KZ_IS_ENTRY (entry));
	g_return_if_fail (text != NULL);

	if (entry->backtext)
		g_free(entry->backtext);
	entry->backtext = g_strdup(text);
}


void
kz_entry_set_icon_from_stock (KzEntry  *entry,
			      const gchar *stock_id,
			      GtkIconSize size)
{
	g_return_if_fail (KZ_IS_ENTRY (entry));
	g_object_freeze_notify (G_OBJECT (entry));
	
	if (entry->stock_id)
		g_free(entry->stock_id);
	
	entry->stock_id = g_strdup(stock_id);
	entry->icon_type = KZ_ENTRY_ICON_STOCK;
	entry->icon_size = size;

	gtk_icon_size_lookup(size, 
			     &entry->icon_width,
			     &entry->icon_height);
	
	g_object_notify(G_OBJECT (entry), "stock");
  
	g_object_thaw_notify (G_OBJECT (entry));
}


void
kz_entry_set_icon_from_pixbuf (KzEntry *entry,
			       GdkPixbuf *pixbuf)
{
	g_return_if_fail (KZ_IS_ENTRY(entry));
	g_object_freeze_notify (G_OBJECT (entry));

	if (pixbuf == entry->pixbuf)
		return;

	if (entry->pixbuf)
	{
		g_object_unref(entry->pixbuf);
	}

	entry->pixbuf = pixbuf;
	entry->icon_type = KZ_ENTRY_ICON_PIXBUF;
	g_object_ref(entry->pixbuf);

	if (pixbuf == NULL)
	{
		entry->icon_type   = KZ_ENTRY_ICON_EMPTY;
		entry->icon_width  = 0;
		entry->icon_height = 0;
		return;
	}
	entry->icon_width  = gdk_pixbuf_get_width(entry->pixbuf);
	entry->icon_height = gdk_pixbuf_get_height(entry->pixbuf);

	g_object_notify(G_OBJECT (entry), "pixbuf");
  
	g_object_thaw_notify (G_OBJECT (entry));
}


G_CONST_RETURN gchar *
kz_entry_get_backtext (KzEntry *entry)
{
	g_return_val_if_fail (KZ_IS_ENTRY (entry), NULL);

	return entry->backtext;
}
