Code

Updated GtkAction subclass to allow "open" selections for a user to type into.
[inkscape.git] / src / ege-select-one-action.cpp
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  */
4 /* ***** BEGIN LICENSE BLOCK *****
5  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6  *
7  * The contents of this file are subject to the Mozilla Public License Version
8  * 1.1 (the "License"); you may not use this file except in compliance with
9  * the License. You may obtain a copy of the License at
10  * http://www.mozilla.org/MPL/
11  *
12  * Software distributed under the License is distributed on an "AS IS" basis,
13  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14  * for the specific language governing rights and limitations under the
15  * License.
16  *
17  * The Original Code is EGE Select One Action.
18  *
19  * The Initial Developer of the Original Code is
20  * Jon A. Cruz.
21  * Portions created by the Initial Developer are Copyright (C) 2010
22  * the Initial Developer. All Rights Reserved.
23  *
24  * Contributor(s):
25  *
26  * Alternatively, the contents of this file may be used under the terms of
27  * either the GNU General Public License Version 2 or later (the "GPL"), or
28  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29  * in which case the provisions of the GPL or the LGPL are applicable instead
30  * of those above. If you wish to allow use of your version of this file only
31  * under the terms of either the GPL or the LGPL, and not to allow others to
32  * use your version of this file under the terms of the MPL, indicate your
33  * decision by deleting the provisions above and replace them with the notice
34  * and other provisions required by the GPL or the LGPL. If you do not delete
35  * the provisions above, a recipient may use your version of this file under
36  * the terms of any one of the MPL, the GPL or the LGPL.
37  *
38  * ***** END LICENSE BLOCK ***** */
40 /* Note: this file should be kept compilable as both .cpp and .c */
42 #include <string.h>
44 #include <gtk/gtkhbox.h>
45 #include <gtk/gtklabel.h>
46 #include <gtk/gtktoolitem.h>
47 #include <gtk/gtk.h>
48 #include <gtk/gtkcellrenderertext.h>
49 #include <gtk/gtkcellrendererpixbuf.h>
50 #include <gtk/gtkcelllayout.h>
51 #include <gtk/gtkradioaction.h>
52 #include <gtk/gtkradiomenuitem.h>
53 #include <gtk/gtktable.h>
55 #include "ege-select-one-action.h"
57 enum {
58     CHANGED = 0,
59     LAST_SIGNAL};
62 static void ege_select_one_action_class_init( EgeSelectOneActionClass* klass );
63 static void ege_select_one_action_init( EgeSelectOneAction* action );
64 static void ege_select_one_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec );
65 static void ege_select_one_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec );
67 static gint find_text_index(EgeSelectOneAction *act, gchar const* text);
68 static void commit_pending_change(EgeSelectOneAction *act);
69 static void resync_active( EgeSelectOneAction* act, gint active, gboolean override );
70 static void combo_entry_changed_cb( GtkEntry* widget, gpointer user_data );
71 static gboolean combo_entry_focus_lost_cb( GtkWidget *widget, GdkEventFocus *event, gpointer data );
72 static void combo_changed_cb( GtkComboBox* widget, gpointer user_data );
73 static void menu_toggled_cb( GtkWidget* obj, gpointer data );
74 static void proxy_action_chagned_cb( GtkRadioAction* action, GtkRadioAction* current, gpointer user_data );
76 static GtkWidget* create_menu_item( GtkAction* action );
77 static GtkWidget* create_tool_item( GtkAction* action );
78 static void connect_proxy( GtkAction *action, GtkWidget *proxy );
79 static void disconnect_proxy( GtkAction *action, GtkWidget *proxy );
81 static GtkActionClass* gParentClass = 0;
82 static guint signals[LAST_SIGNAL] = {0};
83 static GQuark gDataName = 0;
86 enum {
87     APPEARANCE_UNKNOWN = -1,
88     APPEARANCE_NONE = 0,
89     APPEARANCE_FULL,    // label, then all choices represented by separate buttons
90     APPEARANCE_COMPACT, // label, then choices in a drop-down menu
91     APPEARANCE_MINIMAL, // no label, just choices in a drop-down menu
92 };
94 enum {
95     SELECTION_UNKNOWN = -1,
96     SELECTION_CLOSED = 0,
97     SELECTION_OPEN,
98 };
100 struct _EgeSelectOneActionPrivate
102     gint active;
103     gint labelColumn;
104     gint iconColumn;
105     gint tooltipColumn;
106     gint appearanceMode;
107     gint selectionMode;
108     gint iconSize;
109     GType radioActionType;
110     GtkTreeModel* model;
111     gchar *iconProperty;
112     gchar *appearance;
113     gchar *selection;
114     gchar *activeText;
115     gchar *pendingText;
116 };
118 #define EGE_SELECT_ONE_ACTION_GET_PRIVATE( o ) ( G_TYPE_INSTANCE_GET_PRIVATE( (o), EGE_SELECT_ONE_ACTION_TYPE, EgeSelectOneActionPrivate ) )
120 enum {
121     PROP_MODEL = 1,
122     PROP_ACTIVE,
123     PROP_LABEL_COLUMN,
124     PROP_ICON_COLUMN,
125     PROP_TOOLTIP_COLUMN,
126     PROP_ICON_PROP,
127     PROP_ICON_SIZE,
128     PROP_APPEARANCE,
129     PROP_SELECTION
130 };
132 GType ege_select_one_action_get_type( void )
134     static GType myType = 0;
135     if ( !myType ) {
136         static const GTypeInfo myInfo = {
137             sizeof( EgeSelectOneActionClass ),
138             NULL, /* base_init */
139             NULL, /* base_finalize */
140             (GClassInitFunc)ege_select_one_action_class_init,
141             NULL, /* class_finalize */
142             NULL, /* class_data */
143             sizeof( EgeSelectOneAction ),
144             0, /* n_preallocs */
145             (GInstanceInitFunc)ege_select_one_action_init,
146             NULL
147         };
149         myType = g_type_register_static( GTK_TYPE_ACTION, "EgeSelectOneAction", &myInfo, (GTypeFlags)0 );
150     }
152     return myType;
155 GtkTreeModel *ege_select_one_action_get_model(EgeSelectOneAction* action ){
156     return GTK_TREE_MODEL(action->private_data->model);
158 void ege_select_one_action_class_init( EgeSelectOneActionClass* klass )
160     if ( klass ) {
161         gParentClass = GTK_ACTION_CLASS( g_type_class_peek_parent( klass ) );
162         GObjectClass* objClass = G_OBJECT_CLASS( klass );
164         gDataName = g_quark_from_string("ege-select1-action");
166         objClass->get_property = ege_select_one_action_get_property;
167         objClass->set_property = ege_select_one_action_set_property;
169         klass->parent_class.create_menu_item = create_menu_item;
170         klass->parent_class.create_tool_item = create_tool_item;
171         klass->parent_class.connect_proxy = connect_proxy;
172         klass->parent_class.disconnect_proxy = disconnect_proxy;
174         g_object_class_install_property( objClass,
175                                          PROP_MODEL,
176                                          g_param_spec_object( "model",
177                                                               "Tree Model",
178                                                               "Tree model of possible items",
179                                                               GTK_TYPE_TREE_MODEL,
180                                                               (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
182         g_object_class_install_property( objClass,
183                                          PROP_ACTIVE,
184                                          g_param_spec_int( "active",
185                                                            "Active Selection",
186                                                            "The index of the selected item",
187                                                            -1, 20, 0,
188                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
190         g_object_class_install_property( objClass,
191                                          PROP_LABEL_COLUMN,
192                                          g_param_spec_int( "label-column",
193                                                            "Display Column",
194                                                            "The column of the model that holds display strings",
195                                                            0, 20, 0,
196                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
198         g_object_class_install_property( objClass,
199                                          PROP_ICON_COLUMN,
200                                          g_param_spec_int( "icon-column",
201                                                            "Icon Column",
202                                                            "The column of the model that holds display icon name",
203                                                            -1, 20, -1,
204                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
206         g_object_class_install_property( objClass,
207                                          PROP_TOOLTIP_COLUMN,
208                                          g_param_spec_int( "tooltip-column",
209                                                            "Tooltip Column",
210                                                           "The column of the model that holds tooltip strings",
211                                                            -1, 20, -1,
212                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
214         g_object_class_install_property( objClass,
215                                          PROP_ICON_PROP,
216                                          g_param_spec_string( "icon-property",
217                                                               "Icon Property",
218                                                               "Target icon property",
219                                                               "",
220                                                               (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
222         g_object_class_install_property( objClass,
223                                          PROP_ICON_SIZE,
224                                          g_param_spec_int( "icon-size",
225                                                            "Icon Size",
226                                                           "Target icon size",
227                                                            -1, 20, -1,
228                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
230         g_object_class_install_property( objClass,
231                                          PROP_APPEARANCE,
232                                          g_param_spec_string( "appearance",
233                                                               "Appearance hint",
234                                                               "A hint for how to display",
235                                                               "",
236                                                               (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
238         g_object_class_install_property( objClass,
239                                          PROP_SELECTION,
240                                          g_param_spec_string( "selection",
241                                                               "Selection set open or closed",
242                                                               "'open' to allow edits/additions, 'closed' to disallow.",
243                                                               "",
244                                                               (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
246         signals[CHANGED] = g_signal_new( "changed",
247                                          G_TYPE_FROM_CLASS(klass),
248                                          G_SIGNAL_RUN_FIRST,
249                                          G_STRUCT_OFFSET(EgeSelectOneActionClass, changed),
250                                          NULL, NULL,
251                                          g_cclosure_marshal_VOID__VOID,
252                                          G_TYPE_NONE, 0);
254         g_type_class_add_private( klass, sizeof(EgeSelectOneActionClass) );
255     }
259 void ege_select_one_action_init( EgeSelectOneAction* action )
261     action->private_data = EGE_SELECT_ONE_ACTION_GET_PRIVATE( action );
262     action->private_data->active = 0;
263     action->private_data->labelColumn = 0;
264     action->private_data->iconColumn = -1;
265     action->private_data->tooltipColumn = -1;
266     action->private_data->appearanceMode = APPEARANCE_NONE;
267     action->private_data->selectionMode = SELECTION_CLOSED;
268     action->private_data->radioActionType = 0;
269     action->private_data->model = 0;
270     action->private_data->iconProperty = g_strdup("stock-id");
271     action->private_data->iconSize = -1;
272     action->private_data->appearance = 0;
273     action->private_data->selection = 0;
274     action->private_data->activeText = 0;
275     action->private_data->pendingText = 0;
277 /*     g_signal_connect( action, "notify", G_CALLBACK( fixup_labels ), NULL ); */
280 EgeSelectOneAction* ege_select_one_action_new( const gchar *name,
281                                                const gchar *label,
282                                                const gchar *tooltip,
283                                                const gchar *stock_id,
284                                                GtkTreeModel* model )
286     GObject* obj = (GObject*)g_object_new( EGE_SELECT_ONE_ACTION_TYPE,
287                                            "name", name,
288                                            "label", label,
289                                            "tooltip", tooltip,
290                                            "stock_id", stock_id,
291                                            "model", model,
292                                            "active", 0,
293                                            "icon-property", "stock-id",
294                                            NULL );
296     EgeSelectOneAction* action = EGE_SELECT_ONE_ACTION( obj );
298     return action;
302 gint ege_select_one_action_get_active( EgeSelectOneAction* action )
304     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
305     return action->private_data->active;
308 gchar *ege_select_one_action_get_active_text( EgeSelectOneAction* action )
310     GtkTreeIter iter;
311     gchar *str = 0;
312     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
314     if ( action->private_data->active >= 0) {
315         if ( gtk_tree_model_iter_nth_child( action->private_data->model, &iter, NULL, action->private_data->active ) ) {
316             gtk_tree_model_get( action->private_data->model, &iter,
317                                 action->private_data->labelColumn, &str,
318                                 -1 );
319         }
320     } else if ( (action->private_data->active == -1) && action->private_data->activeText ) {
321         str = g_strdup(action->private_data->activeText);
322     }
324     return str;
327 void ege_select_one_action_set_active( EgeSelectOneAction* action, gint val )
329     g_object_set( G_OBJECT(action), "active", val, NULL );
332 gint ege_select_one_action_get_label_column( EgeSelectOneAction* action )
334     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
335     return action->private_data->labelColumn;
338 void ege_select_one_action_set_label_column( EgeSelectOneAction* action, gint col )
340     g_object_set( G_OBJECT(action), "label-column", col, NULL );
343 gint ege_select_one_action_get_icon_column( EgeSelectOneAction* action )
345     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
346     return action->private_data->iconColumn;
349 void ege_select_one_action_set_icon_column( EgeSelectOneAction* action, gint col )
351     g_object_set( G_OBJECT(action), "icon-column", col, NULL );
354 gint ege_select_one_action_get_icon_size( EgeSelectOneAction* action )
356     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
357     return action->private_data->iconSize;
360 void ege_select_one_action_set_icon_size( EgeSelectOneAction* action, gint size )
362     g_object_set( G_OBJECT(action), "icon-size", size, NULL );
365 gint ege_select_one_action_get_tooltip_column( EgeSelectOneAction* action )
367     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
368     return action->private_data->tooltipColumn;
371 void ege_select_one_action_set_tooltip_column( EgeSelectOneAction* action, gint col )
373     g_object_set( G_OBJECT(action), "tooltip-column", col, NULL );
376 void ege_select_one_action_set_appearance( EgeSelectOneAction* action, gchar const* val )
378     g_object_set( G_OBJECT(action), "appearance", val, NULL );
381 void ege_select_one_action_set_selection( EgeSelectOneAction* action, gchar const* val )
383     g_object_set( G_OBJECT(action), "selection", val, NULL );
386 void ege_select_one_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec )
388     EgeSelectOneAction* action = EGE_SELECT_ONE_ACTION( obj );
389     switch ( propId ) {
390         case PROP_MODEL:
391             g_value_set_object( value, action->private_data->model );
392             break;
394         case PROP_ACTIVE:
395             g_value_set_int( value, action->private_data->active );
396             break;
398         case PROP_LABEL_COLUMN:
399             g_value_set_int( value, action->private_data->labelColumn );
400             break;
402         case PROP_ICON_COLUMN:
403             g_value_set_int( value, action->private_data->iconColumn );
404             break;
406         case PROP_TOOLTIP_COLUMN:
407             g_value_set_int( value, action->private_data->tooltipColumn );
408             break;
410         case PROP_ICON_PROP:
411             g_value_set_string( value, action->private_data->iconProperty );
412             break;
414         case PROP_ICON_SIZE:
415             g_value_set_int( value, action->private_data->iconSize );
416             break;
418         case PROP_APPEARANCE:
419             g_value_set_string( value, action->private_data->appearance );
420             break;
422         case PROP_SELECTION:
423             g_value_set_string( value, action->private_data->selection );
424             break;
426         default:
427             G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
428     }
431 void ege_select_one_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec )
433     EgeSelectOneAction* action = EGE_SELECT_ONE_ACTION( obj );
434     switch ( propId ) {
435         case PROP_MODEL:
436         {
437             action->private_data->model = GTK_TREE_MODEL( g_value_get_object( value ) );
438         }
439         break;
441         case PROP_ACTIVE:
442         {
443             resync_active( action, g_value_get_int( value ), FALSE );
444         }
445         break;
447         case PROP_LABEL_COLUMN:
448         {
449             action->private_data->labelColumn = g_value_get_int( value );
450         }
451         break;
453         case PROP_ICON_COLUMN:
454         {
455             action->private_data->iconColumn = g_value_get_int( value );
456         }
457         break;
459         case PROP_TOOLTIP_COLUMN:
460         {
461             action->private_data->tooltipColumn = g_value_get_int( value );
462         }
463         break;
465         case PROP_ICON_PROP:
466         {
467             gchar* tmp = action->private_data->iconProperty;
468             gchar* newVal = g_value_dup_string( value );
469             action->private_data->iconProperty = newVal;
470             g_free( tmp );
471         }
472         break;
474         case PROP_ICON_SIZE:
475         {
476             action->private_data->iconSize = g_value_get_int( value );
477         }
478         break;
480         case PROP_APPEARANCE:
481         {
482             gchar* tmp = action->private_data->appearance;
483             gchar* newVal = g_value_dup_string( value );
484             action->private_data->appearance = newVal;
485             g_free( tmp );
487             if ( !action->private_data->appearance || (strcmp("", newVal) == 0) ) {
488                 action->private_data->appearanceMode = APPEARANCE_NONE;
489             } else if ( strcmp("full", newVal) == 0 ) {
490                 action->private_data->appearanceMode = APPEARANCE_FULL;
491             } else if ( strcmp("compact", newVal) == 0 ) {
492                 action->private_data->appearanceMode = APPEARANCE_COMPACT;
493             } else if ( strcmp("minimal", newVal) == 0 ) {
494                 action->private_data->appearanceMode = APPEARANCE_MINIMAL;
495             } else {
496                 action->private_data->appearanceMode = APPEARANCE_UNKNOWN;
497             }
498         }
499         break;
501         case PROP_SELECTION:
502         {
503             gchar* tmp = action->private_data->selection;
504             gchar* newVal = g_value_dup_string( value );
505             action->private_data->selection = newVal;
506             g_free( tmp );
508             if ( !action->private_data->selection || (strcmp("closed", newVal) == 0) ) {
509                 action->private_data->selectionMode = SELECTION_CLOSED;
510             } else if ( strcmp("open", newVal) == 0 ) {
511                 action->private_data->selectionMode = SELECTION_OPEN;
512             } else {
513                 action->private_data->selectionMode = SELECTION_UNKNOWN;
514             }
515         }
516         break;
518         default:
519             G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
520     }
523 GtkWidget* create_menu_item( GtkAction* action )
525     GtkWidget* item = 0;
527     if ( IS_EGE_SELECT_ONE_ACTION(action) ) {
528         EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION( action );
529         gchar*  sss = 0;
530         gboolean valid = FALSE;
531         gint index = 0;
532         GtkTreeIter iter;
533         GSList* group = 0;
534         GtkWidget* subby = gtk_menu_new();
536         g_object_get( G_OBJECT(action), "label", &sss, NULL );
538         item = gtk_menu_item_new_with_label( sss );
540         valid = gtk_tree_model_get_iter_first( act->private_data->model, &iter );
541         while ( valid ) {
542             gchar* str = 0;
543             gtk_tree_model_get( act->private_data->model, &iter,
544                                 act->private_data->labelColumn, &str,
545                                 -1 );
547             GtkWidget *item = gtk_radio_menu_item_new_with_label( group, str );
548             group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item) );
549             gtk_menu_shell_append( GTK_MENU_SHELL(subby), item );
550             g_object_set_qdata( G_OBJECT(item), gDataName, act );
552             gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(item), index == act->private_data->active );
554             g_free(str);
555             str = 0;
557             g_signal_connect( G_OBJECT(item), "toggled", G_CALLBACK(menu_toggled_cb), GINT_TO_POINTER(index) );
559             index++;
560             valid = gtk_tree_model_iter_next( act->private_data->model, &iter );
561         }
563         gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), subby );
564         gtk_widget_show_all( subby );
566         g_free(sss);
567     } else {
568         item = gParentClass->create_menu_item( action );
569     }
571     return item;
575 void ege_select_one_action_set_radio_action_type( EgeSelectOneAction* action, GType radioActionType )
577     (void)action;
579     if ( g_type_is_a( radioActionType, GTK_TYPE_RADIO_ACTION ) ) {
580         action->private_data->radioActionType = radioActionType;
581     } else {
582         g_warning("Passed in type '%s' is not derived from '%s'", g_type_name(radioActionType), g_type_name(GTK_TYPE_RADIO_ACTION) );
583     }
586 GtkWidget* create_tool_item( GtkAction* action )
588     GtkWidget* item = 0;
590     if ( IS_EGE_SELECT_ONE_ACTION(action) && EGE_SELECT_ONE_ACTION(action)->private_data->model )
591     {
592         EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(action);
593         item = GTK_WIDGET( gtk_tool_item_new() );
595         if ( act->private_data->appearanceMode == APPEARANCE_FULL ) {
596             GtkWidget* holder = gtk_hbox_new( FALSE, 0 );
598             GtkRadioAction* ract = 0;
599             GtkWidget* sub = 0;
600             GSList* group = 0;
601             GtkTreeIter iter;
602             gboolean valid = FALSE;
603             gint index = 0;
604             GtkTooltips* tooltips = gtk_tooltips_new();
606             gchar*  sss = 0;
607             g_object_get( G_OBJECT(action), "short_label", &sss, NULL );
608             if (sss) {
609                 GtkWidget* lbl;
610                 lbl = gtk_label_new(sss);
611                 gtk_box_pack_start( GTK_BOX(holder), lbl, FALSE, FALSE, 4 );
612             }
614             valid = gtk_tree_model_get_iter_first( act->private_data->model, &iter );
615             while ( valid ) {
616                 gchar* str = 0;
617                 gchar* tip = 0;
618                 gchar* iconId = 0;
619                 /*
620                 gint size = 0;
621                 */
622                 gtk_tree_model_get( act->private_data->model, &iter,
623                                     act->private_data->labelColumn, &str,
624                                     -1 );
625                 if ( act->private_data->iconColumn >= 0 ) {
626                     gtk_tree_model_get( act->private_data->model, &iter,
627                                         act->private_data->iconColumn, &iconId,
628                                         -1 );
629                 }
630                 if ( act->private_data->tooltipColumn >= 0 ) {
631                     gtk_tree_model_get( act->private_data->model, &iter,
632                                         act->private_data->tooltipColumn, &tip,
633                                         -1 );
634                 }
636                 if ( act->private_data->radioActionType ) {
637                     void* obj = g_object_new( act->private_data->radioActionType,
638                                               "name", "Name 1",
639                                               "label", str,
640                                               "tooltip", tip,
641                                               "value", index,
642                                               /*
643                                               "iconId", iconId,
644                                               "iconSize", size,
645                                               */
646                                               NULL );
647                     if ( iconId ) {
648                         g_object_set( G_OBJECT(obj), act->private_data->iconProperty, iconId, NULL );
649                     }
651                     if ( act->private_data->iconProperty ) {
652                         /* TODO get this string to be set instead of hardcoded */
653                         if ( act->private_data->iconSize >= 0 ) {
654                             g_object_set( G_OBJECT(obj), "iconSize", act->private_data->iconSize, NULL );
655                         }
656                     }
658                     ract = GTK_RADIO_ACTION(obj);
659                 } else {
660                     ract = gtk_radio_action_new( "Name 1", str, tip, iconId, index );
661                 }
663                 gtk_radio_action_set_group( ract, group );
664                 group = gtk_radio_action_get_group( ract );
666                 if ( index == act->private_data->active ) {
667                     gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(ract), TRUE );
668                 }
669                 g_signal_connect( G_OBJECT(ract), "changed", G_CALLBACK( proxy_action_chagned_cb ), act );
671                 sub = gtk_action_create_tool_item( GTK_ACTION(ract) );
672                 gtk_action_connect_proxy( GTK_ACTION(ract), sub );
673                 gtk_tool_item_set_tooltip( GTK_TOOL_ITEM(sub), tooltips, tip, NULL );
675                 gtk_box_pack_start( GTK_BOX(holder), sub, FALSE, FALSE, 0 );
677                 g_free( str );
678                 g_free( tip );
679                 g_free( iconId );
681                 index++;
682                 valid = gtk_tree_model_iter_next( act->private_data->model, &iter );
683             }
685             g_object_set_data( G_OBJECT(holder), "ege-proxy_action-group", group );
686             g_object_set_data( G_OBJECT(holder), "ege-tooltips", tooltips );
688             gtk_container_add( GTK_CONTAINER(item), holder );
689         } else {
690             GtkCellRenderer * renderer = 0;
691             GtkWidget* holder = gtk_hbox_new( FALSE, 4 );
692             GtkWidget* normal = (act->private_data->selectionMode == SELECTION_OPEN) ?
693                 gtk_combo_box_entry_new_with_model( act->private_data->model, act->private_data->labelColumn ) :
694                 gtk_combo_box_new_with_model( act->private_data->model );
695             if ((act->private_data->selectionMode == SELECTION_OPEN)) {
696                 GtkWidget *child = gtk_bin_get_child( GTK_BIN(normal) );
697                 if (GTK_IS_ENTRY(child)) {
698                     gtk_entry_set_width_chars(GTK_ENTRY(child), 4);
699                     g_signal_connect( G_OBJECT(child), "activate", G_CALLBACK(combo_entry_changed_cb), act );
700                     g_signal_connect( G_OBJECT(child), "focus-out-event", G_CALLBACK(combo_entry_focus_lost_cb), act );
701                 }
702             } else {
703                 if ( act->private_data->iconColumn >= 0 ) {
704                     renderer = gtk_cell_renderer_pixbuf_new();
705                     gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(normal), renderer, TRUE );
707                     // "icon-name"
708                     gtk_cell_layout_add_attribute( GTK_CELL_LAYOUT(normal), renderer, "stock-id", act->private_data->iconColumn );
709                 }
711                 renderer = gtk_cell_renderer_text_new();
712                 gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(normal), renderer, TRUE );
713                 gtk_cell_layout_add_attribute( GTK_CELL_LAYOUT(normal), renderer, "text", act->private_data->labelColumn );
714             }
716             gtk_combo_box_set_active( GTK_COMBO_BOX(normal), act->private_data->active );
718             g_signal_connect( G_OBJECT(normal), "changed", G_CALLBACK(combo_changed_cb), action );
720             g_object_set_data( G_OBJECT(holder), "ege-combo-box", normal );
722             if (act->private_data->appearanceMode == APPEARANCE_COMPACT) {
723                 gchar*  sss = 0;
724                 g_object_get( G_OBJECT(action), "short_label", &sss, NULL );
725                 if (sss) {
726                     GtkWidget* lbl;
727                     lbl = gtk_label_new(sss);
728                     gtk_box_pack_start( GTK_BOX(holder), lbl, FALSE, FALSE, 4 );
729                 }
730             }
732             gtk_box_pack_start( GTK_BOX(holder), normal, FALSE, FALSE, 0 );
734             {
735                 GtkWidget *align = gtk_alignment_new(0, 0.5, 0, 0);
736                 gtk_container_add( GTK_CONTAINER(align), holder);
737                 gtk_container_add( GTK_CONTAINER(item), align );
738             }
739         }
741         gtk_widget_show_all( item );
742     } else {
743         item = gParentClass->create_tool_item( action );
744     }
746     return item;
750 void connect_proxy( GtkAction *action, GtkWidget *proxy )
752     gParentClass->connect_proxy( action, proxy );
755 void disconnect_proxy( GtkAction *action, GtkWidget *proxy )
757     gParentClass->disconnect_proxy( action, proxy );
761 void resync_active( EgeSelectOneAction* act, gint active, gboolean override )
763     if ( override || (act->private_data->active != active) ) {
764         act->private_data->active = active;
765         GSList* proxies = gtk_action_get_proxies( GTK_ACTION(act) );
766         while ( proxies ) {
767             if ( GTK_IS_TOOL_ITEM(proxies->data) ) {
768                 /* Search for the things we built up in create_tool_item() */
769                 GList* children = gtk_container_get_children( GTK_CONTAINER(proxies->data) );
770                 if ( children && children->data ) {
771                     gpointer combodata = g_object_get_data( G_OBJECT(children->data), "ege-combo-box" );
772                     if (!combodata && GTK_IS_ALIGNMENT(children->data)) {
773                         GList *other = gtk_container_get_children( GTK_CONTAINER(children->data) );
774                          combodata = g_object_get_data( G_OBJECT(other->data), "ege-combo-box" );
775                     }
776                     if ( GTK_IS_COMBO_BOX(combodata) ) {
777                         GtkComboBox* combo = GTK_COMBO_BOX(combodata);
778                         if ((active == -1) && (GTK_IS_COMBO_BOX_ENTRY(combo))) {
779                             gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))), act->private_data->activeText);
780                         } else if ( gtk_combo_box_get_active(combo) != active ) {
781                             gtk_combo_box_set_active( combo, active );
782                         }
783                     } else if ( GTK_IS_HBOX(children->data) ) {
784                         gpointer data = g_object_get_data( G_OBJECT(children->data), "ege-proxy_action-group" );
785                         if ( data ) {
786                             GSList* group = (GSList*)data;
787                             GtkRadioAction* oneAction = GTK_RADIO_ACTION(group->data);
788                             gint hot = gtk_radio_action_get_current_value( oneAction );
789                             if ( hot != active ) {
790                                 /*gtk_radio_action_set_current_value( oneAction, active );*/
791                                 gint value = 0;
792                                 while ( group ) {
793                                     GtkRadioAction* possible = GTK_RADIO_ACTION(group->data);
794                                     g_object_get( G_OBJECT(possible), "value", &value, NULL );
795                                     if ( value == active ) {
796                                         /* Found the group member to set active */
797                                         gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(possible), TRUE );
798                                         break;
799                                     }
801                                     group = g_slist_next(group);
802                                 }
803                             }
804                         }
805                     }
806                 }
807             } else if ( GTK_IS_MENU_ITEM(proxies->data) ) {
808                 GtkWidget* subMenu = gtk_menu_item_get_submenu( GTK_MENU_ITEM(proxies->data) );
809                 GList* children = gtk_container_get_children( GTK_CONTAINER(subMenu) );
810                 if ( children && (g_list_length(children) > (guint)active) ) {
811                     gpointer data = g_list_nth_data( children, active );
812                     gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(data), TRUE );
813                 }
814             }
816             proxies = g_slist_next( proxies );
817         }
819         g_signal_emit( G_OBJECT(act), signals[CHANGED], 0);
820     }
823 void combo_changed_cb( GtkComboBox* widget, gpointer user_data )
825     EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(user_data);
826     gint newActive = gtk_combo_box_get_active(widget);
827     gchar *text = gtk_combo_box_get_active_text(widget);
829     if (newActive == -1) {
830         /* indicates the user is entering text for a custom aka "open" value */
831         if (act->private_data->pendingText && text && (strcmp(act->private_data->pendingText, text) == 0) ) {
832             /* The currently entered data matches the last seen */
833         } else {
834             if (act->private_data->pendingText) {
835                 g_free(act->private_data->pendingText);
836             }
837             act->private_data->pendingText = text;
838             text = 0;
839         }
840     } else if (newActive != act->private_data->active) {
841         if (act->private_data->pendingText) {
842             g_free(act->private_data->pendingText);
843             act->private_data->pendingText = 0;
844         }
845         g_object_set( G_OBJECT(act), "active", newActive, NULL );
846     }
848     if (text) {
849         g_free(text);
850         text = 0;
851     }
854 gboolean combo_entry_focus_lost_cb( GtkWidget *widget, GdkEventFocus *event, gpointer data )
856     EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(data);
857     (void)widget;
858     (void)event;
860     commit_pending_change(act);
862     return FALSE;
865 void combo_entry_changed_cb( GtkEntry* widget, gpointer user_data )
867     EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(user_data);
868     (void)widget;
869     commit_pending_change(act);
872 void commit_pending_change(EgeSelectOneAction *act)
874     if (act->private_data->pendingText) {
875         if (act->private_data->activeText && (strcmp(act->private_data->pendingText, act->private_data->activeText) == 0)) {
876             /* Was the same value */
877             g_free(act->private_data->pendingText);
878             act->private_data->pendingText = 0;
879         } else {
880             gint matching = find_text_index(act, act->private_data->pendingText);
882             if (act->private_data->activeText) {
883                 g_free(act->private_data->activeText);
884             }
885             act->private_data->activeText = act->private_data->pendingText;
886             act->private_data->pendingText = 0;
888             if (matching >= 0) {
889                 g_free(act->private_data->activeText);
890                 act->private_data->activeText = 0;
891                 g_object_set( G_OBJECT(act), "active", matching, NULL );
892             } else if (act->private_data->active != -1) {
893                 g_object_set( G_OBJECT(act), "active", -1, NULL );
894             } else {
895                 resync_active( act, -1, TRUE );
896             }
897         }
898     }
901 gint find_text_index(EgeSelectOneAction *act, gchar const* text)
903     gint index = -1;
905     if (text) {
906         GtkTreeIter iter;
907         gboolean valid = gtk_tree_model_get_iter_first( act->private_data->model, &iter );
908         gint curr = 0;
909         while ( valid && (index < 0) ) {
910             gchar* str = 0;
911             gtk_tree_model_get( act->private_data->model, &iter,
912                                 act->private_data->labelColumn, &str,
913                                 -1 );
915             if (str && (strcmp(text, str) == 0)) {
916                 index = curr;
917             }
919             g_free(str);
920             str = 0;
922             curr++;
923             valid = gtk_tree_model_iter_next( act->private_data->model, &iter );
924         }
925     }
927     return index;
930 void menu_toggled_cb( GtkWidget* obj, gpointer data )
932     GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(obj);
933     EgeSelectOneAction* act = (EgeSelectOneAction*)g_object_get_qdata( G_OBJECT(obj), gDataName );
934     gint newActive = GPOINTER_TO_INT(data);
935     if ( gtk_check_menu_item_get_active(item) && (newActive != act->private_data->active) ) {
936         g_object_set( G_OBJECT(act), "active", newActive, NULL );
937     }
940 void proxy_action_chagned_cb( GtkRadioAction* action, GtkRadioAction* current, gpointer user_data )
942     (void)current;
943     if ( gtk_toggle_action_get_active( GTK_TOGGLE_ACTION(action) ) ) {
944         EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(user_data);
945         gint newActive = gtk_radio_action_get_current_value( action );
946         if ( newActive != act->private_data->active ) {
947             g_object_set( G_OBJECT(act), "active", newActive, NULL );
948         }
949     }