2003-11-17 Mike Kestner <mkestner@ximian.com> * 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 <mkestner@ximian.com> * e-table-config.c : use new gal_combo_* namespace. svn path=/trunk/; revision=23402
434 lines
11 KiB
C
434 lines
11 KiB
C
/* -*- 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 <config.h>
|
|
#include <ctype.h>
|
|
#include <gal/util/e-util.h>
|
|
#include <gtk/gtksignal.h>
|
|
#include <gtk/gtkentry.h>
|
|
#include <gtk/gtklist.h>
|
|
#include <gtk/gtkscrolledwindow.h>
|
|
#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);
|
|
}
|
|
|