Code

Store cached icons to disk between runs, and invalidate/purge as needed.
[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 resync_sensitive( EgeSelectOneAction* act );
71 static void combo_entry_changed_cb( GtkEntry* widget, gpointer user_data );
72 static gboolean combo_entry_focus_lost_cb( GtkWidget *widget, GdkEventFocus *event, gpointer data );
73 static void combo_changed_cb( GtkComboBox* widget, gpointer user_data );
74 static void menu_toggled_cb( GtkWidget* obj, gpointer data );
75 static void proxy_action_chagned_cb( GtkRadioAction* action, GtkRadioAction* current, gpointer user_data );
77 static GtkWidget* create_menu_item( GtkAction* action );
78 static GtkWidget* create_tool_item( GtkAction* action );
79 static void connect_proxy( GtkAction *action, GtkWidget *proxy );
80 static void disconnect_proxy( GtkAction *action, GtkWidget *proxy );
82 static int scan_max_width( GtkTreeModel *model, gint labelColumn );
84 static GtkActionClass* gParentClass = 0;
85 static guint signals[LAST_SIGNAL] = {0};
86 static GQuark gDataName = 0;
89 enum {
90     APPEARANCE_UNKNOWN = -1,
91     APPEARANCE_NONE = 0,
92     APPEARANCE_FULL,    /* label, then all choices represented by separate buttons */
93     APPEARANCE_COMPACT, /* label, then choices in a drop-down menu */
94     APPEARANCE_MINIMAL, /* no label, just choices in a drop-down menu */
95 };
97 enum {
98     SELECTION_UNKNOWN = -1,
99     SELECTION_CLOSED = 0,
100     SELECTION_OPEN,
101 };
103 struct _EgeSelectOneActionPrivate
105     gint active;
106     gint labelColumn;
107     gint iconColumn;
108     gint tooltipColumn;
109     gint sensitiveColumn;
110     gint appearanceMode;
111     gint selectionMode;
112     gint iconSize;
113     GType radioActionType;
114     GtkTreeModel* model;
115     gchar *iconProperty;
116     gchar *appearance;
117     gchar *selection;
118     gchar *activeText;
119     gchar *pendingText;
120 };
122 #define EGE_SELECT_ONE_ACTION_GET_PRIVATE( o ) ( G_TYPE_INSTANCE_GET_PRIVATE( (o), EGE_SELECT_ONE_ACTION_TYPE, EgeSelectOneActionPrivate ) )
124 enum {
125     PROP_MODEL = 1,
126     PROP_ACTIVE,
127     PROP_LABEL_COLUMN,
128     PROP_ICON_COLUMN,
129     PROP_TOOLTIP_COLUMN,
130     PROP_SENSITIVE_COLUMN,
131     PROP_ICON_PROP,
132     PROP_ICON_SIZE,
133     PROP_APPEARANCE,
134     PROP_SELECTION
135 };
137 GType ege_select_one_action_get_type( void )
139     static GType myType = 0;
140     if ( !myType ) {
141         static const GTypeInfo myInfo = {
142             sizeof( EgeSelectOneActionClass ),
143             NULL, /* base_init */
144             NULL, /* base_finalize */
145             (GClassInitFunc)ege_select_one_action_class_init,
146             NULL, /* class_finalize */
147             NULL, /* class_data */
148             sizeof( EgeSelectOneAction ),
149             0, /* n_preallocs */
150             (GInstanceInitFunc)ege_select_one_action_init,
151             NULL
152         };
154         myType = g_type_register_static( GTK_TYPE_ACTION, "EgeSelectOneAction", &myInfo, (GTypeFlags)0 );
155     }
157     return myType;
160 GtkTreeModel *ege_select_one_action_get_model(EgeSelectOneAction* action ){
161     return GTK_TREE_MODEL(action->private_data->model);
163 void ege_select_one_action_class_init( EgeSelectOneActionClass* klass )
165     if ( klass ) {
166         gParentClass = GTK_ACTION_CLASS( g_type_class_peek_parent( klass ) );
167         GObjectClass* objClass = G_OBJECT_CLASS( klass );
169         gDataName = g_quark_from_string("ege-select1-action");
171         objClass->get_property = ege_select_one_action_get_property;
172         objClass->set_property = ege_select_one_action_set_property;
174         klass->parent_class.create_menu_item = create_menu_item;
175         klass->parent_class.create_tool_item = create_tool_item;
176         klass->parent_class.connect_proxy = connect_proxy;
177         klass->parent_class.disconnect_proxy = disconnect_proxy;
179         g_object_class_install_property( objClass,
180                                          PROP_MODEL,
181                                          g_param_spec_object( "model",
182                                                               "Tree Model",
183                                                               "Tree model of possible items",
184                                                               GTK_TYPE_TREE_MODEL,
185                                                               (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
187         g_object_class_install_property( objClass,
188                                          PROP_ACTIVE,
189                                          g_param_spec_int( "active",
190                                                            "Active Selection",
191                                                            "The index of the selected item",
192                                                            -1, G_MAXINT, 0,
193                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
195         g_object_class_install_property( objClass,
196                                          PROP_LABEL_COLUMN,
197                                          g_param_spec_int( "label-column",
198                                                            "Display Column",
199                                                            "The column of the model that holds display strings",
200                                                            0, G_MAXINT, 0,
201                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
203         g_object_class_install_property( objClass,
204                                          PROP_ICON_COLUMN,
205                                          g_param_spec_int( "icon-column",
206                                                            "Icon Column",
207                                                            "The column of the model that holds display icon name",
208                                                            -1, G_MAXINT, -1,
209                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
211         g_object_class_install_property( objClass,
212                                          PROP_TOOLTIP_COLUMN,
213                                          g_param_spec_int( "tooltip-column",
214                                                            "Tooltip Column",
215                                                           "The column of the model that holds tooltip strings",
216                                                            -1, G_MAXINT, -1,
217                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
219         g_object_class_install_property( objClass,
220                                          PROP_SENSITIVE_COLUMN,
221                                          g_param_spec_int( "sensitive-column",
222                                                            "Sensitive Column",
223                                                           "The column of the model that holds sensitive state",
224                                                            -1, G_MAXINT, -1,
225                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
227         g_object_class_install_property( objClass,
228                                          PROP_ICON_PROP,
229                                          g_param_spec_string( "icon-property",
230                                                               "Icon Property",
231                                                               "Target icon property",
232                                                               "",
233                                                               (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
235         g_object_class_install_property( objClass,
236                                          PROP_ICON_SIZE,
237                                          g_param_spec_int( "icon-size",
238                                                            "Icon Size",
239                                                           "Target icon size",
240                                                            -1, G_MAXINT, -1,
241                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
243         g_object_class_install_property( objClass,
244                                          PROP_APPEARANCE,
245                                          g_param_spec_string( "appearance",
246                                                               "Appearance hint",
247                                                               "A hint for how to display",
248                                                               "",
249                                                               (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
251         g_object_class_install_property( objClass,
252                                          PROP_SELECTION,
253                                          g_param_spec_string( "selection",
254                                                               "Selection set open or closed",
255                                                               "'open' to allow edits/additions, 'closed' to disallow.",
256                                                               "",
257                                                               (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
259         signals[CHANGED] = g_signal_new( "changed",
260                                          G_TYPE_FROM_CLASS(klass),
261                                          G_SIGNAL_RUN_FIRST,
262                                          G_STRUCT_OFFSET(EgeSelectOneActionClass, changed),
263                                          NULL, NULL,
264                                          g_cclosure_marshal_VOID__VOID,
265                                          G_TYPE_NONE, 0);
267         g_type_class_add_private( klass, sizeof(EgeSelectOneActionClass) );
268     }
272 void ege_select_one_action_init( EgeSelectOneAction* action )
274     action->private_data = EGE_SELECT_ONE_ACTION_GET_PRIVATE( action );
275     action->private_data->active = 0;
276     action->private_data->labelColumn = 0;
277     action->private_data->iconColumn = -1;
278     action->private_data->tooltipColumn = -1;
279     action->private_data->sensitiveColumn = -1;
280     action->private_data->appearanceMode = APPEARANCE_NONE;
281     action->private_data->selectionMode = SELECTION_CLOSED;
282     action->private_data->radioActionType = 0;
283     action->private_data->model = 0;
284     action->private_data->iconProperty = g_strdup("stock-id");
285     action->private_data->iconSize = -1;
286     action->private_data->appearance = 0;
287     action->private_data->selection = 0;
288     action->private_data->activeText = 0;
289     action->private_data->pendingText = 0;
291 /*     g_signal_connect( action, "notify", G_CALLBACK( fixup_labels ), NULL ); */
294 EgeSelectOneAction* ege_select_one_action_new( const gchar *name,
295                                                const gchar *label,
296                                                const gchar *tooltip,
297                                                const gchar *stock_id,
298                                                GtkTreeModel* model )
300     GObject* obj = (GObject*)g_object_new( EGE_SELECT_ONE_ACTION_TYPE,
301                                            "name", name,
302                                            "label", label,
303                                            "tooltip", tooltip,
304                                            "stock_id", stock_id,
305                                            "model", model,
306                                            "active", 0,
307                                            "icon-property", "stock-id",
308                                            NULL );
310     EgeSelectOneAction* action = EGE_SELECT_ONE_ACTION( obj );
312     return action;
316 gint ege_select_one_action_get_active( EgeSelectOneAction* action )
318     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
319     return action->private_data->active;
322 gchar *ege_select_one_action_get_active_text( EgeSelectOneAction* action )
324     GtkTreeIter iter;
325     gchar *str = 0;
326     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
328     if ( action->private_data->active >= 0) {
329         if ( gtk_tree_model_iter_nth_child( action->private_data->model, &iter, NULL, action->private_data->active ) ) {
330             gtk_tree_model_get( action->private_data->model, &iter,
331                                 action->private_data->labelColumn, &str,
332                                 -1 );
333         }
334     } else if ( (action->private_data->active == -1) && action->private_data->activeText ) {
335         str = g_strdup(action->private_data->activeText);
336     }
338     return str;
341 void ege_select_one_action_set_active_text( EgeSelectOneAction* action, gchar const *text )
343     g_return_if_fail( IS_EGE_SELECT_ONE_ACTION(action) );
345     if (action->private_data->activeText) {
346         g_free( action->private_data->activeText );
347     }
348     action->private_data->activeText = g_strdup(text);
350     if (action->private_data->active != -1) {
351         g_object_set( G_OBJECT(action), "active", -1, NULL );
352     } else {
353         resync_active( action, -1, TRUE );
354     }
357 void ege_select_one_action_set_active( EgeSelectOneAction* action, gint val )
359     g_object_set( G_OBJECT(action), "active", val, NULL );
362 void ege_select_one_action_update_sensitive( EgeSelectOneAction* action )
364     if( action->private_data->sensitiveColumn < 0 ) {
365         g_warning( "ege_select_one_action: Attempt to update sensitivity of item without sensitive column\n" );
366         return;
367     }
368     resync_sensitive( action );
371 gint ege_select_one_action_get_label_column( EgeSelectOneAction* action )
373     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
374     return action->private_data->labelColumn;
377 void ege_select_one_action_set_label_column( EgeSelectOneAction* action, gint col )
379     g_object_set( G_OBJECT(action), "label-column", col, NULL );
382 gint ege_select_one_action_get_icon_column( EgeSelectOneAction* action )
384     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
385     return action->private_data->iconColumn;
388 void ege_select_one_action_set_icon_column( EgeSelectOneAction* action, gint col )
390     g_object_set( G_OBJECT(action), "icon-column", col, NULL );
393 gint ege_select_one_action_get_icon_size( EgeSelectOneAction* action )
395     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
396     return action->private_data->iconSize;
399 void ege_select_one_action_set_icon_size( EgeSelectOneAction* action, gint size )
401     g_object_set( G_OBJECT(action), "icon-size", size, NULL );
404 gint ege_select_one_action_get_tooltip_column( EgeSelectOneAction* action )
406     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
407     return action->private_data->tooltipColumn;
410 void ege_select_one_action_set_tooltip_column( EgeSelectOneAction* action, gint col )
412     g_object_set( G_OBJECT(action), "tooltip-column", col, NULL );
415 gint ege_select_one_action_get_sensitive_column( EgeSelectOneAction* action )
417     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
418     return action->private_data->sensitiveColumn;
421 void ege_select_one_action_set_sensitive_column( EgeSelectOneAction* action, gint col )
423     g_object_set( G_OBJECT(action), "sensitive-column", col, NULL );
426 void ege_select_one_action_set_appearance( EgeSelectOneAction* action, gchar const* val )
428     g_object_set( G_OBJECT(action), "appearance", val, NULL );
431 void ege_select_one_action_set_selection( EgeSelectOneAction* action, gchar const* val )
433     g_object_set( G_OBJECT(action), "selection", val, NULL );
436 void ege_select_one_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec )
438     EgeSelectOneAction* action = EGE_SELECT_ONE_ACTION( obj );
439     switch ( propId ) {
440         case PROP_MODEL:
441             g_value_set_object( value, action->private_data->model );
442             break;
444         case PROP_ACTIVE:
445             g_value_set_int( value, action->private_data->active );
446             break;
448         case PROP_LABEL_COLUMN:
449             g_value_set_int( value, action->private_data->labelColumn );
450             break;
452         case PROP_ICON_COLUMN:
453             g_value_set_int( value, action->private_data->iconColumn );
454             break;
456         case PROP_TOOLTIP_COLUMN:
457             g_value_set_int( value, action->private_data->tooltipColumn );
458             break;
460         case PROP_SENSITIVE_COLUMN:
461             g_value_set_int( value, action->private_data->sensitiveColumn );
462             break;
464         case PROP_ICON_PROP:
465             g_value_set_string( value, action->private_data->iconProperty );
466             break;
468         case PROP_ICON_SIZE:
469             g_value_set_int( value, action->private_data->iconSize );
470             break;
472         case PROP_APPEARANCE:
473             g_value_set_string( value, action->private_data->appearance );
474             break;
476         case PROP_SELECTION:
477             g_value_set_string( value, action->private_data->selection );
478             break;
480         default:
481             G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
482     }
485 void ege_select_one_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec )
487     EgeSelectOneAction* action = EGE_SELECT_ONE_ACTION( obj );
488     switch ( propId ) {
489         case PROP_MODEL:
490         {
491             action->private_data->model = GTK_TREE_MODEL( g_value_get_object( value ) );
492         }
493         break;
495         case PROP_ACTIVE:
496         {
497             resync_active( action, g_value_get_int( value ), FALSE );
498         }
499         break;
501         case PROP_LABEL_COLUMN:
502         {
503             action->private_data->labelColumn = g_value_get_int( value );
504         }
505         break;
507         case PROP_ICON_COLUMN:
508         {
509             action->private_data->iconColumn = g_value_get_int( value );
510         }
511         break;
513         case PROP_TOOLTIP_COLUMN:
514         {
515             action->private_data->tooltipColumn = g_value_get_int( value );
516         }
517         break;
519         case PROP_SENSITIVE_COLUMN:
520         {
521             action->private_data->sensitiveColumn = g_value_get_int( value );
522         }
523         break;
525         case PROP_ICON_PROP:
526         {
527             gchar* tmp = action->private_data->iconProperty;
528             gchar* newVal = g_value_dup_string( value );
529             action->private_data->iconProperty = newVal;
530             g_free( tmp );
531         }
532         break;
534         case PROP_ICON_SIZE:
535         {
536             action->private_data->iconSize = g_value_get_int( value );
537         }
538         break;
540         case PROP_APPEARANCE:
541         {
542             gchar* tmp = action->private_data->appearance;
543             gchar* newVal = g_value_dup_string( value );
544             action->private_data->appearance = newVal;
545             g_free( tmp );
547             if ( !action->private_data->appearance || (strcmp("", newVal) == 0) ) {
548                 action->private_data->appearanceMode = APPEARANCE_NONE;
549             } else if ( strcmp("full", newVal) == 0 ) {
550                 action->private_data->appearanceMode = APPEARANCE_FULL;
551             } else if ( strcmp("compact", newVal) == 0 ) {
552                 action->private_data->appearanceMode = APPEARANCE_COMPACT;
553             } else if ( strcmp("minimal", newVal) == 0 ) {
554                 action->private_data->appearanceMode = APPEARANCE_MINIMAL;
555             } else {
556                 action->private_data->appearanceMode = APPEARANCE_UNKNOWN;
557             }
558         }
559         break;
561         case PROP_SELECTION:
562         {
563             gchar* tmp = action->private_data->selection;
564             gchar* newVal = g_value_dup_string( value );
565             action->private_data->selection = newVal;
566             g_free( tmp );
568             if ( !action->private_data->selection || (strcmp("closed", newVal) == 0) ) {
569                 action->private_data->selectionMode = SELECTION_CLOSED;
570             } else if ( strcmp("open", newVal) == 0 ) {
571                 action->private_data->selectionMode = SELECTION_OPEN;
572             } else {
573                 action->private_data->selectionMode = SELECTION_UNKNOWN;
574             }
575         }
576         break;
578         default:
579             G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
580     }
583 GtkWidget* create_menu_item( GtkAction* action )
585     GtkWidget* item = 0;
587     if ( IS_EGE_SELECT_ONE_ACTION(action) ) {
588         EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION( action );
589         gchar*  sss = 0;
590         gboolean valid = FALSE;
591         gint index = 0;
592         GtkTreeIter iter;
593         GSList* group = 0;
594         GtkWidget* subby = gtk_menu_new();
596         g_object_get( G_OBJECT(action), "label", &sss, NULL );
598         item = gtk_menu_item_new_with_label( sss );
600         valid = gtk_tree_model_get_iter_first( act->private_data->model, &iter );
601         while ( valid ) {
602             gchar* str = 0;
603             gtk_tree_model_get( act->private_data->model, &iter,
604                                 act->private_data->labelColumn, &str,
605                                 -1 );
607             GtkWidget *item = gtk_radio_menu_item_new_with_label( group, str );
608             group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item) );
609             gtk_menu_shell_append( GTK_MENU_SHELL(subby), item );
610             g_object_set_qdata( G_OBJECT(item), gDataName, act );
612             gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(item), index == act->private_data->active );
614             g_free(str);
615             str = 0;
617             g_signal_connect( G_OBJECT(item), "toggled", G_CALLBACK(menu_toggled_cb), GINT_TO_POINTER(index) );
619             index++;
620             valid = gtk_tree_model_iter_next( act->private_data->model, &iter );
621         }
623         gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), subby );
624         gtk_widget_show_all( subby );
626         g_free(sss);
627     } else {
628         item = gParentClass->create_menu_item( action );
629     }
631     return item;
635 void ege_select_one_action_set_radio_action_type( EgeSelectOneAction* action, GType radioActionType )
637     (void)action;
639     if ( g_type_is_a( radioActionType, GTK_TYPE_RADIO_ACTION ) ) {
640         action->private_data->radioActionType = radioActionType;
641     } else {
642         g_warning("Passed in type '%s' is not derived from '%s'", g_type_name(radioActionType), g_type_name(GTK_TYPE_RADIO_ACTION) );
643     }
646 GtkWidget* create_tool_item( GtkAction* action )
648     GtkWidget* item = 0;
650     if ( IS_EGE_SELECT_ONE_ACTION(action) && EGE_SELECT_ONE_ACTION(action)->private_data->model )
651     {
652         EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(action);
653         item = GTK_WIDGET( gtk_tool_item_new() );
655         if ( act->private_data->appearanceMode == APPEARANCE_FULL ) {
656             GtkWidget* holder = gtk_hbox_new( FALSE, 0 );
658             GtkRadioAction* ract = 0;
659             GtkWidget* sub = 0;
660             GSList* group = 0;
661             GtkTreeIter iter;
662             gboolean valid = FALSE;
663             gint index = 0;
664             GtkTooltips* tooltips = gtk_tooltips_new();
666             gchar*  sss = 0;
667             g_object_get( G_OBJECT(action), "short_label", &sss, NULL );
668             // If short_label not defined, g_object_get will return label.
669             // This hack allows a label to be used with a drop-down menu when
670             // no label is used with a set of icons that are self-explanatory.
671             if (sss && strcmp( sss, "NotUsed" ) != 0 ) {
672                 GtkWidget* lbl;
673                 lbl = gtk_label_new(sss);
674                 gtk_box_pack_start( GTK_BOX(holder), lbl, FALSE, FALSE, 4 );
675             }
677             valid = gtk_tree_model_get_iter_first( act->private_data->model, &iter );
678             while ( valid ) {
679                 gchar* str = 0;
680                 gchar* tip = 0;
681                 gchar* iconId = 0;
682                 gboolean sens = true;
683                 /*
684                 gint size = 0;
685                 */
686                 gtk_tree_model_get( act->private_data->model, &iter,
687                                     act->private_data->labelColumn, &str,
688                                     -1 );
689                 if ( act->private_data->iconColumn >= 0 ) {
690                     gtk_tree_model_get( act->private_data->model, &iter,
691                                         act->private_data->iconColumn, &iconId,
692                                         -1 );
693                 }
694                 if ( act->private_data->tooltipColumn >= 0 ) {
695                     gtk_tree_model_get( act->private_data->model, &iter,
696                                         act->private_data->tooltipColumn, &tip,
697                                         -1 );
698                 }
699                 if ( act->private_data->sensitiveColumn >= 0 ) {
700                     gtk_tree_model_get( act->private_data->model, &iter,
701                                         act->private_data->sensitiveColumn, &sens,
702                                         -1 );
703                 }
705                 if ( act->private_data->radioActionType ) {
706                     void* obj = g_object_new( act->private_data->radioActionType,
707                                               "name", "Name 1",
708                                               "label", str,
709                                               "tooltip", tip,
710                                               "value", index,
711                                               /*
712                                               "iconId", iconId,
713                                               "iconSize", size,
714                                               */
715                                               NULL );
716                     if ( iconId ) {
717                         g_object_set( G_OBJECT(obj), act->private_data->iconProperty, iconId, NULL );
718                     }
720                     if ( act->private_data->iconProperty ) {
721                         /* TODO get this string to be set instead of hardcoded */
722                         if ( act->private_data->iconSize >= 0 ) {
723                             g_object_set( G_OBJECT(obj), "iconSize", act->private_data->iconSize, NULL );
724                         }
725                     }
727                     ract = GTK_RADIO_ACTION(obj);
728                 } else {
729                     ract = gtk_radio_action_new( "Name 1", str, tip, iconId, index );
730                 }
732                 if ( act->private_data->sensitiveColumn >= 0 ) {
733                     gtk_action_set_sensitive( GTK_ACTION(ract), sens );
734                 }
736                 gtk_radio_action_set_group( ract, group );
737                 group = gtk_radio_action_get_group( ract );
739                 if ( index == act->private_data->active ) {
740                     gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(ract), TRUE );
741                 }
742                 g_signal_connect( G_OBJECT(ract), "changed", G_CALLBACK( proxy_action_chagned_cb ), act );
744                 sub = gtk_action_create_tool_item( GTK_ACTION(ract) );
745                 gtk_action_connect_proxy( GTK_ACTION(ract), sub );
746                 gtk_tool_item_set_tooltip( GTK_TOOL_ITEM(sub), tooltips, tip, NULL );
748                 gtk_box_pack_start( GTK_BOX(holder), sub, FALSE, FALSE, 0 );
750                 g_free( str );
751                 g_free( tip );
752                 g_free( iconId );
754                 index++;
755                 valid = gtk_tree_model_iter_next( act->private_data->model, &iter );
756             }
758             g_object_set_data( G_OBJECT(holder), "ege-proxy_action-group", group );
759             g_object_set_data( G_OBJECT(holder), "ege-tooltips", tooltips );
761             gtk_container_add( GTK_CONTAINER(item), holder );
762         } else {
763             GtkCellRenderer * renderer = 0;
764             GtkWidget *holder = gtk_hbox_new( FALSE, 4 );
765             GtkEntry *entry = 0;
766             GtkWidget *normal = (act->private_data->selectionMode == SELECTION_OPEN) ?
767                 gtk_combo_box_entry_new_with_model( act->private_data->model, act->private_data->labelColumn ) :
768                 gtk_combo_box_new_with_model( act->private_data->model );
769             if ((act->private_data->selectionMode == SELECTION_OPEN)) {
770                 GtkWidget *child = gtk_bin_get_child( GTK_BIN(normal) );
771                 if (GTK_IS_ENTRY(child)) {
772                     int maxUsed = scan_max_width( act->private_data->model, act->private_data->labelColumn );
773                     GtkEntryCompletion *complete = 0;
774                     entry = GTK_ENTRY(child);
775                     gtk_entry_set_width_chars(entry, maxUsed); /* replace with property */
777                     complete = gtk_entry_completion_new();
778                     gtk_entry_completion_set_model( complete, act->private_data->model );
779                     gtk_entry_completion_set_text_column( complete, act->private_data->labelColumn );
780                     gtk_entry_completion_set_inline_completion( complete, FALSE );
781                     gtk_entry_completion_set_inline_selection( complete, FALSE );
782                     gtk_entry_completion_set_popup_completion( complete, TRUE );
783                     gtk_entry_completion_set_popup_set_width( complete, FALSE );
784                     gtk_entry_set_completion( entry, complete );
786                     g_signal_connect( G_OBJECT(child), "activate", G_CALLBACK(combo_entry_changed_cb), act );
787                     g_signal_connect( G_OBJECT(child), "focus-out-event", G_CALLBACK(combo_entry_focus_lost_cb), act );
788                 }
789             } else {
790                 if ( act->private_data->iconColumn >= 0 ) {
791                     renderer = gtk_cell_renderer_pixbuf_new();
792                     gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(normal), renderer, TRUE );
794                     /* "icon-name" */
795                     gtk_cell_layout_add_attribute( GTK_CELL_LAYOUT(normal), renderer, "stock-id", act->private_data->iconColumn );
796                 }
798                 renderer = gtk_cell_renderer_text_new();
799                 gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(normal), renderer, TRUE );
800                 gtk_cell_layout_add_attribute( GTK_CELL_LAYOUT(normal), renderer, "text", act->private_data->labelColumn );
801             }
803             gtk_combo_box_set_active( GTK_COMBO_BOX(normal), act->private_data->active );
804             if ( entry && (act->private_data->active == -1) ) {
805                 gtk_entry_set_text( entry, act->private_data->activeText );
806             }
808             g_signal_connect( G_OBJECT(normal), "changed", G_CALLBACK(combo_changed_cb), action );
810             g_object_set_data( G_OBJECT(holder), "ege-combo-box", normal );
812             if (act->private_data->appearanceMode == APPEARANCE_COMPACT) {
813                 gchar*  sss = 0;
814                 g_object_get( G_OBJECT(action), "short_label", &sss, NULL );
815                 if (sss) {
816                     GtkWidget* lbl;
817                     lbl = gtk_label_new(sss);
818                     gtk_box_pack_start( GTK_BOX(holder), lbl, FALSE, FALSE, 4 );
819                 }
820             }
822             gtk_box_pack_start( GTK_BOX(holder), normal, FALSE, FALSE, 0 );
824             {
825                 GtkWidget *align = gtk_alignment_new(0, 0.5, 0, 0);
826                 gtk_container_add( GTK_CONTAINER(align), holder);
827                 gtk_container_add( GTK_CONTAINER(item), align );
828             }
829         }
831         gtk_widget_show_all( item );
832     } else {
833         item = gParentClass->create_tool_item( action );
834     }
836     return item;
840 void connect_proxy( GtkAction *action, GtkWidget *proxy )
842     gParentClass->connect_proxy( action, proxy );
845 void disconnect_proxy( GtkAction *action, GtkWidget *proxy )
847     gParentClass->disconnect_proxy( action, proxy );
851 void resync_active( EgeSelectOneAction* act, gint active, gboolean override )
853     if ( override || (act->private_data->active != active) ) {
854         act->private_data->active = active;
855         GSList* proxies = gtk_action_get_proxies( GTK_ACTION(act) );
856         while ( proxies ) {
857             if ( GTK_IS_TOOL_ITEM(proxies->data) ) {
858                 /* Search for the things we built up in create_tool_item() */
859                 GList* children = gtk_container_get_children( GTK_CONTAINER(proxies->data) );
860                 if ( children && children->data ) {
861                     gpointer combodata = g_object_get_data( G_OBJECT(children->data), "ege-combo-box" );
862                     if (!combodata && GTK_IS_ALIGNMENT(children->data)) {
863                         GList *other = gtk_container_get_children( GTK_CONTAINER(children->data) );
864                          combodata = g_object_get_data( G_OBJECT(other->data), "ege-combo-box" );
865                     }
866                     if ( GTK_IS_COMBO_BOX(combodata) ) {
867                         GtkComboBox* combo = GTK_COMBO_BOX(combodata);
868                         if ((active == -1) && (GTK_IS_COMBO_BOX_ENTRY(combo))) {
869                             gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))), act->private_data->activeText);
870                         } else if ( gtk_combo_box_get_active(combo) != active ) {
871                             gtk_combo_box_set_active( combo, active );
872                         }
873                     } else if ( GTK_IS_HBOX(children->data) ) {
874                         gpointer data = g_object_get_data( G_OBJECT(children->data), "ege-proxy_action-group" );
875                         if ( data ) {
876                             GSList* group = (GSList*)data;
877                             GtkRadioAction* oneAction = GTK_RADIO_ACTION(group->data);
878                             gint hot = gtk_radio_action_get_current_value( oneAction );
879                             if ( hot != active ) {
880                                 /*gtk_radio_action_set_current_value( oneAction, active );*/
881                                 gint value = 0;
882                                 while ( group ) {
883                                     GtkRadioAction* possible = GTK_RADIO_ACTION(group->data);
884                                     g_object_get( G_OBJECT(possible), "value", &value, NULL );
885                                     if ( value == active ) {
886                                         /* Found the group member to set active */
887                                         gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(possible), TRUE );
888                                         break;
889                                     }
891                                     group = g_slist_next(group);
892                                 }
893                             }
894                         }
895                     }
896                 }
897             } else if ( GTK_IS_MENU_ITEM(proxies->data) ) {
898                 GtkWidget* subMenu = gtk_menu_item_get_submenu( GTK_MENU_ITEM(proxies->data) );
899                 GList* children = gtk_container_get_children( GTK_CONTAINER(subMenu) );
900                 if ( children && (g_list_length(children) > (guint)active) ) {
901                     gpointer data = g_list_nth_data( children, active );
902                     gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(data), TRUE );
903                 }
904             }
906             proxies = g_slist_next( proxies );
907         }
909         g_signal_emit( G_OBJECT(act), signals[CHANGED], 0);
910     }
913 void resync_sensitive( EgeSelectOneAction* act )
915     GSList* proxies = gtk_action_get_proxies( GTK_ACTION(act) );
916     while ( proxies ) {
917         if ( GTK_IS_TOOL_ITEM(proxies->data) ) {
918             /* Search for the things we built up in create_tool_item() */
919             GList* children = gtk_container_get_children( GTK_CONTAINER(proxies->data) );
920             if ( children && children->data ) {
921                 gpointer combodata = g_object_get_data( G_OBJECT(children->data), "ege-combo-box" );
922                 if (!combodata && GTK_IS_ALIGNMENT(children->data)) {
923                     GList *other = gtk_container_get_children( GTK_CONTAINER(children->data) );
924                     combodata = g_object_get_data( G_OBJECT(other->data), "ege-combo-box" );
925                 }
926                 if ( GTK_IS_COMBO_BOX(combodata) ) {
927                     /* Not implemented */
928                 } else if ( GTK_IS_HBOX(children->data) ) {
929                     gpointer data = g_object_get_data( G_OBJECT(children->data), "ege-proxy_action-group" );
930                     if ( data ) {
931                         GSList* group = (GSList*)data;
932                         // List is backwards in group as compared to GtkTreeModel, we better do matching.
933                         while ( group ) {
934 #if GTK_CHECK_VERSION(2,16,0)
935                             GtkRadioAction* ract = GTK_RADIO_ACTION(group->data);
936                             const gchar* label = gtk_action_get_label( GTK_ACTION( ract ) );
938                             // Search for matching GtkTreeModel entry
939                             GtkTreeIter iter;
940                             gboolean valid;
941                             valid = gtk_tree_model_get_iter_first( act->private_data->model, &iter );
942                             gboolean sens = true;
944                             while( valid ) {
946                                 gchar* str = 0;
947                                 gtk_tree_model_get( act->private_data->model, &iter,
948                                                     act->private_data->labelColumn, &str,
949                                                     -1 );
951                                 if( strcmp( label, str ) == 0 ) {
952                                     gtk_tree_model_get( act->private_data->model, &iter,
953                                                         act->private_data->sensitiveColumn, &sens,
954                                                         -1 );
955                                     break;
956                                 }
957                                 g_free( str );
959                                 valid = gtk_tree_model_iter_next( act->private_data->model, &iter );
960                             }
962                             gtk_action_set_sensitive( GTK_ACTION(ract), sens );
963 #endif
965                             group = g_slist_next(group);
966                         }
967                     }
968                 }
969             }
970         } else if ( GTK_IS_MENU_ITEM(proxies->data) ) {
971             /* Not implemented */
972         }
974         proxies = g_slist_next( proxies );
975     }
977     g_signal_emit( G_OBJECT(act), signals[CHANGED], 0);
980 void combo_changed_cb( GtkComboBox* widget, gpointer user_data )
982     EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(user_data);
983     gint newActive = gtk_combo_box_get_active(widget);
984     gchar *text = gtk_combo_box_get_active_text(widget);
986     if (newActive == -1) {
987         /* indicates the user is entering text for a custom aka "open" value */
988         if (act->private_data->pendingText && text && (strcmp(act->private_data->pendingText, text) == 0) ) {
989             /* The currently entered data matches the last seen */
990         } else {
991             if (act->private_data->pendingText) {
992                 g_free(act->private_data->pendingText);
993             }
994             act->private_data->pendingText = text;
995             text = 0;
996         }
997     } else if (newActive != act->private_data->active) {
998         if (act->private_data->pendingText) {
999             g_free(act->private_data->pendingText);
1000             act->private_data->pendingText = 0;
1001         }
1002         g_object_set( G_OBJECT(act), "active", newActive, NULL );
1003     }
1005     if (text) {
1006         g_free(text);
1007         text = 0;
1008     }
1011 gboolean combo_entry_focus_lost_cb( GtkWidget *widget, GdkEventFocus *event, gpointer data )
1013     EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(data);
1014     (void)widget;
1015     (void)event;
1017     commit_pending_change(act);
1019     return FALSE;
1022 void combo_entry_changed_cb( GtkEntry* widget, gpointer user_data )
1024     EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(user_data);
1025     (void)widget;
1026     commit_pending_change(act);
1029 void commit_pending_change(EgeSelectOneAction *act)
1031     if (act->private_data->pendingText) {
1032         if (act->private_data->activeText && (strcmp(act->private_data->pendingText, act->private_data->activeText) == 0)) {
1033             /* Was the same value */
1034             g_free(act->private_data->pendingText);
1035             act->private_data->pendingText = 0;
1036         } else {
1037             gint matching = find_text_index(act, act->private_data->pendingText);
1039             if (act->private_data->activeText) {
1040                 g_free(act->private_data->activeText);
1041             }
1042             act->private_data->activeText = act->private_data->pendingText;
1043             act->private_data->pendingText = 0;
1045             if (matching >= 0) {
1046                 g_free(act->private_data->activeText);
1047                 act->private_data->activeText = 0;
1048                 g_object_set( G_OBJECT(act), "active", matching, NULL );
1049             } else if (act->private_data->active != -1) {
1050                 g_object_set( G_OBJECT(act), "active", -1, NULL );
1051             } else {
1052                 resync_active( act, -1, TRUE );
1053             }
1054         }
1055     }
1058 gint find_text_index(EgeSelectOneAction *act, gchar const* text)
1060     gint index = -1;
1062     if (text) {
1063         GtkTreeIter iter;
1064         gboolean valid = gtk_tree_model_get_iter_first( act->private_data->model, &iter );
1065         gint curr = 0;
1066         while ( valid && (index < 0) ) {
1067             gchar* str = 0;
1068             gtk_tree_model_get( act->private_data->model, &iter,
1069                                 act->private_data->labelColumn, &str,
1070                                 -1 );
1072             if (str && (strcmp(text, str) == 0)) {
1073                 index = curr;
1074             }
1076             g_free(str);
1077             str = 0;
1079             curr++;
1080             valid = gtk_tree_model_iter_next( act->private_data->model, &iter );
1081         }
1082     }
1084     return index;
1087 void menu_toggled_cb( GtkWidget* obj, gpointer data )
1089     GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(obj);
1090     EgeSelectOneAction* act = (EgeSelectOneAction*)g_object_get_qdata( G_OBJECT(obj), gDataName );
1091     gint newActive = GPOINTER_TO_INT(data);
1092     if ( gtk_check_menu_item_get_active(item) && (newActive != act->private_data->active) ) {
1093         g_object_set( G_OBJECT(act), "active", newActive, NULL );
1094     }
1097 void proxy_action_chagned_cb( GtkRadioAction* action, GtkRadioAction* current, gpointer user_data )
1099     (void)current;
1100     if ( gtk_toggle_action_get_active( GTK_TOGGLE_ACTION(action) ) ) {
1101         EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(user_data);
1102         gint newActive = gtk_radio_action_get_current_value( action );
1103         if ( newActive != act->private_data->active ) {
1104             g_object_set( G_OBJECT(act), "active", newActive, NULL );
1105         }
1106     }
1109 int scan_max_width( GtkTreeModel *model, gint labelColumn )
1111     int maxUsed = 0;
1112     GtkTreeIter iter;
1113     gboolean valid = gtk_tree_model_get_iter_first( model, &iter );
1114     while ( valid ) {
1115         gchar* str = 0;
1116         int count = 0;
1117         gtk_tree_model_get( model, &iter,
1118                             labelColumn, &str,
1119                             -1 );
1120         count = strlen(str);
1121         if (count > maxUsed) {
1122             maxUsed = count;
1123         }
1124         g_free( str );
1126         valid = gtk_tree_model_iter_next( model, &iter );
1127     }
1128     return maxUsed;