1474 lines
44 KiB
C
1474 lines
44 KiB
C
/*
|
|
* moocombo.c
|
|
*
|
|
* Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* See COPYING file that comes with this distribution.
|
|
*/
|
|
|
|
#include "mooutils/moomarshals.h"
|
|
#include "mooutils/moocombo.h"
|
|
#include "mooutils/mooentry.h"
|
|
#include <gtk/gtk.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <string.h>
|
|
|
|
|
|
#define MAX_POPUP_LEN 15
|
|
|
|
|
|
struct _MooComboPrivate {
|
|
gboolean use_button;
|
|
GtkWidget *button;
|
|
GtkSizeGroup* size_group;
|
|
|
|
GtkWidget *popup;
|
|
GtkTreeView *treeview;
|
|
GtkTreeViewColumn *column;
|
|
GtkTreeModel *model;
|
|
GtkTreeRowReference *active_row;
|
|
|
|
gboolean walking_list;
|
|
char *real_text;
|
|
|
|
int text_column;
|
|
GtkCellRenderer *text_cell;
|
|
|
|
MooComboGetTextFunc get_text_func;
|
|
gpointer get_text_data;
|
|
|
|
MooComboRowSeparatorFunc row_separator_func;
|
|
gpointer row_separator_data;
|
|
};
|
|
|
|
|
|
static void moo_combo_cell_layout_init (GtkCellLayoutIface *iface);
|
|
static void moo_combo_cell_layout_pack_start (GtkCellLayout *cell_layout,
|
|
GtkCellRenderer *cell,
|
|
gboolean expand);
|
|
static void moo_combo_cell_layout_pack_end (GtkCellLayout *cell_layout,
|
|
GtkCellRenderer *cell,
|
|
gboolean expand);
|
|
static void moo_combo_cell_layout_clear (GtkCellLayout *cell_layout);
|
|
static void moo_combo_cell_layout_add_attribute (GtkCellLayout *cell_layout,
|
|
GtkCellRenderer *cell,
|
|
const gchar *attribute,
|
|
gint column);
|
|
static void moo_combo_cell_layout_set_cell_data_func (GtkCellLayout *cell_layout,
|
|
GtkCellRenderer *cell,
|
|
GtkCellLayoutDataFunc func,
|
|
gpointer func_data,
|
|
GDestroyNotify destroy);
|
|
static void moo_combo_cell_layout_clear_attributes (GtkCellLayout *cell_layout,
|
|
GtkCellRenderer *cell);
|
|
static void moo_combo_cell_layout_reorder (GtkCellLayout *cell_layout,
|
|
GtkCellRenderer *cell,
|
|
gint position);
|
|
|
|
static void moo_combo_class_init (MooComboClass *klass);
|
|
static void moo_combo_init (MooCombo *combo);
|
|
static void moo_combo_finalize (GObject *object);
|
|
static void moo_combo_destroy (GtkObject *object);
|
|
static void moo_combo_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
static void moo_combo_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
static void moo_combo_unmap (GtkWidget *widget);
|
|
static void moo_combo_unrealize (GtkWidget *widget);
|
|
static gboolean moo_combo_focus (GtkWidget *widget,
|
|
GtkDirectionType direction);
|
|
|
|
static void moo_combo_popup_real (MooCombo *combo);
|
|
static void moo_combo_popdown_real (MooCombo *combo);
|
|
static void moo_combo_changed (MooCombo *combo);
|
|
static gboolean moo_combo_popup_key_press (MooCombo *combo,
|
|
GdkEventKey *event);
|
|
|
|
static void create_arrow_button (MooCombo *combo);
|
|
static void button_clicked (MooCombo *combo);
|
|
|
|
static void create_popup (MooCombo *combo);
|
|
static gboolean resize_popup (MooCombo *combo);
|
|
static gboolean entry_focus_out (MooCombo *combo);
|
|
static gboolean popup_button_press (MooCombo *combo,
|
|
GdkEventButton *event);
|
|
static gboolean popup_key_press_cb (MooCombo *combo,
|
|
GdkEventKey *event);
|
|
static int popup_get_selected (MooCombo *combo);
|
|
static gboolean list_button_press (MooCombo *combo,
|
|
GdkEventButton *event);
|
|
static char *default_get_text_func (GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
gpointer data);
|
|
|
|
static void entry_changed (MooCombo *combo);
|
|
|
|
|
|
GType
|
|
moo_combo_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (G_UNLIKELY (!type))
|
|
{
|
|
static const GTypeInfo info =
|
|
{
|
|
sizeof (MooComboClass),
|
|
NULL, /* base_init */
|
|
NULL, /* base_finalize */
|
|
(GClassInitFunc) moo_combo_class_init,
|
|
NULL, /* class_finalize */
|
|
NULL, /* class_data */
|
|
sizeof (MooCombo),
|
|
0,
|
|
(GInstanceInitFunc) moo_combo_init,
|
|
NULL
|
|
};
|
|
|
|
static const GInterfaceInfo cell_layout_info =
|
|
{
|
|
(GInterfaceInitFunc) moo_combo_cell_layout_init,
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
type = g_type_register_static (GTK_TYPE_TABLE, "MooCombo", &info, 0);
|
|
g_type_add_interface_static (type, GTK_TYPE_CELL_LAYOUT, &cell_layout_info);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
|
|
static gpointer moo_combo_parent_class = NULL;
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_ACTIVATES_DEFAULT,
|
|
PROP_USE_BUTTON
|
|
};
|
|
|
|
enum {
|
|
POPUP,
|
|
POPDOWN,
|
|
CHANGED,
|
|
POPUP_KEY_PRESS,
|
|
NUM_SIGNALS
|
|
};
|
|
|
|
static guint signals[NUM_SIGNALS];
|
|
|
|
static void
|
|
moo_combo_class_init (MooComboClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
GtkBindingSet *binding_set;
|
|
|
|
moo_combo_parent_class = g_type_class_peek_parent (klass);
|
|
|
|
gobject_class->finalize = moo_combo_finalize;
|
|
gobject_class->set_property = moo_combo_set_property;
|
|
gobject_class->get_property = moo_combo_get_property;
|
|
|
|
gtkobject_class->destroy = moo_combo_destroy;
|
|
|
|
widget_class->unmap = moo_combo_unmap;
|
|
widget_class->unrealize = moo_combo_unrealize;
|
|
widget_class->focus = moo_combo_focus;
|
|
|
|
klass->popup = moo_combo_popup_real;
|
|
klass->popdown = moo_combo_popdown_real;
|
|
klass->popup_key_press = moo_combo_popup_key_press;
|
|
|
|
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_READWRITE));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_USE_BUTTON,
|
|
g_param_spec_boolean ("use-button",
|
|
"use-button",
|
|
"use-button",
|
|
TRUE,
|
|
G_PARAM_READWRITE));
|
|
|
|
signals[POPUP] =
|
|
g_signal_new ("popup",
|
|
G_OBJECT_CLASS_TYPE (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
G_STRUCT_OFFSET (MooComboClass, popup),
|
|
NULL, NULL,
|
|
_moo_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
signals[POPDOWN] =
|
|
g_signal_new ("popdown",
|
|
G_OBJECT_CLASS_TYPE (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
G_STRUCT_OFFSET (MooComboClass, popdown),
|
|
NULL, NULL,
|
|
_moo_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
signals[CHANGED] =
|
|
g_signal_new ("changed",
|
|
G_OBJECT_CLASS_TYPE (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (MooComboClass, changed),
|
|
NULL, NULL,
|
|
_moo_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
signals[POPUP_KEY_PRESS] =
|
|
g_signal_new ("popup-key-press",
|
|
G_OBJECT_CLASS_TYPE (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (MooComboClass, popup_key_press),
|
|
g_signal_accumulator_true_handled, NULL,
|
|
_moo_marshal_BOOLEAN__BOXED,
|
|
G_TYPE_BOOLEAN, 1,
|
|
GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
|
|
|
|
binding_set = gtk_binding_set_by_class (klass);
|
|
gtk_binding_entry_add_signal (binding_set, GDK_space, GDK_CONTROL_MASK,
|
|
"popup", 0);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_combo_cell_layout_init (GtkCellLayoutIface *iface)
|
|
{
|
|
iface->pack_start = moo_combo_cell_layout_pack_start;
|
|
iface->pack_end = moo_combo_cell_layout_pack_end;
|
|
iface->clear = moo_combo_cell_layout_clear;
|
|
iface->add_attribute = moo_combo_cell_layout_add_attribute;
|
|
iface->set_cell_data_func = moo_combo_cell_layout_set_cell_data_func;
|
|
iface->clear_attributes = moo_combo_cell_layout_clear_attributes;
|
|
iface->reorder = moo_combo_cell_layout_reorder;
|
|
}
|
|
|
|
|
|
static void
|
|
moo_combo_init (MooCombo *combo)
|
|
{
|
|
combo->priv = g_new0 (MooComboPrivate, 1);
|
|
|
|
combo->priv->use_button = TRUE;
|
|
|
|
combo->priv->get_text_func = default_get_text_func;
|
|
combo->priv->get_text_data = combo;
|
|
|
|
gtk_table_resize (GTK_TABLE (combo), 1, 2);
|
|
|
|
combo->priv->size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
|
|
|
|
combo->entry = moo_entry_new ();
|
|
gtk_widget_show (combo->entry);
|
|
gtk_table_attach (GTK_TABLE (combo), combo->entry,
|
|
0, 1, 0, 1,
|
|
GTK_EXPAND | GTK_FILL,
|
|
0, 0, 0);
|
|
gtk_size_group_add_widget (combo->priv->size_group, combo->entry);
|
|
|
|
g_signal_connect_swapped (combo->entry, "changed",
|
|
G_CALLBACK (entry_changed), combo);
|
|
|
|
create_arrow_button (combo);
|
|
create_popup (combo);
|
|
}
|
|
|
|
|
|
static void
|
|
create_arrow_button (MooCombo *combo)
|
|
{
|
|
GtkWidget *arrow, *frame;
|
|
|
|
combo->priv->button = gtk_button_new ();
|
|
gtk_button_set_focus_on_click (GTK_BUTTON (combo->priv->button), FALSE);
|
|
gtk_widget_show (combo->priv->button);
|
|
gtk_size_group_add_widget (combo->priv->size_group, combo->priv->button);
|
|
gtk_table_attach (GTK_TABLE (combo), combo->priv->button,
|
|
1, 2, 0, 1,
|
|
0, 0, 0, 0);
|
|
|
|
g_signal_connect_swapped (combo->priv->button, "clicked",
|
|
G_CALLBACK (button_clicked), combo);
|
|
|
|
frame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1, FALSE);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
|
|
gtk_widget_show (frame);
|
|
gtk_container_add (GTK_CONTAINER (combo->priv->button), frame);
|
|
|
|
arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_ETCHED_IN);
|
|
gtk_widget_set_size_request (arrow, 10, 10);
|
|
gtk_widget_show (arrow);
|
|
gtk_container_add (GTK_CONTAINER (frame), arrow);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_combo_destroy (GtkObject *object)
|
|
{
|
|
MooCombo *combo = MOO_COMBO (object);
|
|
|
|
combo->priv->button = NULL;
|
|
combo->entry = NULL;
|
|
|
|
if (combo->priv->model)
|
|
{
|
|
g_object_unref (combo->priv->model);
|
|
combo->priv->model = NULL;
|
|
}
|
|
|
|
if (combo->priv->popup)
|
|
{
|
|
gtk_widget_destroy (combo->priv->popup);
|
|
combo->priv->popup = NULL;
|
|
combo->priv->treeview = NULL;
|
|
}
|
|
|
|
if (combo->priv->size_group)
|
|
{
|
|
g_object_unref (combo->priv->size_group);
|
|
combo->priv->size_group = NULL;
|
|
}
|
|
|
|
GTK_OBJECT_CLASS(moo_combo_parent_class)->destroy (object);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_combo_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MooCombo *combo = MOO_COMBO (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_ACTIVATES_DEFAULT:
|
|
gtk_entry_set_activates_default (GTK_ENTRY (combo->entry),
|
|
g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_USE_BUTTON:
|
|
moo_combo_set_use_button (combo, g_value_get_boolean (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
moo_combo_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MooCombo *combo = MOO_COMBO (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_ACTIVATES_DEFAULT:
|
|
g_value_set_boolean (value, gtk_entry_get_activates_default (GTK_ENTRY (combo->entry)));
|
|
break;
|
|
|
|
case PROP_USE_BUTTON:
|
|
g_value_set_boolean (value, combo->priv->use_button);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
moo_combo_finalize (GObject *object)
|
|
{
|
|
MooCombo *combo = MOO_COMBO (object);
|
|
gtk_tree_row_reference_free (combo->priv->active_row);
|
|
g_free (combo->priv);
|
|
G_OBJECT_CLASS (moo_combo_parent_class)->finalize (object);
|
|
}
|
|
|
|
|
|
GtkWidget*
|
|
moo_combo_new (void)
|
|
{
|
|
return g_object_new (MOO_TYPE_COMBO, NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
create_popup (MooCombo *combo)
|
|
{
|
|
GtkWidget *scrolled_window, *frame;
|
|
GtkTreeSelection *selection;
|
|
|
|
combo->priv->popup = gtk_window_new (GTK_WINDOW_POPUP);
|
|
gtk_widget_set_size_request (combo->priv->popup, -1, -1);
|
|
gtk_window_set_default_size (GTK_WINDOW (combo->priv->popup), 1, 1);
|
|
gtk_window_set_resizable (GTK_WINDOW (combo->priv->popup), FALSE);
|
|
gtk_widget_add_events (combo->priv->popup, GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK);
|
|
|
|
combo->priv->column = gtk_tree_view_column_new ();
|
|
|
|
combo->priv->treeview = GTK_TREE_VIEW (gtk_tree_view_new ());
|
|
gtk_widget_set_size_request (GTK_WIDGET (combo->priv->treeview), -1, -1);
|
|
gtk_tree_view_append_column (combo->priv->treeview, combo->priv->column);
|
|
gtk_tree_view_set_headers_visible (combo->priv->treeview, FALSE);
|
|
#if 1 && GTK_CHECK_VERSION(2,6,0)
|
|
gtk_tree_view_set_hover_selection (combo->priv->treeview, TRUE);
|
|
#endif
|
|
|
|
selection = gtk_tree_view_get_selection (combo->priv->treeview);
|
|
gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
|
|
|
|
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
|
|
gtk_widget_set_size_request (scrolled_window, -1, -1);
|
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
|
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
|
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
|
|
GTK_SHADOW_ETCHED_IN);
|
|
/* a nasty hack to get the treeview to size nicely */
|
|
gtk_widget_set_size_request (GTK_SCROLLED_WINDOW (scrolled_window)->vscrollbar, -1, 0);
|
|
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_NONE);
|
|
gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (combo->priv->treeview));
|
|
|
|
frame = gtk_frame_new (NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
|
|
gtk_container_add (GTK_CONTAINER (frame), scrolled_window);
|
|
|
|
gtk_widget_show_all (frame);
|
|
gtk_container_add (GTK_CONTAINER (combo->priv->popup), frame);
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_popup (MooCombo *combo)
|
|
{
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
g_signal_emit (combo, signals[POPUP], 0);
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_popdown (MooCombo *combo)
|
|
{
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
|
|
if (combo->priv->walking_list)
|
|
{
|
|
combo->priv->walking_list = FALSE;
|
|
g_free (combo->priv->real_text);
|
|
combo->priv->real_text = NULL;
|
|
}
|
|
|
|
g_signal_emit (combo, signals[POPDOWN], 0);
|
|
}
|
|
|
|
|
|
/* XXX _gtk_entry_get_borders from gtkentry.c */
|
|
static void
|
|
entry_get_borders (GtkEntry *entry,
|
|
gint *xborder,
|
|
gint *yborder)
|
|
{
|
|
GtkWidget *widget = GTK_WIDGET (entry);
|
|
gint focus_width;
|
|
gboolean interior_focus;
|
|
|
|
gtk_widget_style_get (widget,
|
|
"interior-focus", &interior_focus,
|
|
"focus-line-width", &focus_width,
|
|
NULL);
|
|
|
|
if (entry->has_frame)
|
|
{
|
|
*xborder = widget->style->xthickness;
|
|
*yborder = widget->style->ythickness;
|
|
}
|
|
else
|
|
{
|
|
*xborder = 0;
|
|
*yborder = 0;
|
|
}
|
|
|
|
if (!interior_focus)
|
|
{
|
|
*xborder += focus_width;
|
|
*yborder += focus_width;
|
|
}
|
|
}
|
|
|
|
|
|
static gboolean
|
|
model_is_empty (GtkTreeModel *model)
|
|
{
|
|
GtkTreeIter iter;
|
|
return !gtk_tree_model_get_iter_first (model, &iter);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_combo_popup_real (MooCombo *combo)
|
|
{
|
|
GtkWidget *window;
|
|
GtkTreeSelection *selection;
|
|
|
|
if (moo_combo_popup_shown (combo))
|
|
return;
|
|
|
|
if (model_is_empty (combo->priv->model))
|
|
return;
|
|
|
|
window = gtk_widget_get_toplevel (GTK_WIDGET (combo->entry));
|
|
g_return_if_fail (GTK_IS_WINDOW (window));
|
|
|
|
gtk_widget_realize (combo->priv->popup);
|
|
|
|
if (GTK_WINDOW (window)->group)
|
|
gtk_window_group_add_window (GTK_WINDOW (window)->group,
|
|
GTK_WINDOW (combo->priv->popup));
|
|
gtk_window_set_modal (GTK_WINDOW (combo->priv->popup), TRUE);
|
|
|
|
resize_popup (combo);
|
|
|
|
gtk_widget_show (combo->priv->popup);
|
|
|
|
gtk_widget_ensure_style (GTK_WIDGET (combo->priv->treeview));
|
|
gtk_widget_modify_bg (GTK_WIDGET (combo->priv->treeview), GTK_STATE_ACTIVE,
|
|
>K_WIDGET(combo->priv->treeview)->style->base[GTK_STATE_SELECTED]);
|
|
gtk_widget_modify_base (GTK_WIDGET (combo->priv->treeview), GTK_STATE_ACTIVE,
|
|
>K_WIDGET(combo->priv->treeview)->style->base[GTK_STATE_SELECTED]);
|
|
|
|
gtk_grab_add (combo->priv->popup);
|
|
gdk_pointer_grab (combo->priv->popup->window, TRUE,
|
|
GDK_BUTTON_PRESS_MASK |
|
|
GDK_BUTTON_RELEASE_MASK |
|
|
GDK_POINTER_MOTION_MASK,
|
|
NULL, NULL, GDK_CURRENT_TIME);
|
|
|
|
g_signal_connect_swapped (combo->entry, "focus-out-event",
|
|
G_CALLBACK (entry_focus_out), combo);
|
|
|
|
g_signal_connect_swapped (combo->priv->popup, "button-press-event",
|
|
G_CALLBACK (popup_button_press), combo);
|
|
g_signal_connect_swapped (combo->priv->popup, "key-press-event",
|
|
G_CALLBACK (popup_key_press_cb), combo);
|
|
|
|
g_signal_connect_swapped (combo->priv->treeview, "button-press-event",
|
|
G_CALLBACK (list_button_press), combo);
|
|
|
|
/* treeview selects something on focus */
|
|
selection = gtk_tree_view_get_selection (combo->priv->treeview);
|
|
gtk_tree_selection_unselect_all (selection);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_combo_popdown_real (MooCombo *combo)
|
|
{
|
|
if (!moo_combo_popup_shown (combo))
|
|
return;
|
|
|
|
g_signal_handlers_disconnect_by_func (combo->entry,
|
|
(gpointer) entry_focus_out,
|
|
combo);
|
|
|
|
g_signal_handlers_disconnect_by_func (combo->priv->popup,
|
|
(gpointer) popup_button_press,
|
|
combo);
|
|
g_signal_handlers_disconnect_by_func (combo->priv->popup,
|
|
(gpointer) popup_key_press_cb,
|
|
combo);
|
|
|
|
g_signal_handlers_disconnect_by_func (combo->priv->treeview,
|
|
(gpointer) list_button_press,
|
|
combo);
|
|
|
|
gdk_pointer_ungrab (GDK_CURRENT_TIME);
|
|
gtk_grab_remove (combo->priv->popup);
|
|
gtk_widget_hide (combo->priv->popup);
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_update_popup (MooCombo *combo)
|
|
{
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
|
|
if (moo_combo_popup_shown (combo))
|
|
resize_popup (combo);
|
|
}
|
|
|
|
|
|
static gboolean
|
|
count_separators (GtkTreeModel *model,
|
|
G_GNUC_UNUSED GtkTreePath *path,
|
|
GtkTreeIter *iter,
|
|
gpointer user_data)
|
|
{
|
|
struct {
|
|
MooCombo *combo;
|
|
int count;
|
|
} *data = user_data;
|
|
|
|
if (data->combo->priv->row_separator_func (model, iter, data->combo->priv->row_separator_data))
|
|
data->count++;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* XXX gtk_entry_resize_popup from gtkentrycompletion.c */
|
|
static gboolean
|
|
resize_popup (MooCombo *combo)
|
|
{
|
|
GtkWidget *widget = GTK_WIDGET (combo->entry);
|
|
int x, y;
|
|
int matches, items, height, x_border, y_border;
|
|
GdkScreen *screen;
|
|
int monitor_num;
|
|
GdkRectangle monitor;
|
|
GtkRequisition popup_req;
|
|
GtkRequisition combo_req;
|
|
gboolean above;
|
|
int width;
|
|
int separator_height = 0, vert_separator = 0;
|
|
int selected;
|
|
|
|
g_return_val_if_fail (GTK_WIDGET_REALIZED (combo->entry), FALSE);
|
|
|
|
gdk_window_get_origin (widget->window, &x, &y);
|
|
/* XXX */
|
|
entry_get_borders (GTK_ENTRY (combo->entry), &x_border, &y_border);
|
|
|
|
matches = gtk_tree_model_iter_n_children (combo->priv->model, NULL);
|
|
|
|
/* XXX */
|
|
if (combo->priv->row_separator_func)
|
|
{
|
|
struct {
|
|
MooCombo *combo;
|
|
int count;
|
|
} data;
|
|
|
|
int focus_line_width;
|
|
|
|
data.combo = combo;
|
|
data.count = 0;
|
|
gtk_tree_model_foreach (combo->priv->model,
|
|
(GtkTreeModelForeachFunc) count_separators,
|
|
&data);
|
|
|
|
if (data.count)
|
|
{
|
|
matches -= data.count;
|
|
gtk_widget_style_get (GTK_WIDGET (combo->priv->treeview),
|
|
"vertical-separator", &separator_height,
|
|
"focus-line-width", &focus_line_width, NULL);
|
|
separator_height *= data.count;
|
|
separator_height += 2 * data.count * focus_line_width;
|
|
}
|
|
}
|
|
|
|
items = MIN (matches, MAX_POPUP_LEN);
|
|
|
|
gtk_tree_view_column_cell_get_size (combo->priv->column, NULL,
|
|
NULL, NULL, NULL, &height);
|
|
|
|
screen = gtk_widget_get_screen (widget);
|
|
monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window);
|
|
gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
|
|
|
|
width = MIN (GTK_WIDGET(combo)->allocation.width, monitor.width) - 2 * x_border;
|
|
gtk_widget_style_get (GTK_WIDGET (combo->priv->treeview), "vertical-separator",
|
|
&vert_separator, NULL);
|
|
gtk_widget_set_size_request (GTK_WIDGET (combo->priv->treeview), width,
|
|
separator_height + items * (height + vert_separator));
|
|
|
|
gtk_widget_set_size_request (combo->priv->popup, -1, -1);
|
|
gtk_widget_size_request (combo->priv->popup, &popup_req);
|
|
gtk_widget_size_request (widget, &combo_req);
|
|
|
|
if (x < monitor.x)
|
|
x = monitor.x;
|
|
else if (x + popup_req.width > monitor.x + monitor.width)
|
|
x = monitor.x + monitor.width - popup_req.width;
|
|
|
|
if (y + combo_req.height + popup_req.height <= monitor.y + monitor.height)
|
|
{
|
|
y += combo_req.height;
|
|
above = FALSE;
|
|
}
|
|
else
|
|
{
|
|
y -= popup_req.height;
|
|
above = TRUE;
|
|
}
|
|
|
|
gtk_window_move (GTK_WINDOW (combo->priv->popup), x, y);
|
|
|
|
selected = popup_get_selected (combo);
|
|
|
|
if (selected >= 0)
|
|
{
|
|
GtkTreePath *path = gtk_tree_path_new_from_indices (selected, -1);
|
|
gtk_tree_view_scroll_to_cell (combo->priv->treeview, path, NULL, FALSE, 0, 0);
|
|
gtk_tree_path_free (path);
|
|
}
|
|
|
|
return above;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
entry_focus_out (MooCombo *combo)
|
|
{
|
|
moo_combo_popdown (combo);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
popup_button_press (MooCombo *combo,
|
|
GdkEventButton *event)
|
|
{
|
|
if (event->window == combo->priv->popup->window)
|
|
{
|
|
gint width, height;
|
|
gdk_drawable_get_size (GDK_DRAWABLE (event->window),
|
|
&width, &height);
|
|
if (event->x < 0 || event->x >= width ||
|
|
event->y < 0 || event->y >= height)
|
|
{
|
|
moo_combo_popdown (combo);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
moo_combo_popdown (combo);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static int
|
|
popup_get_selected (MooCombo *combo)
|
|
{
|
|
int ind;
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path;
|
|
GtkTreeView *treeview = combo->priv->treeview;
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
|
|
|
|
if (gtk_tree_selection_get_selected (selection, NULL, &iter))
|
|
{
|
|
path = gtk_tree_model_get_path (combo->priv->model, &iter);
|
|
g_return_val_if_fail (path != NULL, -1);
|
|
ind = gtk_tree_path_get_indices (path)[0];
|
|
gtk_tree_path_free (path);
|
|
return ind;
|
|
}
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
combo_set_text_silent (MooCombo *combo,
|
|
const char *text)
|
|
{
|
|
g_return_if_fail (text != NULL);
|
|
|
|
g_signal_handlers_block_by_func (combo->entry, (gpointer) entry_changed, combo);
|
|
|
|
moo_entry_begin_undo_group (MOO_ENTRY (combo->entry));
|
|
gtk_entry_set_text (GTK_ENTRY (combo->entry), text);
|
|
gtk_editable_set_position (GTK_EDITABLE (combo->entry), -1);
|
|
|
|
g_signal_handlers_unblock_by_func (combo->entry, (gpointer) entry_changed, combo);
|
|
}
|
|
|
|
|
|
static void
|
|
popup_move_selection (MooCombo *combo,
|
|
GdkEventKey *event)
|
|
{
|
|
int n_items, current_item, new_item = -1;
|
|
GtkTreePath *path;
|
|
|
|
n_items = gtk_tree_model_iter_n_children (combo->priv->model, NULL);
|
|
g_return_if_fail (n_items != 0);
|
|
|
|
current_item = popup_get_selected (combo);
|
|
|
|
switch (event->keyval)
|
|
{
|
|
case GDK_Down:
|
|
case GDK_KP_Down:
|
|
if (current_item < n_items - 1)
|
|
new_item = current_item + 1;
|
|
else
|
|
new_item = -1;
|
|
break;
|
|
|
|
case GDK_Up:
|
|
case GDK_KP_Up:
|
|
if (current_item < 0)
|
|
new_item = n_items - 1;
|
|
else
|
|
new_item = current_item - 1;
|
|
break;
|
|
|
|
case GDK_Page_Down:
|
|
case GDK_KP_Page_Down:
|
|
new_item = current_item + MAX_POPUP_LEN - 1;
|
|
if (new_item >= n_items)
|
|
new_item = n_items - 1;
|
|
break;
|
|
|
|
case GDK_Page_Up:
|
|
case GDK_KP_Page_Up:
|
|
new_item = current_item - MAX_POPUP_LEN + 1;
|
|
if (new_item < 0)
|
|
new_item = 0;
|
|
break;
|
|
|
|
case GDK_Tab:
|
|
case GDK_KP_Tab:
|
|
if (current_item < n_items - 1)
|
|
new_item = current_item + 1;
|
|
else
|
|
new_item = 0;
|
|
break;
|
|
|
|
case GDK_ISO_Left_Tab:
|
|
if (current_item <= 0)
|
|
new_item = n_items - 1;
|
|
else
|
|
new_item = current_item - 1;
|
|
break;
|
|
|
|
default:
|
|
g_return_if_reached ();
|
|
}
|
|
|
|
if (new_item >= 0 && new_item < n_items)
|
|
{
|
|
GtkTreeIter iter;
|
|
char *new_text;
|
|
|
|
path = gtk_tree_path_new_from_indices (new_item, -1);
|
|
gtk_tree_view_set_cursor (combo->priv->treeview, path, NULL, FALSE);
|
|
gtk_tree_view_scroll_to_cell (combo->priv->treeview, path, NULL, FALSE, 0, 0);
|
|
|
|
if (!combo->priv->walking_list)
|
|
{
|
|
combo->priv->walking_list = TRUE;
|
|
combo->priv->real_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (combo->entry)));
|
|
}
|
|
|
|
gtk_tree_model_get_iter (combo->priv->model, &iter, path);
|
|
new_text = moo_combo_get_text_at_iter (combo, &iter);
|
|
combo_set_text_silent (combo, new_text);
|
|
|
|
gtk_tree_path_free (path);
|
|
g_free (new_text);
|
|
}
|
|
else
|
|
{
|
|
if (combo->priv->walking_list)
|
|
{
|
|
combo->priv->walking_list = FALSE;
|
|
combo_set_text_silent (combo, combo->priv->real_text);
|
|
g_free (combo->priv->real_text);
|
|
combo->priv->real_text = NULL;
|
|
}
|
|
|
|
gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (combo->priv->treeview));
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
popup_select_iter (MooCombo *combo,
|
|
GtkTreeIter *iter)
|
|
{
|
|
char *text;
|
|
|
|
text = moo_combo_get_text_at_iter (combo, iter);
|
|
g_return_if_fail (text != NULL);
|
|
|
|
g_signal_handlers_block_by_func (combo->entry,
|
|
(gpointer) entry_changed, combo);
|
|
moo_entry_begin_undo_group (MOO_ENTRY (combo->entry));
|
|
|
|
moo_combo_popdown (combo);
|
|
gtk_entry_set_text (GTK_ENTRY (combo->entry), text);
|
|
gtk_editable_set_position (GTK_EDITABLE (combo->entry), -1);
|
|
|
|
moo_entry_end_undo_group (MOO_ENTRY (combo->entry));
|
|
g_signal_handlers_unblock_by_func (combo->entry,
|
|
(gpointer) entry_changed, combo);
|
|
|
|
moo_combo_set_active_iter (combo, iter);
|
|
|
|
g_free (text);
|
|
}
|
|
|
|
|
|
char*
|
|
moo_combo_get_text_at_iter (MooCombo *combo,
|
|
GtkTreeIter *iter)
|
|
{
|
|
char *text;
|
|
|
|
g_return_val_if_fail (MOO_IS_COMBO (combo), NULL);
|
|
|
|
text = combo->priv->get_text_func (combo->priv->model, iter,
|
|
combo->priv->get_text_data);
|
|
g_return_val_if_fail (text != NULL, g_strdup (""));
|
|
return text;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
popup_return_key (MooCombo *combo)
|
|
{
|
|
GtkTreeIter iter;
|
|
GtkTreeView *treeview = combo->priv->treeview;
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
|
|
|
|
if (gtk_tree_selection_get_selected (selection, NULL, &iter))
|
|
popup_select_iter (combo, &iter);
|
|
else
|
|
moo_combo_popdown (combo);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
moo_combo_popup_key_press (MooCombo *combo,
|
|
GdkEventKey *event)
|
|
{
|
|
switch (event->keyval)
|
|
{
|
|
case GDK_Down:
|
|
case GDK_Up:
|
|
case GDK_KP_Down:
|
|
case GDK_KP_Up:
|
|
case GDK_Page_Down:
|
|
case GDK_Page_Up:
|
|
case GDK_Tab:
|
|
case GDK_KP_Tab:
|
|
case GDK_ISO_Left_Tab:
|
|
popup_move_selection (combo, event);
|
|
return TRUE;
|
|
|
|
case GDK_Escape:
|
|
moo_combo_popdown (combo);
|
|
return TRUE;
|
|
|
|
case GDK_Return:
|
|
case GDK_ISO_Enter:
|
|
case GDK_KP_Enter:
|
|
return popup_return_key (combo);
|
|
|
|
default:
|
|
return gtk_widget_event (combo->entry, (GdkEvent*) event) ||
|
|
gtk_widget_event (GTK_WIDGET (combo), (GdkEvent*) event);
|
|
}
|
|
}
|
|
|
|
|
|
static gboolean
|
|
popup_key_press_cb (MooCombo *combo,
|
|
GdkEventKey *event)
|
|
{
|
|
gboolean retval;
|
|
g_signal_emit (combo, signals[POPUP_KEY_PRESS], 0, event, &retval);
|
|
return retval;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
list_button_press (MooCombo *combo,
|
|
GdkEventButton *event)
|
|
{
|
|
GtkTreePath *path;
|
|
GtkTreeIter iter;
|
|
|
|
if (gtk_tree_view_get_path_at_pos (combo->priv->treeview,
|
|
event->x, event->y,
|
|
&path, NULL, NULL, NULL))
|
|
{
|
|
gtk_tree_model_get_iter (combo->priv->model, &iter, path);
|
|
|
|
if (!combo->priv->row_separator_func ||
|
|
!combo->priv->row_separator_func (combo->priv->model, &iter,
|
|
combo->priv->row_separator_data))
|
|
{
|
|
popup_select_iter (combo, &iter);
|
|
moo_combo_popdown (combo);
|
|
gtk_tree_path_free (path);
|
|
return TRUE;
|
|
}
|
|
|
|
gtk_tree_path_free (path);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
GtkTreeModel*
|
|
moo_combo_get_model (MooCombo *combo)
|
|
{
|
|
g_return_val_if_fail (MOO_IS_COMBO (combo), NULL);
|
|
return combo->priv->model;
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_set_model (MooCombo *combo,
|
|
GtkTreeModel *model)
|
|
{
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
g_return_if_fail (!model || GTK_IS_TREE_MODEL (model));
|
|
|
|
if (model == combo->priv->model)
|
|
return;
|
|
|
|
if (combo->priv->model)
|
|
{
|
|
g_object_unref (combo->priv->model);
|
|
gtk_tree_view_set_model (combo->priv->treeview, NULL);
|
|
combo->priv->model = NULL;
|
|
}
|
|
|
|
if (model)
|
|
{
|
|
combo->priv->model = model;
|
|
g_object_ref (combo->priv->model);
|
|
gtk_tree_view_set_model (combo->priv->treeview, combo->priv->model);
|
|
}
|
|
|
|
moo_combo_update_popup (combo);
|
|
|
|
if (combo->priv->active_row)
|
|
{
|
|
gtk_tree_row_reference_free (combo->priv->active_row);
|
|
combo->priv->active_row = NULL;
|
|
moo_combo_changed (combo);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_set_text_column (MooCombo *combo,
|
|
int column)
|
|
{
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
g_return_if_fail (column >= 0);
|
|
|
|
combo->priv->text_column = column;
|
|
|
|
if (combo->priv->text_cell)
|
|
{
|
|
gtk_tree_view_column_clear_attributes (combo->priv->column, combo->priv->text_cell);
|
|
}
|
|
else
|
|
{
|
|
combo->priv->text_cell = gtk_cell_renderer_text_new ();
|
|
g_object_set (combo->priv->text_cell, "single-paragraph-mode", TRUE, NULL);
|
|
gtk_tree_view_column_pack_start (combo->priv->column, combo->priv->text_cell, TRUE);
|
|
}
|
|
|
|
gtk_tree_view_column_set_attributes (combo->priv->column, combo->priv->text_cell,
|
|
"text", combo->priv->text_column, NULL);
|
|
}
|
|
|
|
|
|
int
|
|
moo_combo_get_text_column (MooCombo *combo)
|
|
{
|
|
g_return_val_if_fail (MOO_IS_COMBO (combo), -1);
|
|
return combo->priv->text_column;
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_set_get_text_func (MooCombo *combo,
|
|
MooComboGetTextFunc func,
|
|
gpointer data)
|
|
{
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
|
|
if (func)
|
|
{
|
|
combo->priv->get_text_func = func;
|
|
combo->priv->get_text_data = data;
|
|
}
|
|
else
|
|
{
|
|
combo->priv->get_text_func = default_get_text_func;
|
|
combo->priv->get_text_data = combo;
|
|
}
|
|
}
|
|
|
|
|
|
static char*
|
|
default_get_text_func (GtkTreeModel *model,
|
|
GtkTreeIter *iter,
|
|
gpointer data)
|
|
{
|
|
char *text = NULL;
|
|
MooCombo *combo = data;
|
|
gtk_tree_model_get (model, iter, combo->priv->text_column, &text, -1);
|
|
return text;
|
|
}
|
|
|
|
|
|
GtkWidget*
|
|
moo_combo_new_text (void)
|
|
{
|
|
GtkWidget *combo;
|
|
GtkListStore *store;
|
|
|
|
store = gtk_list_store_new (1, G_TYPE_STRING);
|
|
combo = moo_combo_new ();
|
|
moo_combo_set_model (MOO_COMBO (combo), GTK_TREE_MODEL (store));
|
|
moo_combo_set_text_column (MOO_COMBO (combo), 0);
|
|
|
|
g_object_unref (store);
|
|
return combo;
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_add_text (MooCombo *combo,
|
|
const char *text)
|
|
{
|
|
GtkTreeIter iter;
|
|
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
g_return_if_fail (text != NULL);
|
|
g_return_if_fail (GTK_IS_LIST_STORE (combo->priv->model));
|
|
|
|
gtk_list_store_append (GTK_LIST_STORE (combo->priv->model), &iter);
|
|
gtk_list_store_set (GTK_LIST_STORE (combo->priv->model), &iter,
|
|
combo->priv->text_column, text, -1);
|
|
}
|
|
|
|
|
|
static void
|
|
button_clicked (MooCombo *combo)
|
|
{
|
|
if (moo_combo_popup_shown (combo))
|
|
moo_combo_popdown (combo);
|
|
else
|
|
moo_combo_popup (combo);
|
|
}
|
|
|
|
|
|
gboolean
|
|
moo_combo_popup_shown (MooCombo *combo)
|
|
{
|
|
g_return_val_if_fail (MOO_IS_COMBO (combo), FALSE);
|
|
return combo->priv->popup && GTK_WIDGET_MAPPED (combo->priv->popup);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_combo_unmap (GtkWidget *widget)
|
|
{
|
|
moo_combo_popdown (MOO_COMBO (widget));
|
|
GTK_WIDGET_CLASS(moo_combo_parent_class)->unmap (widget);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_combo_unrealize (GtkWidget *widget)
|
|
{
|
|
moo_combo_popdown (MOO_COMBO (widget));
|
|
gtk_widget_unrealize (MOO_COMBO (widget)->priv->popup);
|
|
GTK_WIDGET_CLASS(moo_combo_parent_class)->unrealize (widget);
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_entry_set_text (MooCombo *combo,
|
|
const char *text)
|
|
{
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
g_return_if_fail (text != NULL);
|
|
gtk_entry_set_text (GTK_ENTRY (combo->entry), text);
|
|
}
|
|
|
|
|
|
const char*
|
|
moo_combo_entry_get_text (MooCombo *combo)
|
|
{
|
|
g_return_val_if_fail (MOO_IS_COMBO (combo), NULL);
|
|
return gtk_entry_get_text (GTK_ENTRY (combo->entry));
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_select_region (MooCombo *combo,
|
|
int start,
|
|
int end)
|
|
{
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
gtk_editable_select_region (GTK_EDITABLE (combo->entry), start, end);
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_entry_set_activates_default (MooCombo *combo,
|
|
gboolean setting)
|
|
{
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
g_object_set (combo, "activates-default", setting, NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_combo_cell_layout_pack_start (GtkCellLayout *cell_layout,
|
|
GtkCellRenderer *cell,
|
|
gboolean expand)
|
|
{
|
|
gtk_tree_view_column_pack_start (MOO_COMBO (cell_layout)->priv->column,
|
|
cell, expand);
|
|
}
|
|
|
|
static void
|
|
moo_combo_cell_layout_pack_end (GtkCellLayout *cell_layout,
|
|
GtkCellRenderer *cell,
|
|
gboolean expand)
|
|
{
|
|
gtk_tree_view_column_pack_end (MOO_COMBO (cell_layout)->priv->column,
|
|
cell, expand);
|
|
}
|
|
|
|
static void
|
|
moo_combo_cell_layout_clear (GtkCellLayout *cell_layout)
|
|
{
|
|
gtk_tree_view_column_clear (MOO_COMBO (cell_layout)->priv->column);
|
|
}
|
|
|
|
static void
|
|
moo_combo_cell_layout_add_attribute (GtkCellLayout *cell_layout,
|
|
GtkCellRenderer *cell,
|
|
const gchar *attribute,
|
|
gint column)
|
|
{
|
|
gtk_tree_view_column_add_attribute (MOO_COMBO (cell_layout)->priv->column,
|
|
cell, attribute, column);
|
|
}
|
|
|
|
static void
|
|
moo_combo_cell_layout_set_cell_data_func (GtkCellLayout *cell_layout,
|
|
GtkCellRenderer *cell,
|
|
GtkCellLayoutDataFunc func,
|
|
gpointer func_data,
|
|
GDestroyNotify destroy)
|
|
{
|
|
gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (MOO_COMBO (cell_layout)->priv->column),
|
|
cell, func, func_data, destroy);
|
|
}
|
|
|
|
static void
|
|
moo_combo_cell_layout_clear_attributes (GtkCellLayout *cell_layout,
|
|
GtkCellRenderer *cell)
|
|
{
|
|
gtk_tree_view_column_clear_attributes (MOO_COMBO (cell_layout)->priv->column, cell);
|
|
}
|
|
|
|
static void
|
|
moo_combo_cell_layout_reorder (GtkCellLayout *cell_layout,
|
|
GtkCellRenderer *cell,
|
|
gint position)
|
|
{
|
|
gtk_cell_layout_reorder (GTK_CELL_LAYOUT (MOO_COMBO (cell_layout)->priv->column),
|
|
cell, position);
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_set_use_button (MooCombo *combo,
|
|
gboolean use)
|
|
{
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
|
|
use = use ? TRUE : FALSE;
|
|
if (use == combo->priv->use_button)
|
|
return;
|
|
|
|
if (use)
|
|
{
|
|
create_arrow_button (combo);
|
|
}
|
|
else
|
|
{
|
|
g_signal_handlers_disconnect_by_func (combo->priv->button,
|
|
(gpointer) button_clicked,
|
|
combo);
|
|
gtk_container_remove (GTK_CONTAINER (combo), combo->priv->button);
|
|
combo->priv->button = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_set_active_iter (MooCombo *combo,
|
|
GtkTreeIter *iter)
|
|
{
|
|
GtkTreePath *path;
|
|
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
g_return_if_fail (combo->priv->model != NULL && iter != NULL);
|
|
|
|
if (combo->priv->active_row)
|
|
gtk_tree_row_reference_free (combo->priv->active_row);
|
|
|
|
path = gtk_tree_model_get_path (combo->priv->model, iter);
|
|
combo->priv->active_row = gtk_tree_row_reference_new (combo->priv->model, path);
|
|
|
|
if (!combo->priv->active_row || !gtk_tree_row_reference_valid (combo->priv->active_row))
|
|
{
|
|
gtk_tree_row_reference_free (combo->priv->active_row);
|
|
combo->priv->active_row = NULL;
|
|
g_return_if_reached ();
|
|
}
|
|
|
|
moo_combo_changed (combo);
|
|
|
|
gtk_tree_path_free (path);
|
|
}
|
|
|
|
|
|
gboolean
|
|
moo_combo_get_active_iter (MooCombo *combo,
|
|
GtkTreeIter *iter)
|
|
{
|
|
g_return_val_if_fail (MOO_IS_COMBO (combo), FALSE);
|
|
|
|
if (!combo->priv->active_row)
|
|
return FALSE;
|
|
|
|
if (!gtk_tree_row_reference_valid (combo->priv->active_row))
|
|
{
|
|
gtk_tree_row_reference_free (combo->priv->active_row);
|
|
combo->priv->active_row = NULL;
|
|
return FALSE;
|
|
}
|
|
|
|
if (iter)
|
|
{
|
|
GtkTreePath *path;
|
|
|
|
path = gtk_tree_row_reference_get_path (combo->priv->active_row);
|
|
g_return_val_if_fail (path != NULL, FALSE);
|
|
|
|
gtk_tree_model_get_iter (combo->priv->model, iter, path);
|
|
gtk_tree_path_free (path);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static void
|
|
moo_combo_changed (MooCombo *combo)
|
|
{
|
|
if (!combo->priv->walking_list)
|
|
gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (combo->priv->treeview));
|
|
|
|
g_signal_emit (combo, signals[CHANGED], 0);
|
|
}
|
|
|
|
|
|
static void
|
|
entry_changed (MooCombo *combo)
|
|
{
|
|
GtkTreeIter iter;
|
|
|
|
if (combo->priv->walking_list)
|
|
{
|
|
combo->priv->walking_list = FALSE;
|
|
g_free (combo->priv->real_text);
|
|
combo->priv->real_text = NULL;
|
|
}
|
|
|
|
if (moo_combo_get_active_iter (combo, &iter))
|
|
{
|
|
char *text = moo_combo_get_text_at_iter (combo, &iter);
|
|
const char *entry_text = gtk_entry_get_text (GTK_ENTRY (combo->entry));
|
|
|
|
g_return_if_fail (text != NULL);
|
|
|
|
if (strcmp (text, entry_text))
|
|
{
|
|
gtk_tree_row_reference_free (combo->priv->active_row);
|
|
combo->priv->active_row = NULL;
|
|
moo_combo_changed (combo);
|
|
}
|
|
|
|
g_free (text);
|
|
}
|
|
else
|
|
{
|
|
moo_combo_changed (combo);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
moo_combo_set_row_separator_func (MooCombo *combo,
|
|
MooComboRowSeparatorFunc func,
|
|
gpointer data)
|
|
{
|
|
g_return_if_fail (MOO_IS_COMBO (combo));
|
|
gtk_tree_view_set_row_separator_func (combo->priv->treeview, func, data, NULL);
|
|
combo->priv->row_separator_func = func;
|
|
combo->priv->row_separator_data = data;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
moo_combo_focus (GtkWidget *widget,
|
|
GtkDirectionType direction)
|
|
{
|
|
MooCombo *combo = MOO_COMBO (widget);
|
|
return gtk_widget_child_focus (combo->entry, direction);
|
|
}
|