/* * moocombo.c * * Copyright (C) 2004-2005 by Yevgen Muntyan * * 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 MOO_MARSHALS_H #ifndef __MOO__ #include "moocombo.h" #include "mooentry.h" #else #include "mooutils/moocombo.h" #include "mooutils/mooentry.h" #endif #include #include #include #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; 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 (!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); gtk_tree_view_set_hover_selection (combo->priv->treeview, TRUE); 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 completions 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)); 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; int selected; 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 (window)); gtk_window_set_modal (GTK_WINDOW (combo->priv->popup), TRUE); resize_popup (combo); selected = popup_get_selected (combo); gtk_widget_show (combo->priv->popup); /* treeview selects something on focus */ if (selected < 0) { GtkTreeSelection *selection = gtk_tree_view_get_selection (combo->priv->treeview); gtk_tree_selection_unselect_all (selection); } 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); } 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; 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 = {combo, 0}; int focus_line_width; 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), "horizontal-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_set_size_request (GTK_WIDGET (combo->priv->treeview), width, separator_height + items * height); 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 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) { 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); gtk_tree_path_free (path); } else { 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) { g_return_val_if_fail (MOO_IS_COMBO (combo), NULL); return combo->priv->get_text_func (combo->priv->model, iter, combo->priv->get_text_data); } 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 (); 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); } 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) { g_signal_emit (combo, signals[CHANGED], 0); } static void entry_changed (MooCombo *combo) { GtkTreeIter iter; 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); }