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