From 58cc3326be8fa3b841d0596987090d7cb356e101 Mon Sep 17 00:00:00 2001 From: Guido Guenther Date: Sun, 8 Jun 2008 17:28:55 +0200 Subject: Imported Upstream version 0.7.git30891fc --- gtksecentry/Makefile.am | 7 + gtksecentry/gtksecentry.c | 3412 +++++++++++++++++++++++++++++++++++++++++++++ gtksecentry/gtksecentry.h | 181 +++ 3 files changed, 3600 insertions(+) create mode 100644 gtksecentry/Makefile.am create mode 100644 gtksecentry/gtksecentry.c create mode 100644 gtksecentry/gtksecentry.h (limited to 'gtksecentry') diff --git a/gtksecentry/Makefile.am b/gtksecentry/Makefile.am new file mode 100644 index 0000000..4c15a5a --- /dev/null +++ b/gtksecentry/Makefile.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = $(GTK_CFLAGS) \ + -I$(top_srcdir)/secmem -I$(top_srcdir)/gtksecentry + +noinst_LIBRARIES = libgtksecentry.a + +libgtksecentry_a_SOURCES = gtksecentry.c gtksecentry.h + diff --git a/gtksecentry/gtksecentry.c b/gtksecentry/gtksecentry.c new file mode 100644 index 0000000..e38b9aa --- /dev/null +++ b/gtksecentry/gtksecentry.c @@ -0,0 +1,3412 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * Copyright (C) 2004 Albrecht Dreß + * + * 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. + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* + * Heavily stripped down for use in pinentry-gtk-2 by Albrecht Dreß + * Feb. 2004: + * + * The entry is now always invisible, uses secure memory methods to + * allocate the text memory, and all potentially dangerous methods + * (copy & paste, popup, etc.) have been removed. + */ + +#include +#include +#include +#include + +#include "gtksecentry.h" +#include "memory.h" + +#ifndef _ +# include +# define _(x) gettext(x) +#endif + +#define MIN_SECURE_ENTRY_WIDTH 150 +#define DRAW_TIMEOUT 20 +#define INNER_BORDER 2 + +/* Initial size of buffer, in bytes */ +#define MIN_SIZE 16 + +/* Maximum size of text buffer, in bytes */ +#define MAX_SIZE G_MAXUSHORT + +enum { + ACTIVATE, + MOVE_CURSOR, + INSERT_AT_CURSOR, + DELETE_FROM_CURSOR, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_CURSOR_POSITION, + PROP_SELECTION_BOUND, + PROP_MAX_LENGTH, + PROP_HAS_FRAME, + PROP_INVISIBLE_CHAR, + PROP_ACTIVATES_DEFAULT, + PROP_WIDTH_CHARS, + PROP_SCROLL_OFFSET, + PROP_TEXT +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +/* GObject, GtkObject methods + */ +static void gtk_secure_entry_class_init(GtkSecureEntryClass * klass); +static void gtk_secure_entry_editable_init(GtkEditableClass * iface); +static void gtk_secure_entry_cell_editable_init(GtkCellEditableIface * + iface); +static void gtk_secure_entry_init(GtkSecureEntry * entry); +static void gtk_secure_entry_set_property(GObject * object, + guint prop_id, + const GValue * value, + GParamSpec * pspec); +static void gtk_secure_entry_get_property(GObject * object, + guint prop_id, + GValue * value, + GParamSpec * pspec); +static void gtk_secure_entry_finalize(GObject * object); + +/* GtkWidget methods + */ +static void gtk_secure_entry_realize(GtkWidget * widget); +static void gtk_secure_entry_unrealize(GtkWidget * widget); +static void gtk_secure_entry_size_request(GtkWidget * widget, + GtkRequisition * requisition); +static void gtk_secure_entry_size_allocate(GtkWidget * widget, + GtkAllocation * allocation); +static void gtk_secure_entry_draw_frame(GtkWidget * widget); +static gint gtk_secure_entry_expose(GtkWidget * widget, + GdkEventExpose * event); +static gint gtk_secure_entry_button_press(GtkWidget * widget, + GdkEventButton * event); +static gint gtk_secure_entry_button_release(GtkWidget * widget, + GdkEventButton * event); +static gint gtk_secure_entry_motion_notify(GtkWidget * widget, + GdkEventMotion * event); +static gint gtk_secure_entry_key_press(GtkWidget * widget, + GdkEventKey * event); +static gint gtk_secure_entry_key_release(GtkWidget * widget, + GdkEventKey * event); +static gint gtk_secure_entry_focus_in(GtkWidget * widget, + GdkEventFocus * event); +static gint gtk_secure_entry_focus_out(GtkWidget * widget, + GdkEventFocus * event); +static void gtk_secure_entry_grab_focus(GtkWidget * widget); +static void gtk_secure_entry_style_set(GtkWidget * widget, + GtkStyle * previous_style); +static void gtk_secure_entry_direction_changed(GtkWidget * widget, + GtkTextDirection + previous_dir); +static void gtk_secure_entry_state_changed(GtkWidget * widget, + GtkStateType previous_state); +static void gtk_secure_entry_screen_changed(GtkWidget * widget, + GdkScreen * old_screen); + +/* GtkEditable method implementations + */ +static void gtk_secure_entry_insert_text(GtkEditable * editable, + const gchar * new_text, + gint new_text_length, + gint * position); +static void gtk_secure_entry_delete_text(GtkEditable * editable, + gint start_pos, gint end_pos); +static void gtk_secure_entry_real_set_position(GtkEditable * editable, + gint position); +static gint gtk_secure_entry_get_position(GtkEditable * editable); +static void gtk_secure_entry_set_selection_bounds(GtkEditable * editable, + gint start, gint end); +static gboolean gtk_secure_entry_get_selection_bounds(GtkEditable * + editable, + gint * start, + gint * end); + +/* GtkCellEditable method implementations + */ +static void gtk_secure_entry_start_editing(GtkCellEditable * cell_editable, + GdkEvent * event); + +/* Default signal handlers + */ +static void gtk_secure_entry_real_insert_text(GtkEditable * editable, + const gchar * new_text, + gint new_text_length, + gint * position); +static void gtk_secure_entry_real_delete_text(GtkEditable * editable, + gint start_pos, + gint end_pos); +static void gtk_secure_entry_move_cursor(GtkSecureEntry * entry, + GtkMovementStep step, + gint count, + gboolean extend_selection); +static void gtk_secure_entry_insert_at_cursor(GtkSecureEntry * entry, + const gchar * str); +static void gtk_secure_entry_delete_from_cursor(GtkSecureEntry * entry, + GtkDeleteType type, + gint count); +static void gtk_secure_entry_real_activate(GtkSecureEntry * entry); + +static void gtk_secure_entry_keymap_direction_changed(GdkKeymap * keymap, + GtkSecureEntry * + entry); +/* IM Context Callbacks + */ +static void gtk_secure_entry_commit_cb(GtkIMContext * context, + const gchar * str, + GtkSecureEntry * entry); +static void gtk_secure_entry_preedit_changed_cb(GtkIMContext * context, + GtkSecureEntry * entry); +static gboolean gtk_secure_entry_retrieve_surrounding_cb(GtkIMContext * + context, + GtkSecureEntry * + entry); +static gboolean gtk_secure_entry_delete_surrounding_cb(GtkIMContext * + context, + gint offset, + gint n_chars, + GtkSecureEntry * + entry); + +/* Internal routines + */ +static void gtk_secure_entry_enter_text(GtkSecureEntry * entry, + const gchar * str); +static void gtk_secure_entry_set_positions(GtkSecureEntry * entry, + gint current_pos, + gint selection_bound); +static void gtk_secure_entry_draw_text(GtkSecureEntry * entry); +static void gtk_secure_entry_draw_cursor(GtkSecureEntry * entry); +static PangoLayout *gtk_secure_entry_ensure_layout(GtkSecureEntry * entry, + gboolean + include_preedit); +static void gtk_secure_entry_reset_layout(GtkSecureEntry * entry); +static void gtk_secure_entry_queue_draw(GtkSecureEntry * entry); +static void gtk_secure_entry_reset_im_context(GtkSecureEntry * entry); +static void gtk_secure_entry_recompute(GtkSecureEntry * entry); +static gint gtk_secure_entry_find_position(GtkSecureEntry * entry, gint x); +static void gtk_secure_entry_get_cursor_locations(GtkSecureEntry * entry, + gint * strong_x, + gint * weak_x); +static void gtk_secure_entry_adjust_scroll(GtkSecureEntry * entry); +static gint gtk_secure_entry_move_visually(GtkSecureEntry * editable, + gint start, gint count); +static gint gtk_secure_entry_move_logically(GtkSecureEntry * entry, + gint start, gint count); +static gboolean gtk_secure_entry_mnemonic_activate(GtkWidget * widget, + gboolean group_cycling); +static void gtk_secure_entry_state_changed(GtkWidget * widget, + GtkStateType previous_state); +static void gtk_secure_entry_check_cursor_blink(GtkSecureEntry * entry); +static void gtk_secure_entry_pend_cursor_blink(GtkSecureEntry * entry); +static void get_text_area_size(GtkSecureEntry * entry, + gint * x, + gint * y, gint * width, gint * height); +static void get_widget_window_size(GtkSecureEntry * entry, + gint * x, + gint * y, gint * width, gint * height); + + + +#define _gtk_marshal_VOID__VOID g_cclosure_marshal_VOID__VOID +#define _gtk_marshal_VOID__STRING g_cclosure_marshal_VOID__STRING +static void _gtk_marshal_VOID__ENUM_INT_BOOLEAN(GClosure * closure, + GValue * return_value, + guint n_param_values, + const GValue * + param_values, + gpointer invocation_hint, + gpointer marshal_data); +static void _gtk_marshal_VOID__ENUM_INT(GClosure * closure, + GValue * return_value, + guint n_param_values, + const GValue * param_values, + gpointer invocation_hint, + gpointer marshal_data); + + +static GtkWidgetClass *parent_class = NULL; + +gboolean g_use_secure_mem = FALSE; + +# define g_sec_new(type, count) \ + ((type *) g_sec_malloc ((unsigned) sizeof (type) * (count))) + +#define WITH_SECURE_MEM(EXP) do { \ + gboolean tmp = g_use_secure_mem; \ + g_use_secure_mem = TRUE; \ + EXP; \ + g_use_secure_mem = tmp; \ + } while(0) + + +gpointer +g_malloc(gsize size) +{ + gpointer p; + + if (size == 0) + return NULL; + + if (g_use_secure_mem) + p = (gpointer) secmem_malloc(size); + else + p = (gpointer) malloc(size); + if (!p) + g_error("could not allocate %ld bytes", size); + + return p; +} + +gpointer +g_malloc0(gsize size) +{ + gpointer p; + + if (size == 0) + return NULL; + + if (g_use_secure_mem) { + p = (gpointer) secmem_malloc(size); + if (p) + memset(p, 0, size); + } else + p = (gpointer) calloc(size, 1); + if (!p) + g_error("could not allocate %ld bytes", size); + + return p; +} + +gpointer +g_realloc(gpointer mem, gsize size) +{ + gpointer p; + + if (size == 0) { + g_free(mem); + + return NULL; + } + + if (!mem) { + if (g_use_secure_mem) + p = (gpointer) secmem_malloc(size); + else + p = (gpointer) malloc(size); + } else { + if (g_use_secure_mem) { + g_assert(m_is_secure(mem)); + p = (gpointer) secmem_realloc(mem, size); + } else + p = (gpointer) realloc(mem, size); + } + + if (!p) + g_error("could not reallocate %lu bytes", (gulong) size); + + return p; +} + +void +g_free(gpointer mem) +{ + if (mem) { + if (m_is_secure(mem)) + secmem_free(mem); + else + free(mem); + } +} + +GType +gtk_secure_entry_get_type(void) +{ + static GType entry_type = 0; + + if (!entry_type) { + static const GTypeInfo entry_info = { + sizeof(GtkSecureEntryClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_secure_entry_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(GtkSecureEntry), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_secure_entry_init, + }; + + static const GInterfaceInfo editable_info = { + (GInterfaceInitFunc) gtk_secure_entry_editable_init, /* interface_init */ + NULL, /* interface_finalize */ + NULL /* interface_data */ + }; + + static const GInterfaceInfo cell_editable_info = { + (GInterfaceInitFunc) gtk_secure_entry_cell_editable_init, /* interface_init */ + NULL, /* interface_finalize */ + NULL /* interface_data */ + }; + + entry_type = + g_type_register_static(GTK_TYPE_WIDGET, "GtkSecureEntry", + &entry_info, 0); + + g_type_add_interface_static(entry_type, + GTK_TYPE_EDITABLE, &editable_info); + g_type_add_interface_static(entry_type, + GTK_TYPE_CELL_EDITABLE, + &cell_editable_info); + } + + return entry_type; +} + +static void +add_move_binding(GtkBindingSet * binding_set, + guint keyval, + guint modmask, GtkMovementStep step, gint count) +{ + g_return_if_fail((modmask & GDK_SHIFT_MASK) == 0); + + gtk_binding_entry_add_signal(binding_set, keyval, modmask, + "move_cursor", 3, + G_TYPE_ENUM, step, + G_TYPE_INT, count, G_TYPE_BOOLEAN, FALSE); + + /* Selection-extending version */ + gtk_binding_entry_add_signal(binding_set, keyval, + modmask | GDK_SHIFT_MASK, "move_cursor", + 3, G_TYPE_ENUM, step, G_TYPE_INT, count, + G_TYPE_BOOLEAN, TRUE); +} + +static void +gtk_secure_entry_class_init(GtkSecureEntryClass * class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + GtkWidgetClass *widget_class; + GtkBindingSet *binding_set; + + widget_class = (GtkWidgetClass *) class; + parent_class = g_type_class_peek_parent(class); + + gobject_class->finalize = gtk_secure_entry_finalize; + gobject_class->set_property = gtk_secure_entry_set_property; + gobject_class->get_property = gtk_secure_entry_get_property; + + widget_class->realize = gtk_secure_entry_realize; + widget_class->unrealize = gtk_secure_entry_unrealize; + widget_class->size_request = gtk_secure_entry_size_request; + widget_class->size_allocate = gtk_secure_entry_size_allocate; + widget_class->expose_event = gtk_secure_entry_expose; + widget_class->button_press_event = gtk_secure_entry_button_press; + widget_class->button_release_event = gtk_secure_entry_button_release; + widget_class->motion_notify_event = gtk_secure_entry_motion_notify; + widget_class->key_press_event = gtk_secure_entry_key_press; + widget_class->key_release_event = gtk_secure_entry_key_release; + widget_class->focus_in_event = gtk_secure_entry_focus_in; + widget_class->focus_out_event = gtk_secure_entry_focus_out; + widget_class->grab_focus = gtk_secure_entry_grab_focus; + widget_class->style_set = gtk_secure_entry_style_set; + widget_class->direction_changed = gtk_secure_entry_direction_changed; + widget_class->state_changed = gtk_secure_entry_state_changed; + widget_class->screen_changed = gtk_secure_entry_screen_changed; + widget_class->mnemonic_activate = gtk_secure_entry_mnemonic_activate; + + class->move_cursor = gtk_secure_entry_move_cursor; + class->insert_at_cursor = gtk_secure_entry_insert_at_cursor; + class->delete_from_cursor = gtk_secure_entry_delete_from_cursor; + class->activate = gtk_secure_entry_real_activate; + + g_object_class_install_property(gobject_class, + PROP_CURSOR_POSITION, + g_param_spec_int("cursor_position", + _("Cursor Position"), + _ + ("The current position of the insertion cursor in chars"), + 0, MAX_SIZE, 0, + G_PARAM_READABLE)); + + g_object_class_install_property(gobject_class, + PROP_SELECTION_BOUND, + g_param_spec_int("selection_bound", + _("Selection Bound"), + _ + ("The position of the opposite end of the selection from the cursor in chars"), + 0, MAX_SIZE, 0, + G_PARAM_READABLE)); + + g_object_class_install_property(gobject_class, + PROP_MAX_LENGTH, + g_param_spec_int("max_length", + _("Maximum length"), + _ + ("Maximum number of characters for this entry. Zero if no maximum"), + 0, MAX_SIZE, 0, + G_PARAM_READABLE | + G_PARAM_WRITABLE)); + + g_object_class_install_property(gobject_class, + PROP_HAS_FRAME, + g_param_spec_boolean("has_frame", + _("Has Frame"), + _ + ("FALSE removes outside bevel from entry"), + TRUE, + G_PARAM_READABLE | + G_PARAM_WRITABLE)); + + g_object_class_install_property(gobject_class, + PROP_INVISIBLE_CHAR, + g_param_spec_unichar("invisible_char", + _ + ("Invisible character"), + _ + ("The character to use when masking entry contents (in \"password mode\")"), + '*', + G_PARAM_READABLE | + G_PARAM_WRITABLE)); + + g_object_class_install_property(gobject_class, + PROP_ACTIVATES_DEFAULT, + g_param_spec_boolean + ("activates_default", + _("Activates default"), + _ + ("Whether to activate the default widget (such as the default button in a dialog) when Enter is pressed"), + FALSE, + G_PARAM_READABLE | G_PARAM_WRITABLE)); + g_object_class_install_property(gobject_class, PROP_WIDTH_CHARS, + g_param_spec_int("width_chars", + _("Width in chars"), + _ + ("Number of characters to leave space for in the entry"), + -1, G_MAXINT, -1, + G_PARAM_READABLE | + G_PARAM_WRITABLE)); + + g_object_class_install_property(gobject_class, + PROP_SCROLL_OFFSET, + g_param_spec_int("scroll_offset", + _("Scroll offset"), + _ + ("Number of pixels of the entry scrolled off the screen to the left"), + 0, G_MAXINT, 0, + G_PARAM_READABLE)); + + g_object_class_install_property(gobject_class, + PROP_TEXT, + g_param_spec_string("text", + _("Text"), + _ + ("The contents of the entry"), + "", + G_PARAM_READABLE | + G_PARAM_WRITABLE)); + + /* Action signals */ + + signals[ACTIVATE] = + g_signal_new("activate", + G_OBJECT_CLASS_TYPE(gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(GtkSecureEntryClass, activate), + NULL, NULL, _gtk_marshal_VOID__VOID, G_TYPE_NONE, 0); + widget_class->activate_signal = signals[ACTIVATE]; + + signals[MOVE_CURSOR] = + g_signal_new("move_cursor", + G_OBJECT_CLASS_TYPE(gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(GtkSecureEntryClass, move_cursor), + NULL, NULL, + _gtk_marshal_VOID__ENUM_INT_BOOLEAN, + G_TYPE_NONE, 3, + GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT, G_TYPE_BOOLEAN); + + signals[INSERT_AT_CURSOR] = + g_signal_new("insert_at_cursor", + G_OBJECT_CLASS_TYPE(gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(GtkSecureEntryClass, + insert_at_cursor), NULL, NULL, + _gtk_marshal_VOID__STRING, G_TYPE_NONE, 1, + G_TYPE_STRING); + + signals[DELETE_FROM_CURSOR] = + g_signal_new("delete_from_cursor", + G_OBJECT_CLASS_TYPE(gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(GtkSecureEntryClass, + delete_from_cursor), NULL, NULL, + _gtk_marshal_VOID__ENUM_INT, G_TYPE_NONE, 2, + GTK_TYPE_DELETE_TYPE, G_TYPE_INT); + + /* + * Key bindings + */ + + binding_set = gtk_binding_set_by_class(class); + + /* Moving the insertion point */ + add_move_binding(binding_set, GDK_Right, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, 1); + + add_move_binding(binding_set, GDK_Left, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, -1); + + add_move_binding(binding_set, GDK_KP_Right, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, 1); + + add_move_binding(binding_set, GDK_KP_Left, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, -1); + + add_move_binding(binding_set, GDK_Right, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, 1); + + add_move_binding(binding_set, GDK_Left, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, -1); + + add_move_binding(binding_set, GDK_KP_Right, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, 1); + + add_move_binding(binding_set, GDK_KP_Left, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, -1); + + add_move_binding(binding_set, GDK_Home, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); + + add_move_binding(binding_set, GDK_End, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); + + add_move_binding(binding_set, GDK_KP_Home, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); + + add_move_binding(binding_set, GDK_KP_End, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); + + add_move_binding(binding_set, GDK_Home, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, -1); + + add_move_binding(binding_set, GDK_End, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, 1); + + add_move_binding(binding_set, GDK_KP_Home, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, -1); + + add_move_binding(binding_set, GDK_KP_End, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, 1); + + /* Select all + */ + gtk_binding_entry_add_signal(binding_set, GDK_a, GDK_CONTROL_MASK, + "move_cursor", 3, + GTK_TYPE_MOVEMENT_STEP, + GTK_MOVEMENT_BUFFER_ENDS, G_TYPE_INT, -1, + G_TYPE_BOOLEAN, FALSE); + gtk_binding_entry_add_signal(binding_set, GDK_a, GDK_CONTROL_MASK, + "move_cursor", 3, GTK_TYPE_MOVEMENT_STEP, + GTK_MOVEMENT_BUFFER_ENDS, G_TYPE_INT, 1, + G_TYPE_BOOLEAN, TRUE); + + + /* Activate + */ + gtk_binding_entry_add_signal(binding_set, GDK_Return, 0, + "activate", 0); + gtk_binding_entry_add_signal(binding_set, GDK_KP_Enter, 0, + "activate", 0); + + /* Deleting text */ + gtk_binding_entry_add_signal(binding_set, GDK_Delete, 0, + "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_CHARS, + G_TYPE_INT, 1); + + gtk_binding_entry_add_signal(binding_set, GDK_KP_Delete, 0, + "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_CHARS, + G_TYPE_INT, 1); + + gtk_binding_entry_add_signal(binding_set, GDK_BackSpace, 0, + "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_CHARS, + G_TYPE_INT, -1); + + /* Make this do the same as Backspace, to help with mis-typing */ + gtk_binding_entry_add_signal(binding_set, GDK_BackSpace, + GDK_SHIFT_MASK, "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_CHARS, G_TYPE_INT, + -1); + + gtk_binding_entry_add_signal(binding_set, GDK_Delete, GDK_CONTROL_MASK, + "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_WORD_ENDS, + G_TYPE_INT, 1); + + gtk_binding_entry_add_signal(binding_set, GDK_KP_Delete, + GDK_CONTROL_MASK, "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_WORD_ENDS, + G_TYPE_INT, 1); + + gtk_binding_entry_add_signal(binding_set, GDK_BackSpace, + GDK_CONTROL_MASK, "delete_from_cursor", 2, + G_TYPE_ENUM, GTK_DELETE_WORD_ENDS, + G_TYPE_INT, -1); + + gtk_settings_install_property(g_param_spec_boolean + ("gtk-entry-select-on-focus", + _("Select on focus"), + _ + ("Whether to select the contents of an entry when it is focused"), + TRUE, G_PARAM_READWRITE)); +} + +static void +gtk_secure_entry_editable_init(GtkEditableClass * iface) +{ + iface->do_insert_text = gtk_secure_entry_insert_text; + iface->do_delete_text = gtk_secure_entry_delete_text; + iface->insert_text = gtk_secure_entry_real_insert_text; + iface->delete_text = gtk_secure_entry_real_delete_text; + iface->set_selection_bounds = gtk_secure_entry_set_selection_bounds; + iface->get_selection_bounds = gtk_secure_entry_get_selection_bounds; + iface->set_position = gtk_secure_entry_real_set_position; + iface->get_position = gtk_secure_entry_get_position; +} + +static void +gtk_secure_entry_cell_editable_init(GtkCellEditableIface * iface) +{ + iface->start_editing = gtk_secure_entry_start_editing; +} + +static void +gtk_secure_entry_set_property(GObject * object, + guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(object); + + switch (prop_id) { + case PROP_MAX_LENGTH: + gtk_secure_entry_set_max_length(entry, g_value_get_int(value)); + break; + + case PROP_HAS_FRAME: + gtk_secure_entry_set_has_frame(entry, g_value_get_boolean(value)); + break; + + case PROP_INVISIBLE_CHAR: + gtk_secure_entry_set_invisible_char(entry, + g_value_get_uint(value)); + break; + + case PROP_ACTIVATES_DEFAULT: + gtk_secure_entry_set_activates_default(entry, + g_value_get_boolean(value)); + break; + + case PROP_WIDTH_CHARS: + gtk_secure_entry_set_width_chars(entry, g_value_get_int(value)); + break; + + case PROP_TEXT: + gtk_secure_entry_set_text(entry, g_value_get_string(value)); + break; + + case PROP_SCROLL_OFFSET: + case PROP_CURSOR_POSITION: + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +gtk_secure_entry_get_property(GObject * object, + guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(object); + + switch (prop_id) { + case PROP_CURSOR_POSITION: + g_value_set_int(value, entry->current_pos); + break; + case PROP_SELECTION_BOUND: + g_value_set_int(value, entry->selection_bound); + break; + case PROP_MAX_LENGTH: + g_value_set_int(value, entry->text_max_length); + break; + case PROP_HAS_FRAME: + g_value_set_boolean(value, entry->has_frame); + break; + case PROP_INVISIBLE_CHAR: + g_value_set_uint(value, entry->invisible_char); + break; + case PROP_ACTIVATES_DEFAULT: + g_value_set_boolean(value, entry->activates_default); + break; + case PROP_WIDTH_CHARS: + g_value_set_int(value, entry->width_chars); + break; + case PROP_SCROLL_OFFSET: + g_value_set_int(value, entry->scroll_offset); + break; + case PROP_TEXT: + g_value_set_string(value, gtk_secure_entry_get_text(entry)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + +static void +gtk_secure_entry_init(GtkSecureEntry * entry) +{ + GTK_WIDGET_SET_FLAGS(entry, GTK_CAN_FOCUS); + + entry->text_size = MIN_SIZE; + WITH_SECURE_MEM(entry->text = g_malloc(entry->text_size)); + entry->text[0] = '\0'; + + entry->invisible_char = '*'; + entry->width_chars = -1; + entry->is_cell_renderer = FALSE; + entry->editing_canceled = FALSE; + entry->has_frame = TRUE; + + /* This object is completely private. No external entity can gain a reference + * to it; so we create it here and destroy it in finalize(). + */ + entry->im_context = gtk_im_multicontext_new(); + + g_signal_connect(entry->im_context, "commit", + G_CALLBACK(gtk_secure_entry_commit_cb), entry); + g_signal_connect(entry->im_context, "preedit_changed", + G_CALLBACK(gtk_secure_entry_preedit_changed_cb), + entry); + g_signal_connect(entry->im_context, "retrieve_surrounding", + G_CALLBACK(gtk_secure_entry_retrieve_surrounding_cb), + entry); + g_signal_connect(entry->im_context, "delete_surrounding", + G_CALLBACK(gtk_secure_entry_delete_surrounding_cb), + entry); +} + +static void +gtk_secure_entry_finalize(GObject * object) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(object); + + if (entry->cached_layout) + g_object_unref(entry->cached_layout); + + g_object_unref(entry->im_context); + + if (entry->blink_timeout) + g_source_remove(entry->blink_timeout); + + if (entry->recompute_idle) + g_source_remove(entry->recompute_idle); + + entry->text_size = 0; + + if (entry->text) + WITH_SECURE_MEM(g_free(entry->text)); + entry->text = NULL; + + G_OBJECT_CLASS(parent_class)->finalize(object); +} + +static void +gtk_secure_entry_realize(GtkWidget * widget) +{ + GtkSecureEntry *entry; + GtkEditable *editable; + GdkWindowAttr attributes; + gint attributes_mask; + + GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED); + entry = GTK_SECURE_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; + + entry->text_area = + gdk_window_new(widget->window, &attributes, attributes_mask); + gdk_window_set_user_data(entry->text_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_show(entry->text_area); + + gtk_im_context_set_client_window(entry->im_context, entry->text_area); + + gtk_secure_entry_adjust_scroll(entry); +} + +static void +gtk_secure_entry_unrealize(GtkWidget * widget) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + gtk_secure_entry_reset_layout(entry); + + gtk_im_context_set_client_window(entry->im_context, NULL); + + if (entry->text_area) { + gdk_window_set_user_data(entry->text_area, NULL); + gdk_window_destroy(entry->text_area); + entry->text_area = NULL; + } + + if (GTK_WIDGET_CLASS(parent_class)->unrealize) + (*GTK_WIDGET_CLASS(parent_class)->unrealize) (widget); +} + +static void +get_borders(GtkSecureEntry * 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 +gtk_secure_entry_size_request(GtkWidget * widget, + GtkRequisition * requisition) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + PangoFontMetrics *metrics; + gint xborder, yborder; + PangoContext *context; + + context = gtk_widget_get_pango_context(widget); + metrics = pango_context_get_metrics(context, + widget->style->font_desc, + pango_context_get_language + (context)); + + entry->ascent = pango_font_metrics_get_ascent(metrics); + entry->descent = pango_font_metrics_get_descent(metrics); + + get_borders(entry, &xborder, &yborder); + + xborder += INNER_BORDER; + yborder += INNER_BORDER; + + if (entry->width_chars < 0) + requisition->width = MIN_SECURE_ENTRY_WIDTH + xborder * 2; + else { + gint char_width = + pango_font_metrics_get_approximate_char_width(metrics); + gint digit_width = + pango_font_metrics_get_approximate_digit_width(metrics); + gint char_pixels = + (MAX(char_width, digit_width) + PANGO_SCALE - 1) / PANGO_SCALE; + + requisition->width = + char_pixels * entry->width_chars + xborder * 2; + } + + requisition->height = + PANGO_PIXELS(entry->ascent + entry->descent) + yborder * 2; + + pango_font_metrics_unref(metrics); +} + +static void +get_text_area_size(GtkSecureEntry * 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 +get_widget_window_size(GtkSecureEntry * 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; + } +} + +static void +gtk_secure_entry_size_allocate(GtkWidget * widget, + GtkAllocation * allocation) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + widget->allocation = *allocation; + + if (GTK_WIDGET_REALIZED(widget)) { + /* We call gtk_widget_get_child_requisition, since we want (for + * backwards compatibility reasons) the realization here to + * be affected by the usize of the entry, if set + */ + gint x, y, width, height; + + get_widget_window_size(entry, &x, &y, &width, &height); + + gdk_window_move_resize(widget->window, x, y, width, height); + + get_text_area_size(entry, &x, &y, &width, &height); + + gdk_window_move_resize(entry->text_area, x, y, width, height); + + gtk_secure_entry_recompute(entry); + } +} + +static void +gtk_secure_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 gint +gtk_secure_entry_expose(GtkWidget * widget, GdkEventExpose * event) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + if (widget->window == event->window) + gtk_secure_entry_draw_frame(widget); + else if (entry->text_area == event->window) { + gint area_width, area_height; + + get_text_area_size(entry, NULL, NULL, &area_width, &area_height); + + gtk_paint_flat_box(widget->style, entry->text_area, + GTK_WIDGET_STATE(widget), GTK_SHADOW_NONE, + NULL, widget, "entry_bg", + 0, 0, area_width, area_height); + + if ((entry->invisible_char != 0) && + GTK_WIDGET_HAS_FOCUS(widget) && + entry->selection_bound == entry->current_pos + && entry->cursor_visible) + gtk_secure_entry_draw_cursor(GTK_SECURE_ENTRY(widget)); + + gtk_secure_entry_draw_text(GTK_SECURE_ENTRY(widget)); + } + + return FALSE; +} + +static gint +gtk_secure_entry_button_press(GtkWidget * widget, GdkEventButton * event) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + gint tmp_pos; + + if (event->window != entry->text_area || + (entry->button && event->button != entry->button)) + return FALSE; + + entry->button = event->button; + + if (!GTK_WIDGET_HAS_FOCUS(widget)) { + entry->in_click = TRUE; + gtk_widget_grab_focus(widget); + entry->in_click = FALSE; + } + + tmp_pos = + gtk_secure_entry_find_position(entry, + event->x + entry->scroll_offset); + + if (event->button == 1) { + switch (event->type) { + case GDK_BUTTON_PRESS: + gtk_secure_entry_set_positions(entry, tmp_pos, tmp_pos); + break; + + default: + break; + } + + return TRUE; + } + + return FALSE; +} + +static gint +gtk_secure_entry_button_release(GtkWidget * widget, GdkEventButton * event) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + if (event->window != entry->text_area + || entry->button != event->button) + return FALSE; + + entry->button = 0; + + return TRUE; +} + +static gint +gtk_secure_entry_motion_notify(GtkWidget * widget, GdkEventMotion * event) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + gint tmp_pos; + + if (entry->mouse_cursor_obscured) { + GdkCursor *cursor; + + cursor = + gdk_cursor_new_for_display(gtk_widget_get_display(widget), + GDK_XTERM); + gdk_window_set_cursor(entry->text_area, cursor); + gdk_cursor_unref(cursor); + entry->mouse_cursor_obscured = FALSE; + } + + if (event->window != entry->text_area || entry->button != 1) + return FALSE; + + if (event->is_hint || (entry->text_area != event->window)) + gdk_window_get_pointer(entry->text_area, NULL, NULL, NULL); + + { + gint height; + gdk_drawable_get_size(entry->text_area, NULL, &height); + + if (event->y < 0) + tmp_pos = 0; + else if (event->y >= height) + tmp_pos = entry->text_length; + else + tmp_pos = + gtk_secure_entry_find_position(entry, + event->x + + entry->scroll_offset); + + gtk_secure_entry_set_positions(entry, tmp_pos, -1); + } + + return TRUE; +} + +static void +set_invisible_cursor(GdkWindow * window) +{ + GdkBitmap *empty_bitmap; + GdkCursor *cursor; + GdkColor useless; + char invisible_cursor_bits[] = { 0x0 }; + + useless.red = useless.green = useless.blue = 0; + useless.pixel = 0; + + empty_bitmap = gdk_bitmap_create_from_data(window, + invisible_cursor_bits, 1, + 1); + + cursor = gdk_cursor_new_from_pixmap(empty_bitmap, + empty_bitmap, + &useless, &useless, 0, 0); + + gdk_window_set_cursor(window, cursor); + + gdk_cursor_unref(cursor); + + g_object_unref(empty_bitmap); +} + +static void +gtk_secure_entry_obscure_mouse_cursor(GtkSecureEntry * entry) +{ + if (entry->mouse_cursor_obscured) + return; + + set_invisible_cursor(entry->text_area); + + entry->mouse_cursor_obscured = TRUE; +} + +static gint +gtk_secure_entry_key_press(GtkWidget * widget, GdkEventKey * event) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + gtk_secure_entry_pend_cursor_blink(entry); + + if (gtk_im_context_filter_keypress(entry->im_context, event)) { + gtk_secure_entry_obscure_mouse_cursor(entry); + entry->need_im_reset = TRUE; + return TRUE; + } + + if (GTK_WIDGET_CLASS(parent_class)->key_press_event(widget, event)) + /* Activate key bindings + */ + return TRUE; + + return FALSE; +} + +static gint +gtk_secure_entry_key_release(GtkWidget * widget, GdkEventKey * event) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + if (gtk_im_context_filter_keypress(entry->im_context, event)) { + entry->need_im_reset = TRUE; + return TRUE; + } + + return GTK_WIDGET_CLASS(parent_class)->key_release_event(widget, + event); +} + +static gint +gtk_secure_entry_focus_in(GtkWidget * widget, GdkEventFocus * event) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + gtk_widget_queue_draw(widget); + + entry->need_im_reset = TRUE; + gtk_im_context_focus_in(entry->im_context); + + g_signal_connect(gdk_keymap_get_for_display + (gtk_widget_get_display(widget)), "direction_changed", + G_CALLBACK(gtk_secure_entry_keymap_direction_changed), + entry); + + gtk_secure_entry_check_cursor_blink(entry); + + return FALSE; +} + +static gint +gtk_secure_entry_focus_out(GtkWidget * widget, GdkEventFocus * event) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + gtk_widget_queue_draw(widget); + + entry->need_im_reset = TRUE; + gtk_im_context_focus_out(entry->im_context); + + gtk_secure_entry_check_cursor_blink(entry); + + g_signal_handlers_disconnect_by_func(gdk_keymap_get_for_display + (gtk_widget_get_display(widget)), + gtk_secure_entry_keymap_direction_changed, + entry); + + return FALSE; +} + +static void +gtk_secure_entry_grab_focus(GtkWidget * widget) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + gboolean select_on_focus; + + GTK_WIDGET_SET_FLAGS(widget, GTK_CAN_DEFAULT); + GTK_WIDGET_CLASS(parent_class)->grab_focus(widget); + + g_object_get(gtk_widget_get_settings(widget), + "gtk-entry-select-on-focus", &select_on_focus, NULL); + + if (select_on_focus && !entry->in_click) + gtk_editable_select_region(GTK_EDITABLE(widget), 0, -1); +} + +static void +gtk_secure_entry_direction_changed(GtkWidget * widget, + GtkTextDirection previous_dir) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + gtk_secure_entry_recompute(entry); + + GTK_WIDGET_CLASS(parent_class)->direction_changed(widget, + previous_dir); +} + +static void +gtk_secure_entry_state_changed(GtkWidget * widget, + GtkStateType previous_state) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + if (GTK_WIDGET_REALIZED(widget)) { + 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)]); + } + + if (!GTK_WIDGET_IS_SENSITIVE(widget)) { + /* Clear any selection */ + gtk_editable_select_region(GTK_EDITABLE(entry), entry->current_pos, + entry->current_pos); + } + + gtk_widget_queue_draw(widget); +} + +static void +gtk_secure_entry_screen_changed(GtkWidget * widget, GdkScreen * old_screen) +{ + gtk_secure_entry_recompute(GTK_SECURE_ENTRY(widget)); +} + +/* GtkEditable method implementations + */ +static void +gtk_secure_entry_insert_text(GtkEditable * editable, + const gchar * new_text, + gint new_text_length, gint * position) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(editable); + gchar *text; + + if (*position < 0 || *position > entry->text_length) + *position = entry->text_length; + + g_object_ref(editable); + + WITH_SECURE_MEM(text = g_new(gchar, new_text_length + 1)); + + text[new_text_length] = '\0'; + strncpy(text, new_text, new_text_length); + + g_signal_emit_by_name(editable, "insert_text", text, new_text_length, + position); + + WITH_SECURE_MEM(g_free(text)); + + g_object_unref(editable); +} + +static void +gtk_secure_entry_delete_text(GtkEditable * editable, + gint start_pos, gint end_pos) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(editable); + + if (end_pos < 0 || end_pos > entry->text_length) + end_pos = entry->text_length; + if (start_pos < 0) + start_pos = 0; + if (start_pos > end_pos) + start_pos = end_pos; + + g_object_ref(editable); + + g_signal_emit_by_name(editable, "delete_text", start_pos, end_pos); + + g_object_unref(editable); +} + +static void +gtk_secure_entry_set_position_internal(GtkSecureEntry * entry, + gint position, gboolean reset_im) +{ + if (position < 0 || position > entry->text_length) + position = entry->text_length; + + if (position != entry->current_pos || + position != entry->selection_bound) { + if (reset_im) + gtk_secure_entry_reset_im_context(entry); + gtk_secure_entry_set_positions(entry, position, position); + } +} + +static void +gtk_secure_entry_real_set_position(GtkEditable * editable, gint position) +{ + gtk_secure_entry_set_position_internal(GTK_SECURE_ENTRY(editable), + position, TRUE); +} + +static gint +gtk_secure_entry_get_position(GtkEditable * editable) +{ + return GTK_SECURE_ENTRY(editable)->current_pos; +} + +static void +gtk_secure_entry_set_selection_bounds(GtkEditable * editable, + gint start, gint end) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(editable); + + if (start < 0) + start = entry->text_length; + if (end < 0) + end = entry->text_length; + + gtk_secure_entry_reset_im_context(entry); + + gtk_secure_entry_set_positions(entry, + MIN(end, entry->text_length), + MIN(start, entry->text_length)); +} + +static gboolean +gtk_secure_entry_get_selection_bounds(GtkEditable * editable, + gint * start, gint * end) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(editable); + + *start = entry->selection_bound; + *end = entry->current_pos; + + return (entry->selection_bound != entry->current_pos); +} + +static void +gtk_secure_entry_style_set(GtkWidget * widget, GtkStyle * previous_style) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(widget); + + gtk_secure_entry_recompute(entry); + + if (previous_style && GTK_WIDGET_REALIZED(widget)) { + 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)]); + } +} + +/* GtkCellEditable method implementations + */ +static void +gtk_cell_editable_secure_entry_activated(GtkSecureEntry * entry, gpointer data) +{ + gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry)); + gtk_cell_editable_remove_widget(GTK_CELL_EDITABLE(entry)); +} + +static gboolean +gtk_cell_editable_key_press_event(GtkSecureEntry * entry, + GdkEventKey * key_event, gpointer data) +{ + if (key_event->keyval == GDK_Escape) { + entry->editing_canceled = TRUE; + gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry)); + gtk_cell_editable_remove_widget(GTK_CELL_EDITABLE(entry)); + + return TRUE; + } + + /* override focus */ + if (key_event->keyval == GDK_Up || key_event->keyval == GDK_Down) { + gtk_cell_editable_editing_done(GTK_CELL_EDITABLE(entry)); + gtk_cell_editable_remove_widget(GTK_CELL_EDITABLE(entry)); + + return TRUE; + } + + return FALSE; +} + +static void +gtk_secure_entry_start_editing(GtkCellEditable * cell_editable, + GdkEvent * event) +{ + GTK_SECURE_ENTRY(cell_editable)->is_cell_renderer = TRUE; + + g_signal_connect(cell_editable, "activate", + G_CALLBACK(gtk_cell_editable_secure_entry_activated), NULL); + g_signal_connect(cell_editable, "key_press_event", + G_CALLBACK(gtk_cell_editable_key_press_event), NULL); +} + +/* Default signal handlers + */ +static void +gtk_secure_entry_real_insert_text(GtkEditable * editable, + const gchar * new_text, + gint new_text_length, gint * position) +{ + gint _index; + gint n_chars; + + GtkSecureEntry *entry = GTK_SECURE_ENTRY(editable); + + if (new_text_length < 0) + new_text_length = strlen(new_text); + + n_chars = g_utf8_strlen(new_text, new_text_length); + if (entry->text_max_length > 0 + && n_chars + entry->text_length > entry->text_max_length) { + gdk_display_beep(gtk_widget_get_display(GTK_WIDGET(entry))); + n_chars = entry->text_max_length - entry->text_length; + new_text_length = + g_utf8_offset_to_pointer(new_text, n_chars) - new_text; + } + + if (new_text_length + entry->n_bytes + 1 > entry->text_size) { + while (new_text_length + entry->n_bytes + 1 > entry->text_size) { + if (entry->text_size == 0) + entry->text_size = MIN_SIZE; + else { + if (2 * (guint) entry->text_size < MAX_SIZE && + 2 * (guint) entry->text_size > entry->text_size) + entry->text_size *= 2; + else { + entry->text_size = MAX_SIZE; + if (new_text_length > + (gint) entry->text_size - (gint) entry->n_bytes - + 1) { + new_text_length = + (gint) entry->text_size - + (gint) entry->n_bytes - 1; + new_text_length = + g_utf8_find_prev_char(new_text, + new_text + + new_text_length + 1) - + new_text; + n_chars = g_utf8_strlen(new_text, new_text_length); + } + break; + } + } + } + + WITH_SECURE_MEM(entry->text = + g_realloc(entry->text, entry->text_size)); + } + + _index = g_utf8_offset_to_pointer(entry->text, *position) - entry->text; + + g_memmove(entry->text + _index + new_text_length, entry->text + _index, + entry->n_bytes - _index); + memcpy(entry->text + _index, new_text, new_text_length); + + entry->n_bytes += new_text_length; + entry->text_length += n_chars; + + /* NUL terminate for safety and convenience */ + entry->text[entry->n_bytes] = '\0'; + + if (entry->current_pos > *position) + entry->current_pos += n_chars; + + if (entry->selection_bound > *position) + entry->selection_bound += n_chars; + + *position += n_chars; + + gtk_secure_entry_recompute(entry); + + g_signal_emit_by_name(editable, "changed"); + g_object_notify(G_OBJECT(editable), "text"); +} + +static void +gtk_secure_entry_real_delete_text(GtkEditable * editable, + gint start_pos, gint end_pos) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(editable); + + if (start_pos < 0) + start_pos = 0; + if (end_pos < 0 || end_pos > entry->text_length) + end_pos = entry->text_length; + + if (start_pos < end_pos) { + gint start_index = + g_utf8_offset_to_pointer(entry->text, start_pos) - entry->text; + gint end_index = + g_utf8_offset_to_pointer(entry->text, end_pos) - entry->text; + gint current_pos; + gint selection_bound; + + g_memmove(entry->text + start_index, entry->text + end_index, + entry->n_bytes + 1 - end_index); + entry->text_length -= (end_pos - start_pos); + entry->n_bytes -= (end_index - start_index); + + current_pos = entry->current_pos; + if (current_pos > start_pos) + current_pos -= MIN(current_pos, end_pos) - start_pos; + + selection_bound = entry->selection_bound; + if (selection_bound > start_pos) + selection_bound -= MIN(selection_bound, end_pos) - start_pos; + + gtk_secure_entry_set_positions(entry, current_pos, + selection_bound); + + gtk_secure_entry_recompute(entry); + + g_signal_emit_by_name(editable, "changed"); + g_object_notify(G_OBJECT(editable), "text"); + } +} + +/* Compute the X position for an offset that corresponds to the "more important + * cursor position for that offset. We use this when trying to guess to which + * end of the selection we should go to when the user hits the left or + * right arrow key. + */ +static gint +get_better_cursor_x(GtkSecureEntry * entry, gint offset) +{ + GdkKeymap *keymap = + gdk_keymap_get_for_display(gtk_widget_get_display + (GTK_WIDGET(entry))); + PangoDirection keymap_direction = gdk_keymap_get_direction(keymap); + gboolean split_cursor; + + PangoLayout *layout = gtk_secure_entry_ensure_layout(entry, TRUE); + const gchar *text = pango_layout_get_text(layout); + gint _index = g_utf8_offset_to_pointer(text, offset) - text; + + PangoRectangle strong_pos, weak_pos; + + g_object_get(gtk_widget_get_settings(GTK_WIDGET(entry)), + "gtk-split-cursor", &split_cursor, NULL); + + pango_layout_get_cursor_pos(layout, _index, &strong_pos, &weak_pos); + + if (split_cursor) + return strong_pos.x / PANGO_SCALE; + else + return (keymap_direction == + entry->resolved_dir) ? strong_pos.x / + PANGO_SCALE : weak_pos.x / PANGO_SCALE; +} + +static void +gtk_secure_entry_move_cursor(GtkSecureEntry * entry, + GtkMovementStep step, + gint count, gboolean extend_selection) +{ + gint new_pos = entry->current_pos; + + gtk_secure_entry_reset_im_context(entry); + + if (entry->current_pos != entry->selection_bound && !extend_selection) { + /* If we have a current selection and aren't extending it, move to the + * start/or end of the selection as appropriate + */ + switch (step) { + case GTK_MOVEMENT_VISUAL_POSITIONS: + { + gint current_x = + get_better_cursor_x(entry, entry->current_pos); + gint bound_x = + get_better_cursor_x(entry, entry->selection_bound); + + if (count < 0) + new_pos = + current_x < + bound_x ? entry->current_pos : entry-> + selection_bound; + else + new_pos = + current_x > + bound_x ? entry->current_pos : entry-> + selection_bound; + + break; + } + case GTK_MOVEMENT_LOGICAL_POSITIONS: + case GTK_MOVEMENT_DISPLAY_LINE_ENDS: + case GTK_MOVEMENT_PARAGRAPH_ENDS: + case GTK_MOVEMENT_BUFFER_ENDS: + new_pos = count < 0 ? 0 : entry->text_length; + break; + case GTK_MOVEMENT_WORDS: + case GTK_MOVEMENT_DISPLAY_LINES: + case GTK_MOVEMENT_PARAGRAPHS: + case GTK_MOVEMENT_PAGES: + case GTK_MOVEMENT_HORIZONTAL_PAGES: + break; + } + } else { + switch (step) { + case GTK_MOVEMENT_LOGICAL_POSITIONS: + new_pos = + gtk_secure_entry_move_logically(entry, new_pos, count); + break; + case GTK_MOVEMENT_VISUAL_POSITIONS: + new_pos = + gtk_secure_entry_move_visually(entry, new_pos, count); + break; + case GTK_MOVEMENT_DISPLAY_LINE_ENDS: + case GTK_MOVEMENT_PARAGRAPH_ENDS: + case GTK_MOVEMENT_BUFFER_ENDS: + new_pos = count < 0 ? 0 : entry->text_length; + break; + case GTK_MOVEMENT_WORDS: + case GTK_MOVEMENT_DISPLAY_LINES: + case GTK_MOVEMENT_PARAGRAPHS: + case GTK_MOVEMENT_PAGES: + case GTK_MOVEMENT_HORIZONTAL_PAGES: + break; + } + } + + if (extend_selection) + gtk_editable_select_region(GTK_EDITABLE(entry), + entry->selection_bound, new_pos); + else + gtk_editable_set_position(GTK_EDITABLE(entry), new_pos); + + gtk_secure_entry_pend_cursor_blink(entry); +} + +static void +gtk_secure_entry_insert_at_cursor(GtkSecureEntry * entry, + const gchar * str) +{ + GtkEditable *editable = GTK_EDITABLE(entry); + gint pos = entry->current_pos; + + gtk_secure_entry_reset_im_context(entry); + + gtk_editable_insert_text(editable, str, -1, &pos); + gtk_editable_set_position(editable, pos); +} + +static void +gtk_secure_entry_delete_from_cursor(GtkSecureEntry * entry, + GtkDeleteType type, gint count) +{ + GtkEditable *editable = GTK_EDITABLE(entry); + gint start_pos = entry->current_pos; + gint end_pos = entry->current_pos; + + gtk_secure_entry_reset_im_context(entry); + + if (entry->selection_bound != entry->current_pos) { + gtk_editable_delete_selection(editable); + return; + } + + switch (type) { + case GTK_DELETE_CHARS: + end_pos = + gtk_secure_entry_move_logically(entry, entry->current_pos, + count); + gtk_editable_delete_text(editable, MIN(start_pos, end_pos), + MAX(start_pos, end_pos)); + break; + case GTK_DELETE_DISPLAY_LINE_ENDS: + case GTK_DELETE_PARAGRAPH_ENDS: + if (count < 0) + gtk_editable_delete_text(editable, 0, entry->current_pos); + else + gtk_editable_delete_text(editable, entry->current_pos, -1); + break; + case GTK_DELETE_DISPLAY_LINES: + case GTK_DELETE_PARAGRAPHS: + gtk_editable_delete_text(editable, 0, -1); + break; + default: + break; + } + + gtk_secure_entry_pend_cursor_blink(entry); +} + +static void +gtk_secure_entry_delete_cb(GtkSecureEntry * entry) +{ + GtkEditable *editable = GTK_EDITABLE(entry); + gint start, end; + + if (gtk_editable_get_selection_bounds(editable, &start, &end)) + gtk_editable_delete_text(editable, start, end); +} + +static void +gtk_secure_entry_toggle_overwrite(GtkSecureEntry * entry) +{ + entry->overwrite_mode = !entry->overwrite_mode; +} + +static void +gtk_secure_entry_real_activate(GtkSecureEntry * entry) +{ + GtkWindow *window; + GtkWidget *toplevel; + GtkWidget *widget; + + widget = GTK_WIDGET(entry); + + if (entry->activates_default) { + toplevel = gtk_widget_get_toplevel(widget); + if (GTK_IS_WINDOW(toplevel)) { + window = GTK_WINDOW(toplevel); + + if (window && + widget != window->default_widget && + !(widget == window->focus_widget && + (!window->default_widget + || !GTK_WIDGET_SENSITIVE(window->default_widget)))) + gtk_window_activate_default(window); + } + } +} + +static void +gtk_secure_entry_keymap_direction_changed(GdkKeymap * keymap, + GtkSecureEntry * entry) +{ + gtk_secure_entry_recompute(entry); +} + +/* IM Context Callbacks + */ + +static void +gtk_secure_entry_commit_cb(GtkIMContext * context, + const gchar * str, GtkSecureEntry * entry) +{ + gtk_secure_entry_enter_text(entry, str); +} + +static void +gtk_secure_entry_preedit_changed_cb(GtkIMContext * context, + GtkSecureEntry * entry) +{ + gchar *preedit_string; + gint cursor_pos; + + gtk_im_context_get_preedit_string(entry->im_context, + &preedit_string, NULL, &cursor_pos); + entry->preedit_length = strlen(preedit_string); + cursor_pos = CLAMP(cursor_pos, 0, g_utf8_strlen(preedit_string, -1)); + entry->preedit_cursor = cursor_pos; + g_free(preedit_string); + + gtk_secure_entry_recompute(entry); +} + +static gboolean +gtk_secure_entry_retrieve_surrounding_cb(GtkIMContext * context, + GtkSecureEntry * entry) +{ + gtk_im_context_set_surrounding(context, + entry->text, + entry->n_bytes, + g_utf8_offset_to_pointer(entry->text, + entry-> + current_pos) - + entry->text); + + return TRUE; +} + +static gboolean +gtk_secure_entry_delete_surrounding_cb(GtkIMContext * slave, + gint offset, + gint n_chars, + GtkSecureEntry * entry) +{ + gtk_editable_delete_text(GTK_EDITABLE(entry), + entry->current_pos + offset, + entry->current_pos + offset + n_chars); + + return TRUE; +} + +/* Internal functions + */ + +/* Used for im_commit_cb and inserting Unicode chars */ +static void +gtk_secure_entry_enter_text(GtkSecureEntry * entry, const gchar * str) +{ + GtkEditable *editable = GTK_EDITABLE(entry); + gint tmp_pos; + + if (gtk_editable_get_selection_bounds(editable, NULL, NULL)) + gtk_editable_delete_selection(editable); + else { + if (entry->overwrite_mode) + gtk_secure_entry_delete_from_cursor(entry, GTK_DELETE_CHARS, + 1); + } + + tmp_pos = entry->current_pos; + gtk_editable_insert_text(editable, str, strlen(str), &tmp_pos); + gtk_secure_entry_set_position_internal(entry, tmp_pos, FALSE); +} + +/* All changes to entry->current_pos and entry->selection_bound + * should go through this function. + */ +static void +gtk_secure_entry_set_positions(GtkSecureEntry * entry, + gint current_pos, gint selection_bound) +{ + gboolean changed = FALSE; + + g_object_freeze_notify(G_OBJECT(entry)); + + if (current_pos != -1 && entry->current_pos != current_pos) { + entry->current_pos = current_pos; + changed = TRUE; + + g_object_notify(G_OBJECT(entry), "cursor_position"); + } + + if (selection_bound != -1 && entry->selection_bound != selection_bound) { + entry->selection_bound = selection_bound; + changed = TRUE; + + g_object_notify(G_OBJECT(entry), "selection_bound"); + } + + g_object_thaw_notify(G_OBJECT(entry)); + + if (changed) + gtk_secure_entry_recompute(entry); +} + +static void +gtk_secure_entry_reset_layout(GtkSecureEntry * entry) +{ + if (entry->cached_layout) { + g_object_unref(entry->cached_layout); + entry->cached_layout = NULL; + } +} + +static void +update_im_cursor_location(GtkSecureEntry * entry) +{ + GdkRectangle area; + gint strong_x; + gint strong_xoffset; + gint area_width, area_height; + + gtk_secure_entry_get_cursor_locations(entry, &strong_x, NULL); + get_text_area_size(entry, NULL, NULL, &area_width, &area_height); + + strong_xoffset = strong_x - entry->scroll_offset; + if (strong_xoffset < 0) { + strong_xoffset = 0; + } else if (strong_xoffset > area_width) { + strong_xoffset = area_width; + } + area.x = strong_xoffset; + area.y = 0; + area.width = 0; + area.height = area_height; + + gtk_im_context_set_cursor_location(entry->im_context, &area); +} + +static gboolean +recompute_idle_func(gpointer data) +{ + GtkSecureEntry *entry; + + GDK_THREADS_ENTER(); + + entry = GTK_SECURE_ENTRY(data); + + entry->recompute_idle = 0; + + if (gtk_widget_has_screen(GTK_WIDGET(entry))) { + gtk_secure_entry_adjust_scroll(entry); + gtk_secure_entry_queue_draw(entry); + + update_im_cursor_location(entry); + } + + GDK_THREADS_LEAVE(); + + return FALSE; +} + +static void +gtk_secure_entry_recompute(GtkSecureEntry * entry) +{ + gtk_secure_entry_reset_layout(entry); + gtk_secure_entry_check_cursor_blink(entry); + + if (!entry->recompute_idle) { + entry->recompute_idle = g_idle_add_full(G_PRIORITY_HIGH_IDLE + 15, /* between resize and redraw */ + recompute_idle_func, entry, + NULL); + } +} + +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 PangoLayout * +gtk_secure_entry_create_layout(GtkSecureEntry * entry, + gboolean include_preedit) +{ + GtkWidget *widget = GTK_WIDGET(entry); + PangoLayout *layout = gtk_widget_create_pango_layout(widget, 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; + + 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 { + PangoDirection pango_dir; + + pango_dir = pango_find_base_dir(entry->text, entry->n_bytes); + if (pango_dir == PANGO_DIRECTION_NEUTRAL) { + if (GTK_WIDGET_HAS_FOCUS(widget)) { + GdkDisplay *display = gtk_widget_get_display(widget); + GdkKeymap *keymap = gdk_keymap_get_for_display(display); + pango_dir = gdk_keymap_get_direction(keymap); + } else { + if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_LTR) + pango_dir = PANGO_DIRECTION_LTR; + else + pango_dir = PANGO_DIRECTION_RTL; + } + } + + pango_context_set_base_dir(gtk_widget_get_pango_context(widget), + pango_dir); + + pango_layout_set_alignment(layout, pango_dir); + + entry->resolved_dir = pango_dir; + + { + 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_secure_entry_ensure_layout(GtkSecureEntry * entry, + gboolean include_preedit) +{ + if (entry->preedit_length > 0 && + !include_preedit != !entry->cache_includes_preedit) + gtk_secure_entry_reset_layout(entry); + + if (!entry->cached_layout) { + entry->cached_layout = + gtk_secure_entry_create_layout(entry, include_preedit); + entry->cache_includes_preedit = include_preedit; + } + + return entry->cached_layout; +} + +static void +get_layout_position(GtkSecureEntry * entry, gint * x, gint * y) +{ + PangoLayout *layout; + PangoRectangle logical_rect; + gint area_width, area_height; + gint y_pos; + PangoLayoutLine *line; + + layout = gtk_secure_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; +} + +static void +gtk_secure_entry_draw_text(GtkSecureEntry * entry) +{ + GtkWidget *widget; + PangoLayoutLine *line; + + if (entry->invisible_char == 0) + return; + + if (GTK_WIDGET_DRAWABLE(entry)) { + PangoLayout *layout = gtk_secure_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 = + 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 +draw_insertion_cursor(GtkSecureEntry * 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_secure_entry_draw_cursor(GtkSecureEntry * entry) +{ + 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_secure_entry_get_cursor_locations(entry, &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); + } + } +} + +static void +gtk_secure_entry_queue_draw(GtkSecureEntry * entry) +{ + if (GTK_WIDGET_REALIZED(entry)) + gdk_window_invalidate_rect(entry->text_area, NULL, FALSE); +} + +static void +gtk_secure_entry_reset_im_context(GtkSecureEntry * entry) +{ + if (entry->need_im_reset) { + entry->need_im_reset = 0; + gtk_im_context_reset(entry->im_context); + } +} + +static gint +gtk_secure_entry_find_position(GtkSecureEntry * entry, gint x) +{ + PangoLayout *layout; + PangoLayoutLine *line; + gint _index; + gint pos; + gboolean trailing; + const gchar *text; + gint cursor_index; + + layout = gtk_secure_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 void +gtk_secure_entry_get_cursor_locations(GtkSecureEntry * entry, + gint * strong_x, gint * weak_x) +{ + if (!entry->invisible_char) { + if (strong_x) + *strong_x = 0; + + if (weak_x) + *weak_x = 0; + } else { + PangoLayout *layout = gtk_secure_entry_ensure_layout(entry, TRUE); + const gchar *text = pango_layout_get_text(layout); + PangoRectangle strong_pos, weak_pos; + gint _index; + + _index = + g_utf8_offset_to_pointer(text, + entry->current_pos + + entry->preedit_cursor) - text; + + 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; + } +} + +static void +gtk_secure_entry_adjust_scroll(GtkSecureEntry * entry) +{ + gint min_offset, max_offset; + gint text_area_width, text_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_secure_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 */ + + text_width = PANGO_PIXELS(logical_rect.width); + + if (text_width > text_area_width) { + min_offset = 0; + max_offset = text_width - text_area_width; + } else { + min_offset = 0; + max_offset = min_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_secure_entry_get_cursor_locations(entry, &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_secure_entry_move_visually(GtkSecureEntry * entry, + gint start, gint count) +{ + gint _index; + PangoLayout *layout = gtk_secure_entry_ensure_layout(entry, FALSE); + const gchar *text; + + text = pango_layout_get_text(layout); + + _index = g_utf8_offset_to_pointer(text, start) - text; + + while (count != 0) { + int new_index, new_trailing; + gboolean split_cursor; + gboolean strong; + + g_object_get(gtk_widget_get_settings(GTK_WIDGET(entry)), + "gtk-split-cursor", &split_cursor, NULL); + + if (split_cursor) + strong = TRUE; + else { + GdkKeymap *keymap = + gdk_keymap_get_for_display(gtk_widget_get_display + (GTK_WIDGET(entry))); + PangoDirection keymap_direction = + gdk_keymap_get_direction(keymap); + + strong = keymap_direction == entry->resolved_dir; + } + + if (count > 0) { + pango_layout_move_cursor_visually(layout, strong, _index, 0, 1, + &new_index, &new_trailing); + count--; + } else { + pango_layout_move_cursor_visually(layout, strong, _index, 0, -1, + &new_index, &new_trailing); + count++; + } + + if (new_index < 0 || new_index == G_MAXINT) + break; + + _index = new_index; + + while (new_trailing--) + _index = g_utf8_next_char(text + new_index) - text; + } + + return g_utf8_pointer_to_offset(text, text + _index); +} + +static gint +gtk_secure_entry_move_logically(GtkSecureEntry * entry, + gint start, gint count) +{ + gint new_pos = start; + + /* Prevent any leak of information */ + new_pos = CLAMP(start + count, 0, entry->text_length); + + return new_pos; +} + +/* Public API + */ + +GtkWidget * +gtk_secure_entry_new(void) +{ + return g_object_new(GTK_TYPE_SECURE_ENTRY, NULL); +} + +/** + * gtk_secure_entry_new_with_max_length: + * @max: the maximum length of the entry, or 0 for no maximum. + * (other than the maximum length of entries.) The value passed in will + * be clamped to the range 0-65536. + * + * Creates a new #GtkSecureEntry widget with the given maximum length. + * + * Note: the existence of this function is inconsistent + * with the rest of the GTK+ API. The normal setup would + * be to just require the user to make an extra call + * to gtk_secure_entry_set_max_length() instead. It is not + * expected that this function will be removed, but + * it would be better practice not to use it. + * + * Return value: a new #GtkSecureEntry. + **/ +GtkWidget * +gtk_secure_entry_new_with_max_length(gint max) +{ + GtkSecureEntry *entry; + + max = CLAMP(max, 0, MAX_SIZE); + + entry = g_object_new(GTK_TYPE_SECURE_ENTRY, NULL); + entry->text_max_length = max; + + return GTK_WIDGET(entry); +} + +void +gtk_secure_entry_set_text(GtkSecureEntry * entry, const gchar * text) +{ + gint tmp_pos; + + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + g_return_if_fail(text != NULL); + + /* Actually setting the text will affect the cursor and selection; + * if the contents don't actually change, this will look odd to the user. + */ + if (strcmp(entry->text, text) == 0) + return; + + gtk_editable_delete_text(GTK_EDITABLE(entry), 0, -1); + + tmp_pos = 0; + gtk_editable_insert_text(GTK_EDITABLE(entry), text, strlen(text), + &tmp_pos); +} + +void +gtk_secure_entry_append_text(GtkSecureEntry * entry, const gchar * text) +{ + gint tmp_pos; + + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + g_return_if_fail(text != NULL); + + tmp_pos = entry->text_length; + gtk_editable_insert_text(GTK_EDITABLE(entry), text, -1, &tmp_pos); +} + +void +gtk_secure_entry_prepend_text(GtkSecureEntry * entry, const gchar * text) +{ + gint tmp_pos; + + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + g_return_if_fail(text != NULL); + + tmp_pos = 0; + gtk_editable_insert_text(GTK_EDITABLE(entry), text, -1, &tmp_pos); +} + +void +gtk_secure_entry_set_position(GtkSecureEntry * entry, gint position) +{ + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + + gtk_editable_set_position(GTK_EDITABLE(entry), position); +} + +/** + * gtk_secure_entry_set_invisible_char: + * @entry: a #GtkSecureEntry + * @ch: a Unicode character + * + * Sets the character to use in place of the actual text when + * gtk_secure_entry_set_visibility() has been called to set text visibility + * to %FALSE. i.e. this is the character used in "password mode" to + * show the user how many characters have been typed. The default + * invisible char is an asterisk ('*'). If you set the invisible char + * to 0, then the user will get no feedback at all; there will be + * no text on the screen as they type. + * + **/ +void +gtk_secure_entry_set_invisible_char(GtkSecureEntry * entry, gunichar ch) +{ + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + + if (ch == entry->invisible_char) + return; + + entry->invisible_char = ch; + g_object_notify(G_OBJECT(entry), "invisible_char"); + gtk_secure_entry_recompute(entry); +} + +/** + * gtk_secure_entry_get_invisible_char: + * @entry: a #GtkSecureEntry + * + * Retrieves the character displayed in place of the real characters + * for entries with visisbility set to false. See gtk_secure_entry_set_invisible_char(). + * + * Return value: the current invisible char, or 0, if the entry does not + * show invisible text at all. + **/ +gunichar +gtk_secure_entry_get_invisible_char(GtkSecureEntry * entry) +{ + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), 0); + + return entry->invisible_char; +} + +/** + * gtk_secure_entry_get_text: + * @entry: a #GtkSecureEntry + * + * Retrieves the contents of the entry widget. + * See also gtk_editable_get_chars(). + * + * Return value: a pointer to the contents of the widget as a + * string. This string points to internally allocated + * storage in the widget and must not be freed, modified or + * stored. + **/ +G_CONST_RETURN gchar * +gtk_secure_entry_get_text(GtkSecureEntry * entry) +{ + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), NULL); + + return entry->text; +} + +void +gtk_secure_entry_select_region(GtkSecureEntry * entry, + gint start, gint end) +{ + gtk_editable_select_region(GTK_EDITABLE(entry), start, end); +} + +/** + * gtk_secure_entry_set_max_length: + * @entry: a #GtkSecureEntry. + * @max: the maximum length of the entry, or 0 for no maximum. + * (other than the maximum length of entries.) The value passed in will + * be clamped to the range 0-65536. + * + * Sets the maximum allowed length of the contents of the widget. If + * the current contents are longer than the given length, then they + * will be truncated to fit. + **/ +void +gtk_secure_entry_set_max_length(GtkSecureEntry * entry, gint max) +{ + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + + max = CLAMP(max, 0, MAX_SIZE); + + if (max > 0 && entry->text_length > max) + gtk_editable_delete_text(GTK_EDITABLE(entry), max, -1); + + entry->text_max_length = max; + g_object_notify(G_OBJECT(entry), "max_length"); +} + +/** + * gtk_secure_entry_get_max_length: + * @entry: a #GtkSecureEntry + * + * Retrieves the maximum allowed length of the text in + * @entry. See gtk_secure_entry_set_max_length(). + * + * Return value: the maximum allowed number of characters + * in #GtkSecureEntry, or 0 if there is no maximum. + **/ +gint +gtk_secure_entry_get_max_length(GtkSecureEntry * entry) +{ + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), 0); + + return entry->text_max_length; +} + +/** + * gtk_secure_entry_set_activates_default: + * @entry: a #GtkSecureEntry + * @setting: %TRUE to activate window's default widget on Enter keypress + * + * If @setting is %TRUE, pressing Enter in the @entry will activate the default + * widget for the window containing the entry. This usually means that + * the dialog box containing the entry will be closed, since the default + * widget is usually one of the dialog buttons. + * + * (For experts: if @setting is %TRUE, the entry calls + * gtk_window_activate_default() on the window containing the entry, in + * the default handler for the "activate" signal.) + * + **/ +void +gtk_secure_entry_set_activates_default(GtkSecureEntry * entry, + gboolean setting) +{ + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + setting = setting != FALSE; + + if (setting != entry->activates_default) { + entry->activates_default = setting; + g_object_notify(G_OBJECT(entry), "activates_default"); + } +} + +/** + * gtk_secure_entry_get_activates_default: + * @entry: a #GtkSecureEntry + * + * Retrieves the value set by gtk_secure_entry_set_activates_default(). + * + * Return value: %TRUE if the entry will activate the default widget + **/ +gboolean +gtk_secure_entry_get_activates_default(GtkSecureEntry * entry) +{ + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), FALSE); + + return entry->activates_default; +} + +/** + * gtk_secure_entry_set_width_chars: + * @entry: a #GtkSecureEntry + * @n_chars: width in chars + * + * Changes the size request of the entry to be about the right size + * for @n_chars characters. Note that it changes the size + * request, the size can still be affected by + * how you pack the widget into containers. If @n_chars is -1, the + * size reverts to the default entry size. + * + **/ +void +gtk_secure_entry_set_width_chars(GtkSecureEntry * entry, gint n_chars) +{ + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + + if (entry->width_chars != n_chars) { + entry->width_chars = n_chars; + g_object_notify(G_OBJECT(entry), "width_chars"); + gtk_widget_queue_resize(GTK_WIDGET(entry)); + } +} + +/** + * gtk_secure_entry_get_width_chars: + * @entry: a #GtkSecureEntry + * + * Gets the value set by gtk_secure_entry_set_width_chars(). + * + * Return value: number of chars to request space for, or negative if unset + **/ +gint +gtk_secure_entry_get_width_chars(GtkSecureEntry * entry) +{ + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), 0); + + return entry->width_chars; +} + +/** + * gtk_secure_entry_set_has_frame: + * @entry: a #GtkSecureEntry + * @setting: new value + * + * Sets whether the entry has a beveled frame around it. + **/ +void +gtk_secure_entry_set_has_frame(GtkSecureEntry * entry, gboolean setting) +{ + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + + setting = (setting != FALSE); + + if (entry->has_frame == setting) + return; + + gtk_widget_queue_resize(GTK_WIDGET(entry)); + entry->has_frame = setting; + g_object_notify(G_OBJECT(entry), "has_frame"); +} + +/** + * gtk_secure_entry_get_has_frame: + * @entry: a #GtkSecureEntry + * + * Gets the value set by gtk_secure_entry_set_has_frame(). + * + * Return value: whether the entry has a beveled frame + **/ +gboolean +gtk_secure_entry_get_has_frame(GtkSecureEntry * entry) +{ + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), FALSE); + + return entry->has_frame; +} + + +/** + * gtk_secure_entry_get_layout: + * @entry: a #GtkSecureEntry + * + * Gets the #PangoLayout used to display the entry. + * The layout is useful to e.g. convert text positions to + * pixel positions, in combination with gtk_secure_entry_get_layout_offsets(). + * The returned layout is owned by the entry so need not be + * freed by the caller. + * + * Keep in mind that the layout text may contain a preedit string, so + * gtk_secure_entry_layout_index_to_text_index() and + * gtk_secure_entry_text_index_to_layout_index() are needed to convert byte + * indices in the layout to byte indices in the entry contents. + * + * Return value: the #PangoLayout for this entry + **/ +PangoLayout * +gtk_secure_entry_get_layout(GtkSecureEntry * entry) +{ + PangoLayout *layout; + + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), NULL); + + layout = gtk_secure_entry_ensure_layout(entry, TRUE); + + return layout; +} + + +/** + * gtk_secure_entry_layout_index_to_text_index: + * @entry: a #GtkSecureEntry + * @layout_index: byte index into the entry layout text + * + * Converts from a position in the entry contents (returned + * by gtk_secure_entry_get_text()) to a position in the + * entry's #PangoLayout (returned by gtk_secure_entry_get_layout(), + * with text retrieved via pango_layout_get_text()). + * + * Return value: byte index into the entry contents + **/ +gint +gtk_secure_entry_layout_index_to_text_index(GtkSecureEntry * entry, + gint layout_index) +{ + PangoLayout *layout; + const gchar *text; + gint cursor_index; + + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), 0); + + layout = gtk_secure_entry_ensure_layout(entry, TRUE); + text = pango_layout_get_text(layout); + cursor_index = + g_utf8_offset_to_pointer(text, entry->current_pos) - text; + + if (layout_index >= cursor_index && entry->preedit_length) { + if (layout_index >= cursor_index + entry->preedit_length) + layout_index -= entry->preedit_length; + else + layout_index = cursor_index; + } + + return layout_index; +} + +/** + * gtk_secure_entry_text_index_to_layout_index: + * @entry: a #GtkSecureEntry + * @text_index: byte index into the entry contents + * + * Converts from a position in the entry's #PangoLayout(returned by + * gtk_secure_entry_get_layout()) to a position in the entry contents + * (returned by gtk_secure_entry_get_text()). + * + * Return value: byte index into the entry layout text + **/ +gint +gtk_secure_entry_text_index_to_layout_index(GtkSecureEntry * entry, + gint text_index) +{ + PangoLayout *layout; + const gchar *text; + gint cursor_index; + g_return_val_if_fail(GTK_IS_SECURE_ENTRY(entry), 0); + + layout = gtk_secure_entry_ensure_layout(entry, TRUE); + text = pango_layout_get_text(layout); + cursor_index = + g_utf8_offset_to_pointer(text, entry->current_pos) - text; + + if (text_index > cursor_index) + text_index += entry->preedit_length; + + return text_index; +} + +/** + * gtk_secure_entry_get_layout_offsets: + * @entry: a #GtkSecureEntry + * @x: location to store X offset of layout, or %NULL + * @y: location to store Y offset of layout, or %NULL + * + * + * Obtains the position of the #PangoLayout used to render text + * in the entry, in widget coordinates. Useful if you want to line + * up the text in an entry with some other text, e.g. when using the + * entry to implement editable cells in a sheet widget. + * + * Also useful to convert mouse events into coordinates inside the + * #PangoLayout, e.g. to take some action if some part of the entry text + * is clicked. + * + * Note that as the user scrolls around in the entry the offsets will + * change; you'll need to connect to the "notify::scroll_offset" + * signal to track this. Remember when using the #PangoLayout + * functions you need to convert to and from pixels using + * PANGO_PIXELS() or #PANGO_SCALE. + * + * Keep in mind that the layout text may contain a preedit string, so + * gtk_secure_entry_layout_index_to_text_index() and + * gtk_secure_entry_text_index_to_layout_index() are needed to convert byte + * indices in the layout to byte indices in the entry contents. + * + **/ +void +gtk_secure_entry_get_layout_offsets(GtkSecureEntry * entry, + gint * x, gint * y) +{ + gint text_area_x, text_area_y; + + g_return_if_fail(GTK_IS_SECURE_ENTRY(entry)); + + /* this gets coords relative to text area */ + get_layout_position(entry, x, y); + + /* convert to widget coords */ + get_text_area_size(entry, &text_area_x, &text_area_y, NULL, NULL); + + if (x) + *x += text_area_x; + + if (y) + *y += text_area_y; +} + + +/* Quick hack of a popup menu + */ +static void +activate_cb(GtkWidget * menuitem, GtkSecureEntry * entry) +{ + const gchar *signal = + g_object_get_data(G_OBJECT(menuitem), "gtk-signal"); + g_signal_emit_by_name(entry, signal); +} + + +static gboolean +gtk_secure_entry_mnemonic_activate(GtkWidget * widget, + gboolean group_cycling) +{ + gtk_widget_grab_focus(widget); + return TRUE; +} + + +static void +unichar_chosen_func(const char *text, gpointer data) +{ + GtkSecureEntry *entry = GTK_SECURE_ENTRY(data); + + gtk_secure_entry_enter_text(entry, text); +} + +/* We display the cursor when + * + * - the selection is empty, AND + * - the widget has focus + */ + +#define CURSOR_ON_MULTIPLIER 0.66 +#define CURSOR_OFF_MULTIPLIER 0.34 +#define CURSOR_PEND_MULTIPLIER 1.0 + +static gboolean +cursor_blinks(GtkSecureEntry * entry) +{ + GtkSettings *settings = gtk_widget_get_settings(GTK_WIDGET(entry)); + gboolean blink; + + if (GTK_WIDGET_HAS_FOCUS(entry) && + entry->selection_bound == entry->current_pos) { + g_object_get(settings, "gtk-cursor-blink", &blink, NULL); + return blink; + } else + return FALSE; +} + +static gint +get_cursor_time(GtkSecureEntry * entry) +{ + GtkSettings *settings = gtk_widget_get_settings(GTK_WIDGET(entry)); + gint blinktime; + + g_object_get(settings, "gtk-cursor-blink-time", &blinktime, NULL); + + return blinktime; +} + +static void +show_cursor(GtkSecureEntry * entry) +{ + if (!entry->cursor_visible) { + entry->cursor_visible = TRUE; + + if (GTK_WIDGET_HAS_FOCUS(entry) + && entry->selection_bound == entry->current_pos) + gtk_widget_queue_draw(GTK_WIDGET(entry)); + } +} + +static void +hide_cursor(GtkSecureEntry * entry) +{ + if (entry->cursor_visible) { + entry->cursor_visible = FALSE; + + if (GTK_WIDGET_HAS_FOCUS(entry) + && entry->selection_bound == entry->current_pos) + gtk_widget_queue_draw(GTK_WIDGET(entry)); + } +} + +/* + * Blink! + */ +static gint +blink_cb(gpointer data) +{ + GtkSecureEntry *entry; + + GDK_THREADS_ENTER(); + + entry = GTK_SECURE_ENTRY(data); + + if (!GTK_WIDGET_HAS_FOCUS(entry)) { + g_warning + ("GtkSecureEntry - did not receive focus-out-event. If you\n" + "connect a handler to this signal, it must return\n" + "FALSE so the entry gets the event as well"); + } + + g_assert(GTK_WIDGET_HAS_FOCUS(entry)); + g_assert(entry->selection_bound == entry->current_pos); + + if (entry->cursor_visible) { + hide_cursor(entry); + entry->blink_timeout = + g_timeout_add(get_cursor_time(entry) * CURSOR_OFF_MULTIPLIER, + blink_cb, entry); + } else { + show_cursor(entry); + entry->blink_timeout = + g_timeout_add(get_cursor_time(entry) * CURSOR_ON_MULTIPLIER, + blink_cb, entry); + } + + GDK_THREADS_LEAVE(); + + /* Remove ourselves */ + return FALSE; +} + +static void +gtk_secure_entry_check_cursor_blink(GtkSecureEntry * entry) +{ + if (cursor_blinks(entry)) { + if (!entry->blink_timeout) { + entry->blink_timeout = + g_timeout_add(get_cursor_time(entry) * + CURSOR_ON_MULTIPLIER, blink_cb, entry); + show_cursor(entry); + } + } else { + if (entry->blink_timeout) { + g_source_remove(entry->blink_timeout); + entry->blink_timeout = 0; + } + + entry->cursor_visible = TRUE; + } + +} + +static void +gtk_secure_entry_pend_cursor_blink(GtkSecureEntry * entry) +{ + if (cursor_blinks(entry)) { + if (entry->blink_timeout != 0) + g_source_remove(entry->blink_timeout); + + entry->blink_timeout = + g_timeout_add(get_cursor_time(entry) * CURSOR_PEND_MULTIPLIER, + blink_cb, entry); + show_cursor(entry); + } +} + +static inline gboolean +keyval_is_cursor_move(guint keyval) +{ + if (keyval == GDK_Up || keyval == GDK_KP_Up) + return TRUE; + + if (keyval == GDK_Down || keyval == GDK_KP_Down) + return TRUE; + + if (keyval == GDK_Page_Up) + return TRUE; + + if (keyval == GDK_Page_Down) + return TRUE; + + return FALSE; +} + +/* stolen from gtkmarshalers.c */ + +#define g_marshal_value_peek_enum(v) (v)->data[0].v_int +#define g_marshal_value_peek_int(v) (v)->data[0].v_int +#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int + +/* VOID:ENUM,INT,BOOLEAN (gtkmarshalers.list:64) */ +static void +_gtk_marshal_VOID__ENUM_INT_BOOLEAN(GClosure * closure, + GValue * return_value, + guint n_param_values, + const GValue * param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__ENUM_INT_BOOLEAN) (gpointer data1, + gint arg_1, + gint arg_2, + gboolean arg_3, + gpointer data2); + register GMarshalFunc_VOID__ENUM_INT_BOOLEAN callback; + register GCClosure *cc = (GCClosure *) closure; + register gpointer data1, data2; + + g_return_if_fail(n_param_values == 4); + + if (G_CCLOSURE_SWAP_DATA(closure)) { + data1 = closure->data; + data2 = g_value_peek_pointer(param_values + 0); + } else { + data1 = g_value_peek_pointer(param_values + 0); + data2 = closure->data; + } + callback = + (GMarshalFunc_VOID__ENUM_INT_BOOLEAN) (marshal_data ? marshal_data + : cc->callback); + + callback(data1, + g_marshal_value_peek_enum(param_values + 1), + g_marshal_value_peek_int(param_values + 2), + g_marshal_value_peek_boolean(param_values + 3), data2); +} + +static void +_gtk_marshal_VOID__ENUM_INT(GClosure * closure, + GValue * return_value, + guint n_param_values, + const GValue * param_values, + gpointer invocation_hint, + gpointer marshal_data) +{ + typedef void (*GMarshalFunc_VOID__ENUM_INT) (gpointer data1, + gint arg_1, + gint arg_2, + gpointer data2); + register GMarshalFunc_VOID__ENUM_INT callback; + register GCClosure *cc = (GCClosure *) closure; + register gpointer data1, data2; + + g_return_if_fail(n_param_values == 3); + + if (G_CCLOSURE_SWAP_DATA(closure)) { + data1 = closure->data; + data2 = g_value_peek_pointer(param_values + 0); + } else { + data1 = g_value_peek_pointer(param_values + 0); + data2 = closure->data; + } + callback = + (GMarshalFunc_VOID__ENUM_INT) (marshal_data ? marshal_data : cc-> + callback); + + callback(data1, + g_marshal_value_peek_enum(param_values + 1), + g_marshal_value_peek_int(param_values + 2), data2); +} diff --git a/gtksecentry/gtksecentry.h b/gtksecentry/gtksecentry.h new file mode 100644 index 0000000..f621ee2 --- /dev/null +++ b/gtksecentry/gtksecentry.h @@ -0,0 +1,181 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * Copyright (C) 2004 Albrecht Dreß + * + * 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. + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* + * Heavily stripped down for use in pinentry-gtk-2 by Albrecht Dreß + * Feb. 2004: + * + * The entry is now always invisible, uses secure memory methods to + * allocate the text memory, and all potentially dangerous methods + * (copy & paste, popup, etc.) have been removed. + */ + +#ifndef __GTK_SECURE_ENTRY_H__ +#define __GTK_SECURE_ENTRY_H__ + + +#include + +#ifdef __cplusplus +extern "C" { +#ifdef MAKE_EMACS_HAPPY +} +#endif /* MAKE_EMACS_HAPPY */ +#endif /* __cplusplus */ +#define GTK_TYPE_SECURE_ENTRY (gtk_secure_entry_get_type ()) +#define GTK_SECURE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SECURE_ENTRY, GtkSecureEntry)) +#define GTK_SECURE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SECURE_ENTRY, GtkSecureEntryClass)) +#define GTK_IS_SECURE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SECURE_ENTRY)) +#define GTK_IS_SECURE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SECURE_ENTRY)) +#define GTK_SECURE_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SECURE_ENTRY, GtkSecureEntryClass)) +typedef struct _GtkSecureEntry GtkSecureEntry; +typedef struct _GtkSecureEntryClass GtkSecureEntryClass; + +struct _GtkSecureEntry { + GtkWidget widget; + + gchar *text; + + guint overwrite_mode:1; + + guint16 text_length; /* length in use, in chars */ + guint16 text_max_length; + + /*< private > */ + GdkWindow *text_area; + GtkIMContext *im_context; + + gint current_pos; + gint selection_bound; + + PangoLayout *cached_layout; + guint cache_includes_preedit:1; + + guint need_im_reset:1; + + guint has_frame:1; + + guint activates_default:1; + + guint cursor_visible:1; + + guint in_click:1; /* Flag so we don't select all when clicking in entry to focus in */ + + guint is_cell_renderer:1; + guint editing_canceled:1; /* Only used by GtkCellRendererText */ + + guint mouse_cursor_obscured:1; + + guint resolved_dir : 4; /* PangoDirection */ + + guint button; + guint blink_timeout; + guint recompute_idle; + gint scroll_offset; + gint ascent; /* font ascent, in pango units */ + gint descent; /* font descent, in pango units */ + + guint16 text_size; /* allocated size, in bytes */ + guint16 n_bytes; /* length in use, in bytes */ + + guint16 preedit_length; /* length of preedit string, in bytes */ + guint16 preedit_cursor; /* offset of cursor within preedit string, in chars */ + + gunichar invisible_char; + + gint width_chars; +}; + +struct _GtkSecureEntryClass { + GtkWidgetClass parent_class; + + /* Action signals + */ + void (*activate) (GtkSecureEntry * entry); + void (*move_cursor) (GtkSecureEntry * entry, + GtkMovementStep step, + gint count, gboolean extend_selection); + void (*insert_at_cursor) (GtkSecureEntry * entry, const gchar * str); + void (*delete_from_cursor) (GtkSecureEntry * entry, + GtkDeleteType type, gint count); + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); +}; + +GType +gtk_secure_entry_get_type(void) + G_GNUC_CONST; +GtkWidget * +gtk_secure_entry_new(void); +void +gtk_secure_entry_set_invisible_char(GtkSecureEntry * entry, gunichar ch); +gunichar +gtk_secure_entry_get_invisible_char(GtkSecureEntry * entry); +void +gtk_secure_entry_set_has_frame(GtkSecureEntry * entry, gboolean setting); +gboolean +gtk_secure_entry_get_has_frame(GtkSecureEntry * entry); +/* text is truncated if needed */ +void +gtk_secure_entry_set_max_length(GtkSecureEntry * entry, gint max); +gint +gtk_secure_entry_get_max_length(GtkSecureEntry * entry); +void +gtk_secure_entry_set_activates_default(GtkSecureEntry * entry, + gboolean setting); +gboolean +gtk_secure_entry_get_activates_default(GtkSecureEntry * entry); + +void +gtk_secure_entry_set_width_chars(GtkSecureEntry * entry, gint n_chars); +gint +gtk_secure_entry_get_width_chars(GtkSecureEntry * entry); + +/* Somewhat more convenient than the GtkEditable generic functions + */ +void +gtk_secure_entry_set_text(GtkSecureEntry * entry, const gchar * text); +/* returns a reference to the text */ +G_CONST_RETURN gchar * +gtk_secure_entry_get_text(GtkSecureEntry * entry); + +PangoLayout * +gtk_secure_entry_get_layout(GtkSecureEntry * entry); +void +gtk_secure_entry_get_layout_offsets(GtkSecureEntry * entry, + gint * x, gint * y); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif /* __GTK_SECURE_ENTRY_H__ */ -- cgit