From 9582e8159c9862e160d8b7569c534c8196e40686 Mon Sep 17 00:00:00 2001 From: Mike Kestner Date: Mon, 17 Nov 2003 21:05:15 +0000 Subject: renamespace and move to here build new files 2003-11-17 Mike Kestner * gal/widgets/gtk-combo* : renamespace and move to * gal/widgets/gal-combo* : here * gal/widgets/Makefile.am : build new files 2003-11-17 Mike Kestner * e-table-config.c : use new gal_combo_* namespace. svn path=/trunk/; revision=23402 --- widgets/misc/gal-combo-text.c | 433 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 433 insertions(+) create mode 100644 widgets/misc/gal-combo-text.c (limited to 'widgets/misc/gal-combo-text.c') diff --git a/widgets/misc/gal-combo-text.c b/widgets/misc/gal-combo-text.c new file mode 100644 index 0000000000..ba3b4ad7c5 --- /dev/null +++ b/widgets/misc/gal-combo-text.c @@ -0,0 +1,433 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * gtk-combo-text.c - A combo box for selecting from a list. + * Copyright 2000, 2001, Ximian, Inc. + * + * Authors: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License, version 2, as published by the Free Software Foundation. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "gal-combo-text.h" + +#define PARENT_TYPE GAL_COMBO_BOX_TYPE +static GtkObjectClass *gal_combo_text_parent_class; + +static gboolean cb_pop_down (GtkWidget *w, GtkWidget *pop_down, + gpointer dummy); + +static void list_unselect_cb (GtkWidget *list, GtkWidget *child, + gpointer data); + +static void update_list_selection (GalComboText *ct, const gchar *text); + +static void +gal_combo_text_destroy (GtkObject *object) +{ + GalComboText *ct = GAL_COMBO_TEXT (object); + + if (ct->elements != NULL) { + g_hash_table_destroy (ct->elements); + ct->elements = NULL; + } + if (ct->list != NULL) { + g_signal_handlers_disconnect_matched (ct, + G_SIGNAL_MATCH_FUNC, + 0, 0, NULL, + cb_pop_down, NULL); + + g_signal_handlers_disconnect_matched (ct->list, + G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, + 0, 0, NULL, + list_unselect_cb, ct); + ct->list = NULL; + } + + (*gal_combo_text_parent_class->destroy) (object); +} + +static void +gal_combo_text_class_init (GtkObjectClass *object_class) +{ + object_class->destroy = &gal_combo_text_destroy; + gal_combo_text_parent_class = g_type_class_ref (PARENT_TYPE); +} + +static void +gal_combo_text_init (GalComboText *object) +{ +} + +E_MAKE_TYPE (gal_combo_text, + "GalComboText", + GalComboText, + gal_combo_text_class_init, + gal_combo_text_init, + PARENT_TYPE) + +static gint +strcase_equal (gconstpointer v, gconstpointer v2) +{ + return g_strcasecmp ((const gchar*) v, (const gchar*)v2) == 0; +} + + +/* + * a char* hash function from ASU + * + * This is cut/paste from gutils.c + * We've got to do this, because this widget will soon move out of the + * Gnumeric source and into a separate library. + */ +static guint +strcase_hash (gconstpointer v) +{ + const unsigned char *s = (const unsigned char *)v; + const unsigned char *p; + guint h = 0, g; + + for(p = s; *p != '\0'; p += 1) { + h = ( h << 4 ) + tolower (*p); + if ( ( g = h & 0xf0000000 ) ) { + h = h ^ (g >> 24); + h = h ^ g; + } + } + + return h /* % M */; +} + +/** + * gal_combo_text_set_case_sensitive + * @combo_text: ComboText widget + * @val: make case sensitive if TRUE + * + * Specifies whether the text entered into the GtkEntry field and the text + * in the list items is case sensitive. Because the values are stored in a + * hash, it is not legal to change case sensitivity when the list contains + * elements. + * + * Returns: The function returns -1 if request could not be honored. On + * success, it returns 0. + */ +gint +gal_combo_text_set_case_sensitive (GalComboText *combo, gboolean val) +{ + if (combo->elements + && g_hash_table_size (combo->elements) > 0 + && val != combo->case_sensitive) + return -1; + else { + combo->case_sensitive = val; + if (val != combo->case_sensitive) { + GHashFunc hashfunc; + GCompareFunc comparefunc; + + g_hash_table_destroy (combo->elements); + if (combo->case_sensitive) { + hashfunc = g_str_hash; + comparefunc = g_str_equal; + } else { + hashfunc = strcase_hash; + comparefunc = strcase_equal; + } + combo->elements = g_hash_table_new (hashfunc, + comparefunc); + } + return 0; + } +} + +static void +entry_activate_cb (GtkWidget *entry, gpointer data) +{ + GalComboText *combo = GAL_COMBO_TEXT (data); + + update_list_selection (combo, + gtk_entry_get_text (GTK_ENTRY (combo->entry))); +} + + +static void +list_select_cb (GtkWidget *list, GtkWidget *child, gpointer data) +{ + GalComboText *combo = GAL_COMBO_TEXT (data); + GtkEntry *entry = GTK_ENTRY (combo->entry); + gchar *value = (gchar*) g_object_get_data (G_OBJECT (child), "value"); + + g_return_if_fail (entry && value); + + if (combo->cached_entry == child) + combo->cached_entry = NULL; + + gtk_entry_set_text (entry, value); + gtk_signal_handler_block_by_func (GTK_OBJECT (entry), + GTK_SIGNAL_FUNC (entry_activate_cb), + (gpointer) combo); + g_signal_emit_by_name (entry, "activate"); + gtk_signal_handler_unblock_by_func (GTK_OBJECT (entry), + GTK_SIGNAL_FUNC (entry_activate_cb), + (gpointer) combo); + + gal_combo_box_popup_hide (GAL_COMBO_BOX (data)); +} + +static void +list_unselect_cb (GtkWidget *list, GtkWidget *child, gpointer data) +{ + if (GTK_WIDGET_VISIBLE (list)) /* Undo interactive unselect */ + gtk_list_select_child (GTK_LIST (list), child); +} + +static void +cb_toggle (GtkWidget *child, gpointer data) +{ + GalComboText *ct = GAL_COMBO_TEXT (data); + + gtk_list_select_child (GTK_LIST (ct->list), child); +} + +void +gal_combo_text_select_item (GalComboText *ct, int elem) +{ + gtk_list_select_item (GTK_LIST(ct->list), elem); +} + +static void +update_list_selection (GalComboText *ct, const gchar *text) +{ + gpointer candidate; + GtkWidget *child; + + gtk_signal_handler_block_by_func (GTK_OBJECT (ct->list), + GTK_SIGNAL_FUNC (list_select_cb), + (gpointer) ct); + gtk_signal_handler_block_by_func (GTK_OBJECT (ct->list), + GTK_SIGNAL_FUNC (list_unselect_cb), + (gpointer) ct); + + gtk_list_unselect_all (GTK_LIST (ct->list)); + candidate = g_hash_table_lookup (ct->elements, (gconstpointer) text); + if (candidate && GTK_IS_WIDGET (candidate)) { + child = GTK_WIDGET (candidate); + gtk_list_select_child (GTK_LIST (ct->list), child); + gtk_widget_grab_focus (child); + } + gtk_signal_handler_unblock_by_func (GTK_OBJECT (ct->list), + GTK_SIGNAL_FUNC (list_select_cb), + (gpointer) ct); + gtk_signal_handler_unblock_by_func (GTK_OBJECT (ct->list), + GTK_SIGNAL_FUNC (list_unselect_cb), + (gpointer) ct); +} + +void +gal_combo_text_set_text (GalComboText *ct, const gchar *text) +{ + gtk_entry_set_text (GTK_ENTRY (ct->entry), text); + update_list_selection (ct, text); +} + +/* + * We can't just cache the old widget state on entry: If the pointer is + * dragged, we receive two enter-notify-events, and the original cached + * value would be overwritten with the GTK_STATE_ACTIVE we just set. + * + * However, we know that the gtklist only uses GTK_STATE_SELECTED and + * GTK_STATE_NORMAL. We're OK if we only cache those two. + */ +static gboolean +cb_enter (GtkWidget *w, GdkEventCrossing *event, + gpointer user) +{ + GalComboText *ct = user; + GtkStateType state = GTK_WIDGET_STATE (w); + + if (state == GTK_STATE_NORMAL || state == GTK_STATE_SELECTED) { + ct->cached_entry = w; + ct->cache_mouse_state = state; + } + if (state != GTK_STATE_SELECTED) + gtk_widget_set_state (w, GTK_STATE_ACTIVE); + + return TRUE; +} +static gboolean +cb_exit (GtkWidget *w, GdkEventCrossing *event, + gpointer user) +{ + GalComboText *ct = user; + + if (ct->cached_entry == w) + gtk_widget_set_state (w, ct->cache_mouse_state); + + return TRUE; +} + +static gboolean +cb_pop_down (GtkWidget *w, GtkWidget *pop_down, gpointer dummy) +{ + GalComboText *ct = GAL_COMBO_TEXT (w); + + if (ct->cached_entry) + gtk_widget_set_state (ct->cached_entry, ct->cache_mouse_state); + ct->cached_entry = NULL; + + return FALSE; +} + +typedef struct { + GalComboText *ct; + gchar *value; +} WeakRefClosure; + +static void +cb_remove_from_hash (gpointer data, GObject *where_object_was) +{ + WeakRefClosure *closure = data; + + if (closure->ct->elements) + g_hash_table_remove (closure->ct->elements, closure->value); + + g_free (closure->value); + g_free (closure); +} + +void +gal_combo_text_add_item (GalComboText *ct, + const gchar *item, + const gchar *value) +{ + WeakRefClosure *weak_ref_closure; + GtkWidget *listitem; + gchar *value_copy; + + g_return_if_fail (item); + + if (!value) + value = item; + + value_copy = g_strdup (value); + + listitem = gtk_list_item_new_with_label (item); + gtk_widget_show (listitem); + + g_object_set_data_full (G_OBJECT (listitem), "value", + value_copy, g_free); + g_signal_connect (listitem, "enter-notify-event", + G_CALLBACK (cb_enter), + (gpointer) ct); + g_signal_connect (listitem, "leave-notify-event", + G_CALLBACK (cb_exit), + (gpointer) ct); + g_signal_connect (listitem, "toggle", + G_CALLBACK (cb_toggle), + (gpointer) ct); + + gtk_container_add (GTK_CONTAINER (ct->list), + listitem); + + g_hash_table_insert (ct->elements, (gpointer)value_copy, + (gpointer) listitem); + + weak_ref_closure = g_new (WeakRefClosure, 1); + weak_ref_closure->ct = ct; + weak_ref_closure->value = g_strdup (value_copy); + + g_object_weak_ref (G_OBJECT (listitem), + cb_remove_from_hash, + weak_ref_closure); +} + +static void +cb_list_mapped (GtkWidget *widget, gpointer user_data) +{ + GtkList *list = GTK_LIST (widget); + + if (g_list_length (list->selection) > 0) + gtk_widget_grab_focus (GTK_WIDGET ((list->selection->data))); +} + +void +gal_combo_text_construct (GalComboText *ct, gboolean const is_scrolled) +{ + GtkWidget *entry, *list, *scroll, *display_widget; + + ct->case_sensitive = FALSE; + ct->elements = g_hash_table_new (&strcase_hash, + &strcase_equal); + + /* Probably irrelevant, but lets be careful */ + ct->cache_mouse_state = GTK_STATE_NORMAL; + ct->cached_entry = NULL; + + entry = ct->entry = gtk_entry_new (); + list = ct->list = gtk_list_new (); + if (is_scrolled) { + display_widget = scroll = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + + gtk_scrolled_window_add_with_viewport ( + GTK_SCROLLED_WINDOW(scroll), list); + gtk_container_set_focus_hadjustment ( + GTK_CONTAINER (list), + gtk_scrolled_window_get_hadjustment ( + GTK_SCROLLED_WINDOW (scroll))); + gtk_container_set_focus_vadjustment ( + GTK_CONTAINER (list), + gtk_scrolled_window_get_vadjustment ( + GTK_SCROLLED_WINDOW (scroll))); + gtk_widget_set_usize (scroll, 0, 200); /* MAGIC NUMBER */ + } else + display_widget = list; + + g_signal_connect (entry, "activate", + G_CALLBACK (entry_activate_cb), + ct); + g_signal_connect (list, "select-child", + G_CALLBACK (list_select_cb), + ct); + g_signal_connect (list, "unselect-child", + G_CALLBACK (list_unselect_cb), + ct); + g_signal_connect (list, "map", + G_CALLBACK (cb_list_mapped), NULL); + + gtk_widget_show (display_widget); + gtk_widget_show (entry); + gal_combo_box_construct (GAL_COMBO_BOX (ct), entry, display_widget); + g_signal_connect (ct, "pop_down_done", + G_CALLBACK (cb_pop_down), NULL); +} + +GtkWidget* +gal_combo_text_new (gboolean const is_scrolled) +{ + GalComboText *ct; + + ct = g_object_new (GAL_COMBO_TEXT_TYPE, NULL); + gal_combo_text_construct (ct, is_scrolled); + return GTK_WIDGET (ct); +} + -- cgit v1.2.3