Code

Added completion entry for editable lists.
[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 int scan_max_width( GtkTreeModel *model, gint labelColumn );
83 static GtkActionClass* gParentClass = 0;
84 static guint signals[LAST_SIGNAL] = {0};
85 static GQuark gDataName = 0;
88 enum {
89     APPEARANCE_UNKNOWN = -1,
90     APPEARANCE_NONE = 0,
91     APPEARANCE_FULL,    /* label, then all choices represented by separate buttons */
92     APPEARANCE_COMPACT, /* label, then choices in a drop-down menu */
93     APPEARANCE_MINIMAL, /* no label, just choices in a drop-down menu */
94 };
96 enum {
97     SELECTION_UNKNOWN = -1,
98     SELECTION_CLOSED = 0,
99     SELECTION_OPEN,
100 };
102 struct _EgeSelectOneActionPrivate
104     gint active;
105     gint labelColumn;
106     gint iconColumn;
107     gint tooltipColumn;
108     gint appearanceMode;
109     gint selectionMode;
110     gint iconSize;
111     GType radioActionType;
112     GtkTreeModel* model;
113     gchar *iconProperty;
114     gchar *appearance;
115     gchar *selection;
116     gchar *activeText;
117     gchar *pendingText;
118 };
120 #define EGE_SELECT_ONE_ACTION_GET_PRIVATE( o ) ( G_TYPE_INSTANCE_GET_PRIVATE( (o), EGE_SELECT_ONE_ACTION_TYPE, EgeSelectOneActionPrivate ) )
122 enum {
123     PROP_MODEL = 1,
124     PROP_ACTIVE,
125     PROP_LABEL_COLUMN,
126     PROP_ICON_COLUMN,
127     PROP_TOOLTIP_COLUMN,
128     PROP_ICON_PROP,
129     PROP_ICON_SIZE,
130     PROP_APPEARANCE,
131     PROP_SELECTION
132 };
134 GType ege_select_one_action_get_type( void )
136     static GType myType = 0;
137     if ( !myType ) {
138         static const GTypeInfo myInfo = {
139             sizeof( EgeSelectOneActionClass ),
140             NULL, /* base_init */
141             NULL, /* base_finalize */
142             (GClassInitFunc)ege_select_one_action_class_init,
143             NULL, /* class_finalize */
144             NULL, /* class_data */
145             sizeof( EgeSelectOneAction ),
146             0, /* n_preallocs */
147             (GInstanceInitFunc)ege_select_one_action_init,
148             NULL
149         };
151         myType = g_type_register_static( GTK_TYPE_ACTION, "EgeSelectOneAction", &myInfo, (GTypeFlags)0 );
152     }
154     return myType;
157 GtkTreeModel *ege_select_one_action_get_model(EgeSelectOneAction* action ){
158     return GTK_TREE_MODEL(action->private_data->model);
160 void ege_select_one_action_class_init( EgeSelectOneActionClass* klass )
162     if ( klass ) {
163         gParentClass = GTK_ACTION_CLASS( g_type_class_peek_parent( klass ) );
164         GObjectClass* objClass = G_OBJECT_CLASS( klass );
166         gDataName = g_quark_from_string("ege-select1-action");
168         objClass->get_property = ege_select_one_action_get_property;
169         objClass->set_property = ege_select_one_action_set_property;
171         klass->parent_class.create_menu_item = create_menu_item;
172         klass->parent_class.create_tool_item = create_tool_item;
173         klass->parent_class.connect_proxy = connect_proxy;
174         klass->parent_class.disconnect_proxy = disconnect_proxy;
176         g_object_class_install_property( objClass,
177                                          PROP_MODEL,
178                                          g_param_spec_object( "model",
179                                                               "Tree Model",
180                                                               "Tree model of possible items",
181                                                               GTK_TYPE_TREE_MODEL,
182                                                               (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
184         g_object_class_install_property( objClass,
185                                          PROP_ACTIVE,
186                                          g_param_spec_int( "active",
187                                                            "Active Selection",
188                                                            "The index of the selected item",
189                                                            -1, 20, 0,
190                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
192         g_object_class_install_property( objClass,
193                                          PROP_LABEL_COLUMN,
194                                          g_param_spec_int( "label-column",
195                                                            "Display Column",
196                                                            "The column of the model that holds display strings",
197                                                            0, 20, 0,
198                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
200         g_object_class_install_property( objClass,
201                                          PROP_ICON_COLUMN,
202                                          g_param_spec_int( "icon-column",
203                                                            "Icon Column",
204                                                            "The column of the model that holds display icon name",
205                                                            -1, 20, -1,
206                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
208         g_object_class_install_property( objClass,
209                                          PROP_TOOLTIP_COLUMN,
210                                          g_param_spec_int( "tooltip-column",
211                                                            "Tooltip Column",
212                                                           "The column of the model that holds tooltip strings",
213                                                            -1, 20, -1,
214                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
216         g_object_class_install_property( objClass,
217                                          PROP_ICON_PROP,
218                                          g_param_spec_string( "icon-property",
219                                                               "Icon Property",
220                                                               "Target icon property",
221                                                               "",
222                                                               (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
224         g_object_class_install_property( objClass,
225                                          PROP_ICON_SIZE,
226                                          g_param_spec_int( "icon-size",
227                                                            "Icon Size",
228                                                           "Target icon size",
229                                                            -1, 20, -1,
230                                                            (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
232         g_object_class_install_property( objClass,
233                                          PROP_APPEARANCE,
234                                          g_param_spec_string( "appearance",
235                                                               "Appearance hint",
236                                                               "A hint for how to display",
237                                                               "",
238                                                               (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
240         g_object_class_install_property( objClass,
241                                          PROP_SELECTION,
242                                          g_param_spec_string( "selection",
243                                                               "Selection set open or closed",
244                                                               "'open' to allow edits/additions, 'closed' to disallow.",
245                                                               "",
246                                                               (GParamFlags)(G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT) ) );
248         signals[CHANGED] = g_signal_new( "changed",
249                                          G_TYPE_FROM_CLASS(klass),
250                                          G_SIGNAL_RUN_FIRST,
251                                          G_STRUCT_OFFSET(EgeSelectOneActionClass, changed),
252                                          NULL, NULL,
253                                          g_cclosure_marshal_VOID__VOID,
254                                          G_TYPE_NONE, 0);
256         g_type_class_add_private( klass, sizeof(EgeSelectOneActionClass) );
257     }
261 void ege_select_one_action_init( EgeSelectOneAction* action )
263     action->private_data = EGE_SELECT_ONE_ACTION_GET_PRIVATE( action );
264     action->private_data->active = 0;
265     action->private_data->labelColumn = 0;
266     action->private_data->iconColumn = -1;
267     action->private_data->tooltipColumn = -1;
268     action->private_data->appearanceMode = APPEARANCE_NONE;
269     action->private_data->selectionMode = SELECTION_CLOSED;
270     action->private_data->radioActionType = 0;
271     action->private_data->model = 0;
272     action->private_data->iconProperty = g_strdup("stock-id");
273     action->private_data->iconSize = -1;
274     action->private_data->appearance = 0;
275     action->private_data->selection = 0;
276     action->private_data->activeText = 0;
277     action->private_data->pendingText = 0;
279 /*     g_signal_connect( action, "notify", G_CALLBACK( fixup_labels ), NULL ); */
282 EgeSelectOneAction* ege_select_one_action_new( const gchar *name,
283                                                const gchar *label,
284                                                const gchar *tooltip,
285                                                const gchar *stock_id,
286                                                GtkTreeModel* model )
288     GObject* obj = (GObject*)g_object_new( EGE_SELECT_ONE_ACTION_TYPE,
289                                            "name", name,
290                                            "label", label,
291                                            "tooltip", tooltip,
292                                            "stock_id", stock_id,
293                                            "model", model,
294                                            "active", 0,
295                                            "icon-property", "stock-id",
296                                            NULL );
298     EgeSelectOneAction* action = EGE_SELECT_ONE_ACTION( obj );
300     return action;
304 gint ege_select_one_action_get_active( EgeSelectOneAction* action )
306     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
307     return action->private_data->active;
310 gchar *ege_select_one_action_get_active_text( EgeSelectOneAction* action )
312     GtkTreeIter iter;
313     gchar *str = 0;
314     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
316     if ( action->private_data->active >= 0) {
317         if ( gtk_tree_model_iter_nth_child( action->private_data->model, &iter, NULL, action->private_data->active ) ) {
318             gtk_tree_model_get( action->private_data->model, &iter,
319                                 action->private_data->labelColumn, &str,
320                                 -1 );
321         }
322     } else if ( (action->private_data->active == -1) && action->private_data->activeText ) {
323         str = g_strdup(action->private_data->activeText);
324     }
326     return str;
329 void ege_select_one_action_set_active_text( EgeSelectOneAction* action, gchar const *text )
331     g_return_if_fail( IS_EGE_SELECT_ONE_ACTION(action) );
333     if (action->private_data->activeText) {
334         g_free( action->private_data->activeText );
335     }
336     action->private_data->activeText = g_strdup(text);
338     if (action->private_data->active != -1) {
339         g_object_set( G_OBJECT(action), "active", -1, NULL );
340     } else {
341         resync_active( action, -1, TRUE );
342     }
345 void ege_select_one_action_set_active( EgeSelectOneAction* action, gint val )
347     g_object_set( G_OBJECT(action), "active", val, NULL );
350 gint ege_select_one_action_get_label_column( EgeSelectOneAction* action )
352     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
353     return action->private_data->labelColumn;
356 void ege_select_one_action_set_label_column( EgeSelectOneAction* action, gint col )
358     g_object_set( G_OBJECT(action), "label-column", col, NULL );
361 gint ege_select_one_action_get_icon_column( EgeSelectOneAction* action )
363     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
364     return action->private_data->iconColumn;
367 void ege_select_one_action_set_icon_column( EgeSelectOneAction* action, gint col )
369     g_object_set( G_OBJECT(action), "icon-column", col, NULL );
372 gint ege_select_one_action_get_icon_size( EgeSelectOneAction* action )
374     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
375     return action->private_data->iconSize;
378 void ege_select_one_action_set_icon_size( EgeSelectOneAction* action, gint size )
380     g_object_set( G_OBJECT(action), "icon-size", size, NULL );
383 gint ege_select_one_action_get_tooltip_column( EgeSelectOneAction* action )
385     g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
386     return action->private_data->tooltipColumn;
389 void ege_select_one_action_set_tooltip_column( EgeSelectOneAction* action, gint col )
391     g_object_set( G_OBJECT(action), "tooltip-column", col, NULL );
394 void ege_select_one_action_set_appearance( EgeSelectOneAction* action, gchar const* val )
396     g_object_set( G_OBJECT(action), "appearance", val, NULL );
399 void ege_select_one_action_set_selection( EgeSelectOneAction* action, gchar const* val )
401     g_object_set( G_OBJECT(action), "selection", val, NULL );
404 void ege_select_one_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec )
406     EgeSelectOneAction* action = EGE_SELECT_ONE_ACTION( obj );
407     switch ( propId ) {
408         case PROP_MODEL:
409             g_value_set_object( value, action->private_data->model );
410             break;
412         case PROP_ACTIVE:
413             g_value_set_int( value, action->private_data->active );
414             break;
416         case PROP_LABEL_COLUMN:
417             g_value_set_int( value, action->private_data->labelColumn );
418             break;
420         case PROP_ICON_COLUMN:
421             g_value_set_int( value, action->private_data->iconColumn );
422             break;
424         case PROP_TOOLTIP_COLUMN:
425             g_value_set_int( value, action->private_data->tooltipColumn );
426             break;
428         case PROP_ICON_PROP:
429             g_value_set_string( value, action->private_data->iconProperty );
430             break;
432         case PROP_ICON_SIZE:
433             g_value_set_int( value, action->private_data->iconSize );
434             break;
436         case PROP_APPEARANCE:
437             g_value_set_string( value, action->private_data->appearance );
438             break;
440         case PROP_SELECTION:
441             g_value_set_string( value, action->private_data->selection );
442             break;
444         default:
445             G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
446     }
449 void ege_select_one_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec )
451     EgeSelectOneAction* action = EGE_SELECT_ONE_ACTION( obj );
452     switch ( propId ) {
453         case PROP_MODEL:
454         {
455             action->private_data->model = GTK_TREE_MODEL( g_value_get_object( value ) );
456         }
457         break;
459         case PROP_ACTIVE:
460         {
461             resync_active( action, g_value_get_int( value ), FALSE );
462         }
463         break;
465         case PROP_LABEL_COLUMN:
466         {
467             action->private_data->labelColumn = g_value_get_int( value );
468         }
469         break;
471         case PROP_ICON_COLUMN:
472         {
473             action->private_data->iconColumn = g_value_get_int( value );
474         }
475         break;
477         case PROP_TOOLTIP_COLUMN:
478         {
479             action->private_data->tooltipColumn = g_value_get_int( value );
480         }
481         break;
483         case PROP_ICON_PROP:
484         {
485             gchar* tmp = action->private_data->iconProperty;
486             gchar* newVal = g_value_dup_string( value );
487             action->private_data->iconProperty = newVal;
488             g_free( tmp );
489         }
490         break;
492         case PROP_ICON_SIZE:
493         {
494             action->private_data->iconSize = g_value_get_int( value );
495         }
496         break;
498         case PROP_APPEARANCE:
499         {
500             gchar* tmp = action->private_data->appearance;
501             gchar* newVal = g_value_dup_string( value );
502             action->private_data->appearance = newVal;
503             g_free( tmp );
505             if ( !action->private_data->appearance || (strcmp("", newVal) == 0) ) {
506                 action->private_data->appearanceMode = APPEARANCE_NONE;
507             } else if ( strcmp("full", newVal) == 0 ) {
508                 action->private_data->appearanceMode = APPEARANCE_FULL;
509             } else if ( strcmp("compact", newVal) == 0 ) {
510                 action->private_data->appearanceMode = APPEARANCE_COMPACT;
511             } else if ( strcmp("minimal", newVal) == 0 ) {
512                 action->private_data->appearanceMode = APPEARANCE_MINIMAL;
513             } else {
514                 action->private_data->appearanceMode = APPEARANCE_UNKNOWN;
515             }
516         }
517         break;
519         case PROP_SELECTION:
520         {
521             gchar* tmp = action->private_data->selection;
522             gchar* newVal = g_value_dup_string( value );
523             action->private_data->selection = newVal;
524             g_free( tmp );
526             if ( !action->private_data->selection || (strcmp("closed", newVal) == 0) ) {
527                 action->private_data->selectionMode = SELECTION_CLOSED;
528             } else if ( strcmp("open", newVal) == 0 ) {
529                 action->private_data->selectionMode = SELECTION_OPEN;
530             } else {
531                 action->private_data->selectionMode = SELECTION_UNKNOWN;
532             }
533         }
534         break;
536         default:
537             G_OBJECT_WARN_INVALID_PROPERTY_ID( obj, propId, pspec );
538     }
541 GtkWidget* create_menu_item( GtkAction* action )
543     GtkWidget* item = 0;
545     if ( IS_EGE_SELECT_ONE_ACTION(action) ) {
546         EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION( action );
547         gchar*  sss = 0;
548         gboolean valid = FALSE;
549         gint index = 0;
550         GtkTreeIter iter;
551         GSList* group = 0;
552         GtkWidget* subby = gtk_menu_new();
554         g_object_get( G_OBJECT(action), "label", &sss, NULL );
556         item = gtk_menu_item_new_with_label( sss );
558         valid = gtk_tree_model_get_iter_first( act->private_data->model, &iter );
559         while ( valid ) {
560             gchar* str = 0;
561             gtk_tree_model_get( act->private_data->model, &iter,
562                                 act->private_data->labelColumn, &str,
563                                 -1 );
565             GtkWidget *item = gtk_radio_menu_item_new_with_label( group, str );
566             group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item) );
567             gtk_menu_shell_append( GTK_MENU_SHELL(subby), item );
568             g_object_set_qdata( G_OBJECT(item), gDataName, act );
570             gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(item), index == act->private_data->active );
572             g_free(str);
573             str = 0;
575             g_signal_connect( G_OBJECT(item), "toggled", G_CALLBACK(menu_toggled_cb), GINT_TO_POINTER(index) );
577             index++;
578             valid = gtk_tree_model_iter_next( act->private_data->model, &iter );
579         }
581         gtk_menu_item_set_submenu( GTK_MENU_ITEM(item), subby );
582         gtk_widget_show_all( subby );
584         g_free(sss);
585     } else {
586         item = gParentClass->create_menu_item( action );
587     }
589     return item;
593 void ege_select_one_action_set_radio_action_type( EgeSelectOneAction* action, GType radioActionType )
595     (void)action;
597     if ( g_type_is_a( radioActionType, GTK_TYPE_RADIO_ACTION ) ) {
598         action->private_data->radioActionType = radioActionType;
599     } else {
600         g_warning("Passed in type '%s' is not derived from '%s'", g_type_name(radioActionType), g_type_name(GTK_TYPE_RADIO_ACTION) );
601     }
604 GtkWidget* create_tool_item( GtkAction* action )
606     GtkWidget* item = 0;
608     if ( IS_EGE_SELECT_ONE_ACTION(action) && EGE_SELECT_ONE_ACTION(action)->private_data->model )
609     {
610         EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(action);
611         item = GTK_WIDGET( gtk_tool_item_new() );
613         if ( act->private_data->appearanceMode == APPEARANCE_FULL ) {
614             GtkWidget* holder = gtk_hbox_new( FALSE, 0 );
616             GtkRadioAction* ract = 0;
617             GtkWidget* sub = 0;
618             GSList* group = 0;
619             GtkTreeIter iter;
620             gboolean valid = FALSE;
621             gint index = 0;
622             GtkTooltips* tooltips = gtk_tooltips_new();
624             gchar*  sss = 0;
625             g_object_get( G_OBJECT(action), "short_label", &sss, NULL );
626             if (sss) {
627                 GtkWidget* lbl;
628                 lbl = gtk_label_new(sss);
629                 gtk_box_pack_start( GTK_BOX(holder), lbl, FALSE, FALSE, 4 );
630             }
632             valid = gtk_tree_model_get_iter_first( act->private_data->model, &iter );
633             while ( valid ) {
634                 gchar* str = 0;
635                 gchar* tip = 0;
636                 gchar* iconId = 0;
637                 /*
638                 gint size = 0;
639                 */
640                 gtk_tree_model_get( act->private_data->model, &iter,
641                                     act->private_data->labelColumn, &str,
642                                     -1 );
643                 if ( act->private_data->iconColumn >= 0 ) {
644                     gtk_tree_model_get( act->private_data->model, &iter,
645                                         act->private_data->iconColumn, &iconId,
646                                         -1 );
647                 }
648                 if ( act->private_data->tooltipColumn >= 0 ) {
649                     gtk_tree_model_get( act->private_data->model, &iter,
650                                         act->private_data->tooltipColumn, &tip,
651                                         -1 );
652                 }
654                 if ( act->private_data->radioActionType ) {
655                     void* obj = g_object_new( act->private_data->radioActionType,
656                                               "name", "Name 1",
657                                               "label", str,
658                                               "tooltip", tip,
659                                               "value", index,
660                                               /*
661                                               "iconId", iconId,
662                                               "iconSize", size,
663                                               */
664                                               NULL );
665                     if ( iconId ) {
666                         g_object_set( G_OBJECT(obj), act->private_data->iconProperty, iconId, NULL );
667                     }
669                     if ( act->private_data->iconProperty ) {
670                         /* TODO get this string to be set instead of hardcoded */
671                         if ( act->private_data->iconSize >= 0 ) {
672                             g_object_set( G_OBJECT(obj), "iconSize", act->private_data->iconSize, NULL );
673                         }
674                     }
676                     ract = GTK_RADIO_ACTION(obj);
677                 } else {
678                     ract = gtk_radio_action_new( "Name 1", str, tip, iconId, index );
679                 }
681                 gtk_radio_action_set_group( ract, group );
682                 group = gtk_radio_action_get_group( ract );
684                 if ( index == act->private_data->active ) {
685                     gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(ract), TRUE );
686                 }
687                 g_signal_connect( G_OBJECT(ract), "changed", G_CALLBACK( proxy_action_chagned_cb ), act );
689                 sub = gtk_action_create_tool_item( GTK_ACTION(ract) );
690                 gtk_action_connect_proxy( GTK_ACTION(ract), sub );
691                 gtk_tool_item_set_tooltip( GTK_TOOL_ITEM(sub), tooltips, tip, NULL );
693                 gtk_box_pack_start( GTK_BOX(holder), sub, FALSE, FALSE, 0 );
695                 g_free( str );
696                 g_free( tip );
697                 g_free( iconId );
699                 index++;
700                 valid = gtk_tree_model_iter_next( act->private_data->model, &iter );
701             }
703             g_object_set_data( G_OBJECT(holder), "ege-proxy_action-group", group );
704             g_object_set_data( G_OBJECT(holder), "ege-tooltips", tooltips );
706             gtk_container_add( GTK_CONTAINER(item), holder );
707         } else {
708             GtkCellRenderer * renderer = 0;
709             GtkWidget *holder = gtk_hbox_new( FALSE, 4 );
710             GtkEntry *entry = 0;
711             GtkWidget *normal = (act->private_data->selectionMode == SELECTION_OPEN) ?
712                 gtk_combo_box_entry_new_with_model( act->private_data->model, act->private_data->labelColumn ) :
713                 gtk_combo_box_new_with_model( act->private_data->model );
714             if ((act->private_data->selectionMode == SELECTION_OPEN)) {
715                 GtkWidget *child = gtk_bin_get_child( GTK_BIN(normal) );
716                 if (GTK_IS_ENTRY(child)) {
717                     int maxUsed = scan_max_width( act->private_data->model, act->private_data->labelColumn );
718                     GtkEntryCompletion *complete = 0;
719                     entry = GTK_ENTRY(child);
720                     gtk_entry_set_width_chars(entry, maxUsed); /* replace with property */
722                     complete = gtk_entry_completion_new();
723                     gtk_entry_completion_set_model( complete, act->private_data->model );
724                     gtk_entry_completion_set_text_column( complete, act->private_data->labelColumn );
725                     gtk_entry_completion_set_inline_completion( complete, FALSE );
726                     gtk_entry_completion_set_inline_selection( complete, FALSE );
727                     gtk_entry_completion_set_popup_completion( complete, TRUE );
728                     gtk_entry_completion_set_popup_set_width( complete, FALSE );
729                     gtk_entry_set_completion( entry, complete );
731                     g_signal_connect( G_OBJECT(child), "activate", G_CALLBACK(combo_entry_changed_cb), act );
732                     g_signal_connect( G_OBJECT(child), "focus-out-event", G_CALLBACK(combo_entry_focus_lost_cb), act );
733                 }
734             } else {
735                 if ( act->private_data->iconColumn >= 0 ) {
736                     renderer = gtk_cell_renderer_pixbuf_new();
737                     gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(normal), renderer, TRUE );
739                     /* "icon-name" */
740                     gtk_cell_layout_add_attribute( GTK_CELL_LAYOUT(normal), renderer, "stock-id", act->private_data->iconColumn );
741                 }
743                 renderer = gtk_cell_renderer_text_new();
744                 gtk_cell_layout_pack_start( GTK_CELL_LAYOUT(normal), renderer, TRUE );
745                 gtk_cell_layout_add_attribute( GTK_CELL_LAYOUT(normal), renderer, "text", act->private_data->labelColumn );
746             }
748             gtk_combo_box_set_active( GTK_COMBO_BOX(normal), act->private_data->active );
749             if ( entry && (act->private_data->active == -1) ) {
750                 gtk_entry_set_text( entry, act->private_data->activeText );
751             }
753             g_signal_connect( G_OBJECT(normal), "changed", G_CALLBACK(combo_changed_cb), action );
755             g_object_set_data( G_OBJECT(holder), "ege-combo-box", normal );
757             if (act->private_data->appearanceMode == APPEARANCE_COMPACT) {
758                 gchar*  sss = 0;
759                 g_object_get( G_OBJECT(action), "short_label", &sss, NULL );
760                 if (sss) {
761                     GtkWidget* lbl;
762                     lbl = gtk_label_new(sss);
763                     gtk_box_pack_start( GTK_BOX(holder), lbl, FALSE, FALSE, 4 );
764                 }
765             }
767             gtk_box_pack_start( GTK_BOX(holder), normal, FALSE, FALSE, 0 );
769             {
770                 GtkWidget *align = gtk_alignment_new(0, 0.5, 0, 0);
771                 gtk_container_add( GTK_CONTAINER(align), holder);
772                 gtk_container_add( GTK_CONTAINER(item), align );
773             }
774         }
776         gtk_widget_show_all( item );
777     } else {
778         item = gParentClass->create_tool_item( action );
779     }
781     return item;
785 void connect_proxy( GtkAction *action, GtkWidget *proxy )
787     gParentClass->connect_proxy( action, proxy );
790 void disconnect_proxy( GtkAction *action, GtkWidget *proxy )
792     gParentClass->disconnect_proxy( action, proxy );
796 void resync_active( EgeSelectOneAction* act, gint active, gboolean override )
798     if ( override || (act->private_data->active != active) ) {
799         act->private_data->active = active;
800         GSList* proxies = gtk_action_get_proxies( GTK_ACTION(act) );
801         while ( proxies ) {
802             if ( GTK_IS_TOOL_ITEM(proxies->data) ) {
803                 /* Search for the things we built up in create_tool_item() */
804                 GList* children = gtk_container_get_children( GTK_CONTAINER(proxies->data) );
805                 if ( children && children->data ) {
806                     gpointer combodata = g_object_get_data( G_OBJECT(children->data), "ege-combo-box" );
807                     if (!combodata && GTK_IS_ALIGNMENT(children->data)) {
808                         GList *other = gtk_container_get_children( GTK_CONTAINER(children->data) );
809                          combodata = g_object_get_data( G_OBJECT(other->data), "ege-combo-box" );
810                     }
811                     if ( GTK_IS_COMBO_BOX(combodata) ) {
812                         GtkComboBox* combo = GTK_COMBO_BOX(combodata);
813                         if ((active == -1) && (GTK_IS_COMBO_BOX_ENTRY(combo))) {
814                             gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))), act->private_data->activeText);
815                         } else if ( gtk_combo_box_get_active(combo) != active ) {
816                             gtk_combo_box_set_active( combo, active );
817                         }
818                     } else if ( GTK_IS_HBOX(children->data) ) {
819                         gpointer data = g_object_get_data( G_OBJECT(children->data), "ege-proxy_action-group" );
820                         if ( data ) {
821                             GSList* group = (GSList*)data;
822                             GtkRadioAction* oneAction = GTK_RADIO_ACTION(group->data);
823                             gint hot = gtk_radio_action_get_current_value( oneAction );
824                             if ( hot != active ) {
825                                 /*gtk_radio_action_set_current_value( oneAction, active );*/
826                                 gint value = 0;
827                                 while ( group ) {
828                                     GtkRadioAction* possible = GTK_RADIO_ACTION(group->data);
829                                     g_object_get( G_OBJECT(possible), "value", &value, NULL );
830                                     if ( value == active ) {
831                                         /* Found the group member to set active */
832                                         gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(possible), TRUE );
833                                         break;
834                                     }
836                                     group = g_slist_next(group);
837                                 }
838                             }
839                         }
840                     }
841                 }
842             } else if ( GTK_IS_MENU_ITEM(proxies->data) ) {
843                 GtkWidget* subMenu = gtk_menu_item_get_submenu( GTK_MENU_ITEM(proxies->data) );
844                 GList* children = gtk_container_get_children( GTK_CONTAINER(subMenu) );
845                 if ( children && (g_list_length(children) > (guint)active) ) {
846                     gpointer data = g_list_nth_data( children, active );
847                     gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(data), TRUE );
848                 }
849             }
851             proxies = g_slist_next( proxies );
852         }
854         g_signal_emit( G_OBJECT(act), signals[CHANGED], 0);
855     }
858 void combo_changed_cb( GtkComboBox* widget, gpointer user_data )
860     EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(user_data);
861     gint newActive = gtk_combo_box_get_active(widget);
862     gchar *text = gtk_combo_box_get_active_text(widget);
864     if (newActive == -1) {
865         /* indicates the user is entering text for a custom aka "open" value */
866         if (act->private_data->pendingText && text && (strcmp(act->private_data->pendingText, text) == 0) ) {
867             /* The currently entered data matches the last seen */
868         } else {
869             if (act->private_data->pendingText) {
870                 g_free(act->private_data->pendingText);
871             }
872             act->private_data->pendingText = text;
873             text = 0;
874         }
875     } else if (newActive != act->private_data->active) {
876         if (act->private_data->pendingText) {
877             g_free(act->private_data->pendingText);
878             act->private_data->pendingText = 0;
879         }
880         g_object_set( G_OBJECT(act), "active", newActive, NULL );
881     }
883     if (text) {
884         g_free(text);
885         text = 0;
886     }
889 gboolean combo_entry_focus_lost_cb( GtkWidget *widget, GdkEventFocus *event, gpointer data )
891     EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(data);
892     (void)widget;
893     (void)event;
895     commit_pending_change(act);
897     return FALSE;
900 void combo_entry_changed_cb( GtkEntry* widget, gpointer user_data )
902     EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(user_data);
903     (void)widget;
904     commit_pending_change(act);
907 void commit_pending_change(EgeSelectOneAction *act)
909     if (act->private_data->pendingText) {
910         if (act->private_data->activeText && (strcmp(act->private_data->pendingText, act->private_data->activeText) == 0)) {
911             /* Was the same value */
912             g_free(act->private_data->pendingText);
913             act->private_data->pendingText = 0;
914         } else {
915             gint matching = find_text_index(act, act->private_data->pendingText);
917             if (act->private_data->activeText) {
918                 g_free(act->private_data->activeText);
919             }
920             act->private_data->activeText = act->private_data->pendingText;
921             act->private_data->pendingText = 0;
923             if (matching >= 0) {
924                 g_free(act->private_data->activeText);
925                 act->private_data->activeText = 0;
926                 g_object_set( G_OBJECT(act), "active", matching, NULL );
927             } else if (act->private_data->active != -1) {
928                 g_object_set( G_OBJECT(act), "active", -1, NULL );
929             } else {
930                 resync_active( act, -1, TRUE );
931             }
932         }
933     }
936 gint find_text_index(EgeSelectOneAction *act, gchar const* text)
938     gint index = -1;
940     if (text) {
941         GtkTreeIter iter;
942         gboolean valid = gtk_tree_model_get_iter_first( act->private_data->model, &iter );
943         gint curr = 0;
944         while ( valid && (index < 0) ) {
945             gchar* str = 0;
946             gtk_tree_model_get( act->private_data->model, &iter,
947                                 act->private_data->labelColumn, &str,
948                                 -1 );
950             if (str && (strcmp(text, str) == 0)) {
951                 index = curr;
952             }
954             g_free(str);
955             str = 0;
957             curr++;
958             valid = gtk_tree_model_iter_next( act->private_data->model, &iter );
959         }
960     }
962     return index;
965 void menu_toggled_cb( GtkWidget* obj, gpointer data )
967     GtkCheckMenuItem* item = GTK_CHECK_MENU_ITEM(obj);
968     EgeSelectOneAction* act = (EgeSelectOneAction*)g_object_get_qdata( G_OBJECT(obj), gDataName );
969     gint newActive = GPOINTER_TO_INT(data);
970     if ( gtk_check_menu_item_get_active(item) && (newActive != act->private_data->active) ) {
971         g_object_set( G_OBJECT(act), "active", newActive, NULL );
972     }
975 void proxy_action_chagned_cb( GtkRadioAction* action, GtkRadioAction* current, gpointer user_data )
977     (void)current;
978     if ( gtk_toggle_action_get_active( GTK_TOGGLE_ACTION(action) ) ) {
979         EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(user_data);
980         gint newActive = gtk_radio_action_get_current_value( action );
981         if ( newActive != act->private_data->active ) {
982             g_object_set( G_OBJECT(act), "active", newActive, NULL );
983         }
984     }
987 int scan_max_width( GtkTreeModel *model, gint labelColumn )
989     int maxUsed = 0;
990     GtkTreeIter iter;
991     gboolean valid = gtk_tree_model_get_iter_first( model, &iter );
992     while ( valid ) {
993         gchar* str = 0;
994         int count = 0;
995         gtk_tree_model_get( model, &iter,
996                             labelColumn, &str,
997                             -1 );
998         count = strlen(str);
999         if (count > maxUsed) {
1000             maxUsed = count;
1001         }
1002         g_free( str );
1004         valid = gtk_tree_model_iter_next( model, &iter );
1005     }
1006     return maxUsed;