/* Screem: screem-combo_box.c * * Copyright (C) 2004 David A Knight * * 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. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * * For contact information with the author of this source code please see * the AUTHORS file. If there is no AUTHORS file present then check the * about box under the help menu for a contact address */ #include #include #include "screem-combo-box.h" #include "support.h" #include "screemmarshal.h" enum { PROP_0, PROP_MODEL, PROP_ACTIVE }; enum { CHANGED, LAST_SIGNAL }; static guint screem_combo_box_signals[ LAST_SIGNAL ] = { 0 }; static void screem_combo_box_activate( GtkWidget *widget, ScreemComboBox *box ); static void screem_combo_box_set_model_real( ScreemComboBox *box, GtkTreeModel *model ); static void screem_combo_box_build_menu( ScreemComboBox *box ); static void screem_combo_box_item_inserted( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *it, ScreemComboBox *box ); static void screem_combo_box_item_deleted( GtkTreeModel *model, GtkTreePath *path, ScreemComboBox *box ); static void screem_combo_box_item_changed( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *it, ScreemComboBox *box ); static void screem_combo_box_set_active_real( ScreemComboBox *box, gint active ); static void screem_combo_box_activate_item( GtkWidget *item, ScreemComboBox *box ); struct ScreemComboBoxPrivate { GtkTreeModel *model; gint active; GtkWidget *button; GtkWidget *menu; GtkWidget *label; }; /* G Object stuff */ G_DEFINE_TYPE( ScreemComboBox, screem_combo_box, GTK_TYPE_BIN ) static void screem_combo_box_finalize( GObject *combo_box ); static void screem_combo_box_set_prop( GObject *object, guint property_id, const GValue *value, GParamSpec *pspec ); static void screem_combo_box_get_prop( GObject *object, guint property_id, GValue *value, GParamSpec *pspec ); static void screem_combo_box_size_request( GtkWidget *widget, GtkRequisition *requisition ); static void screem_combo_box_size_allocate( GtkWidget *widget, GtkAllocation *allocation ); static void screem_combo_box_class_init( ScreemComboBoxClass *klass ) { GObjectClass *object_class; GtkWidgetClass *widget_class; GParamSpec *pspec; object_class = G_OBJECT_CLASS( klass ); widget_class = (GtkWidgetClass *)klass; object_class->finalize = screem_combo_box_finalize; object_class->get_property = screem_combo_box_get_prop; object_class->set_property = screem_combo_box_set_prop; widget_class->size_request = screem_combo_box_size_request; widget_class->size_allocate = screem_combo_box_size_allocate; pspec = g_param_spec_object( "model", "model", "model", GTK_TYPE_TREE_MODEL, G_PARAM_READABLE | G_PARAM_WRITABLE ); g_object_class_install_property( G_OBJECT_CLASS( object_class ), PROP_MODEL, pspec ); pspec = g_param_spec_int( "active", "active", "active", -1, 100, -1, G_PARAM_READABLE | G_PARAM_WRITABLE ); g_object_class_install_property( G_OBJECT_CLASS( object_class ), PROP_ACTIVE, pspec ); screem_combo_box_signals[ CHANGED ] = g_signal_new( "changed", G_OBJECT_CLASS_TYPE( object_class ), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET( ScreemComboBoxClass, changed), NULL, NULL, screem_marshal_VOID__VOID, G_TYPE_NONE, 0 ); } static void screem_combo_box_init( ScreemComboBox *combo_box ) { ScreemComboBoxPrivate *priv; GtkWidget *widget; GtkWidget *arrow; GtkRcStyle *style; priv = combo_box->priv = g_new0( ScreemComboBoxPrivate, 1 ); priv->active = -1; priv->button = gtk_toggle_button_new(); widget = gtk_hbox_new( FALSE, 0 ); priv->label = gtk_label_new( "" ); gtk_box_pack_start( GTK_BOX( widget ), priv->label, TRUE, TRUE, 0 ); arrow = gtk_arrow_new( GTK_ARROW_DOWN, GTK_SHADOW_NONE ); gtk_box_pack_start( GTK_BOX( widget ), arrow, FALSE, FALSE, 0 ); gtk_container_add( GTK_CONTAINER( priv->button ), widget ); gtk_button_set_relief( GTK_BUTTON( priv->button ), GTK_RELIEF_NONE ); gtk_toggle_button_set_mode( GTK_TOGGLE_BUTTON( priv->button ), FALSE ); g_signal_connect( G_OBJECT( priv->button ), "toggled", G_CALLBACK( screem_combo_box_activate ), combo_box ); gtk_container_add( GTK_CONTAINER( combo_box ), priv->button ); gtk_container_set_border_width( GTK_CONTAINER( priv->button ), 0 ); gtk_container_set_border_width( GTK_CONTAINER( combo_box ), 0 ); style = gtk_widget_get_modifier_style( priv->button ); style->xthickness = 0; style->ythickness = 0; gtk_widget_modify_style( priv->button, style ); } static void screem_combo_box_finalize( GObject *screem_combo_box ) { ScreemComboBox *combo_box; ScreemComboBoxPrivate *priv; combo_box = SCREEM_COMBO_BOX( screem_combo_box ); priv = combo_box->priv; if( priv->model ) { g_object_unref( priv->model ); } if( priv->menu ) { gtk_widget_destroy( priv->menu ); } g_free( priv ); G_OBJECT_CLASS( screem_combo_box_parent_class )->finalize( screem_combo_box ); } static void screem_combo_box_set_prop( GObject *object, guint property_id, const GValue *value, GParamSpec *pspec ) { ScreemComboBox *combo_box; GtkTreeModel *model; gint active; combo_box = SCREEM_COMBO_BOX( object ); switch( property_id ) { case PROP_MODEL: model = g_value_get_object( value ); screem_combo_box_set_model_real( combo_box, model ); break; case PROP_ACTIVE: active = g_value_get_int( value ); screem_combo_box_set_active_real( combo_box, active ); break; } } static void screem_combo_box_get_prop( GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { ScreemComboBox *combo_box; ScreemComboBoxPrivate *priv; combo_box = SCREEM_COMBO_BOX( object ); priv = combo_box->priv; switch( property_id ) { case PROP_MODEL: g_value_set_object( value, priv->model ); break; case PROP_ACTIVE: g_value_set_int( value, priv->active ); break; } } static void screem_combo_box_size_request( GtkWidget *widget, GtkRequisition *requisition ) { ScreemComboBox *box; ScreemComboBoxPrivate *priv; GtkBin *bin; GtkRequisition child_requisition; box = SCREEM_COMBO_BOX( widget ); priv = box->priv; bin = GTK_BIN (widget); requisition->width = GTK_CONTAINER( widget )->border_width * 2; requisition->height = GTK_CONTAINER( widget )->border_width * 2; if( priv->menu ) { gtk_widget_size_request( priv->menu, &child_requisition ); requisition->width += child_requisition.width; } if( bin->child && GTK_WIDGET_VISIBLE( bin->child ) ) { gtk_widget_size_request( bin->child, &child_requisition ); requisition->width = MAX( requisition->width, child_requisition.width ); requisition->height += child_requisition.height; } } static void screem_combo_box_size_allocate( GtkWidget *widget, GtkAllocation *allocation ) { ScreemComboBox *box; ScreemComboBoxPrivate *priv; GtkBin *bin; GtkAllocation child_allocation; GtkRequisition child_req; box = SCREEM_COMBO_BOX( widget ); priv = box->priv; bin = GTK_BIN( widget ); widget->allocation = *allocation; if( bin->child ) { child_allocation.x = allocation->x + GTK_CONTAINER( widget )->border_width; child_allocation.y = allocation->y + GTK_CONTAINER (widget)->border_width; child_allocation.width = MAX( allocation->width - GTK_CONTAINER( widget )->border_width * 2, 0); child_allocation.height = MAX( allocation->height - GTK_CONTAINER (widget)->border_width * 2, 0); if( priv->menu ) { gtk_widget_size_request( priv->menu, &child_req ); child_allocation.width = MAX( child_allocation.width, child_req.width ); } gtk_widget_size_allocate( bin->child, &child_allocation ); } } /* static */ static void screem_combo_box_pos_func( GtkWidget *menu, gint *x, gint *y, gboolean *push_in, ScreemComboBox *box ) { GdkWindow *win; GtkRequisition req; GdkScreen *screen; gint h; win = GTK_BUTTON( box->priv->button )->event_window; gdk_window_get_origin( win, x, y ); gtk_widget_size_request( menu, &req ); screen = gtk_widget_get_screen( menu ); h = gdk_screen_get_height( screen ); if( (*y) + req.height > h ) { (*y) = h - req.height; if( (*y) < 0 ) { (*y) = 0; } } } static void screem_combo_box_activate( GtkWidget *widget, ScreemComboBox *box ) { ScreemComboBoxPrivate *priv; gboolean active; priv = box->priv; active = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( widget ) ); if( active ) { screem_popup_menu_do_popup_modal( priv->menu, (GtkMenuPositionFunc)screem_combo_box_pos_func, box, NULL, 0, priv->button ); gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( widget ), FALSE ); } else { gtk_widget_hide( priv->menu ); } } static void screem_combo_box_set_model_real( ScreemComboBox *box, GtkTreeModel *model ) { ScreemComboBoxPrivate *priv; priv = box->priv; if( priv->model ) { g_object_unref( priv->model ); } g_object_ref( model ); priv->model = model; screem_combo_box_set_active( box, -1 ); screem_combo_box_build_menu( box ); g_signal_connect( G_OBJECT( model ), "row_inserted", G_CALLBACK( screem_combo_box_item_inserted ), box ); g_signal_connect( G_OBJECT( model ), "row_deleted", G_CALLBACK( screem_combo_box_item_deleted ), box ); g_signal_connect( G_OBJECT( model ), "row_changed", G_CALLBACK( screem_combo_box_item_changed ), box ); } static void screem_combo_box_build_menu( ScreemComboBox *box ) { ScreemComboBoxPrivate *priv; GtkTreeModel *model; GtkTreeIter it; GtkWidget *item; gint i; gboolean loop; gchar *label; priv = box->priv; model = screem_combo_box_get_model( box ); if( priv->menu ) { gtk_widget_destroy( priv->menu ); } priv->menu = gtk_menu_new(); loop = gtk_tree_model_get_iter_first( model, &it ); for( i = 0; loop; ++ i ) { gtk_tree_model_get( model, &it, 0, &label, -1 ); item = gtk_menu_item_new_with_label( label ); g_free( label ); gtk_widget_show( item ); g_object_set_data( G_OBJECT( item ), "item", GINT_TO_POINTER( i ) ); g_signal_connect( G_OBJECT( item ), "activate", G_CALLBACK( screem_combo_box_activate_item ), box ); gtk_menu_shell_append( GTK_MENU_SHELL( priv->menu ), item ); loop = gtk_tree_model_iter_next( model, &it ); } } static void screem_combo_box_item_inserted( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *it, ScreemComboBox *box ) { ScreemComboBoxPrivate *priv; GtkWidget *item; gint *i; priv = box->priv; i = gtk_tree_path_get_indices( path ); item = gtk_menu_item_new_with_label( "" ); gtk_widget_show( item ); g_object_set_data( G_OBJECT( item ), "item", GINT_TO_POINTER( *i ) ); g_signal_connect( G_OBJECT( item ), "activate", G_CALLBACK( screem_combo_box_activate_item ), box ); gtk_menu_shell_insert( GTK_MENU_SHELL( priv->menu ), item, *i ); } static void screem_combo_box_item_deleted( GtkTreeModel *model, GtkTreePath *path, ScreemComboBox *box ) { screem_combo_box_build_menu( box ); } static void screem_combo_box_item_changed( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *it, ScreemComboBox *box ) { screem_combo_box_build_menu( box ); } static void screem_combo_box_set_active_real( ScreemComboBox *box, gint active ) { ScreemComboBoxPrivate *priv; GtkTreeModel *model; GtkTreeIter it; gchar *label; priv = box->priv; if( active < 0 ) { active = -1; } priv->active = active; model = screem_combo_box_get_model( box ); if( active == -1 || ! model ) { gtk_label_set_text( GTK_LABEL( priv->label ), "" ); } else if( gtk_tree_model_iter_nth_child( model, &it, NULL, active ) ) { gtk_tree_model_get( model, &it, 0, &label, -1 ); gtk_label_set_text( GTK_LABEL( priv->label ), label ); g_free( label ); } else { gtk_label_set_text( GTK_LABEL( priv->label ), "" ); } g_signal_emit( G_OBJECT( box ), screem_combo_box_signals[ CHANGED ], 0, NULL ); } static void screem_combo_box_activate_item( GtkWidget *item, ScreemComboBox *box ) { gint i; i = GPOINTER_TO_INT( g_object_get_data( G_OBJECT( item ), "item" ) ); screem_combo_box_set_active( box, i ); } /* public */ ScreemComboBox *screem_combo_box_new( void ) { ScreemComboBox *box; box = g_object_new( SCREEM_TYPE_COMBO_BOX, NULL ); return box; } GtkTreeModel *screem_combo_box_get_model( ScreemComboBox *combo ) { g_return_val_if_fail( SCREEM_IS_COMBO_BOX( combo ), NULL ); return combo->priv->model; } void screem_combo_box_set_model( ScreemComboBox *combo, GtkTreeModel *model ) { g_return_if_fail( SCREEM_IS_COMBO_BOX( combo ) ); g_return_if_fail( model != NULL ); g_object_set( G_OBJECT( combo ), "model", model, NULL ); } gint screem_combo_box_get_active( ScreemComboBox *combo ) { g_return_val_if_fail( SCREEM_IS_COMBO_BOX( combo ), -1 ); return combo->priv->active; } void screem_combo_box_set_active( ScreemComboBox *combo, gint active ) { g_return_if_fail( SCREEM_IS_COMBO_BOX( combo ) ); g_object_set( G_OBJECT( combo ), "active", active, NULL ); } gboolean screem_combo_box_get_active_iter( ScreemComboBox *combo, GtkTreeIter *iter ) { gboolean ret; gint active; GtkTreeModel *model; GtkTreePath *path; g_return_val_if_fail( SCREEM_IS_COMBO_BOX( combo ), FALSE ); g_return_val_if_fail( iter != NULL, FALSE ); ret = FALSE; active = screem_combo_box_get_active( combo ); model = screem_combo_box_get_model( combo ); if( active != -1 && model ) { path = gtk_tree_path_new_from_indices( active, -1 ); ret = gtk_tree_model_get_iter( model, iter, path ); gtk_tree_path_free( path ); } return ret; } void screem_combo_box_set_active_iter( ScreemComboBox *combo, GtkTreeIter *iter ) { GtkTreeModel *model; GtkTreePath *path; gint *i; g_return_if_fail( SCREEM_IS_COMBO_BOX( combo ) ); g_return_if_fail( iter != NULL ); model = screem_combo_box_get_model( combo ); g_return_if_fail( model != NULL ); path = gtk_tree_model_get_path( model, iter ); i = gtk_tree_path_get_indices( path ); screem_combo_box_set_active( combo, *i ); gtk_tree_path_free( path ); }