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
104 {
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 )
138 {
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;
158 }
160 GtkTreeModel *ege_select_one_action_get_model(EgeSelectOneAction* action ){
161 return GTK_TREE_MODEL(action->private_data->model);
162 }
163 void ege_select_one_action_class_init( EgeSelectOneActionClass* klass )
164 {
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 }
269 }
272 void ege_select_one_action_init( EgeSelectOneAction* action )
273 {
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 ); */
292 }
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 )
299 {
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;
313 }
316 gint ege_select_one_action_get_active( EgeSelectOneAction* action )
317 {
318 g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
319 return action->private_data->active;
320 }
322 gchar *ege_select_one_action_get_active_text( EgeSelectOneAction* action )
323 {
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;
339 }
341 void ege_select_one_action_set_active_text( EgeSelectOneAction* action, gchar const *text )
342 {
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 }
355 }
357 void ege_select_one_action_set_active( EgeSelectOneAction* action, gint val )
358 {
359 g_object_set( G_OBJECT(action), "active", val, NULL );
360 }
362 void ege_select_one_action_update_sensitive( EgeSelectOneAction* action )
363 {
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 );
369 }
371 gint ege_select_one_action_get_label_column( EgeSelectOneAction* action )
372 {
373 g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
374 return action->private_data->labelColumn;
375 }
377 void ege_select_one_action_set_label_column( EgeSelectOneAction* action, gint col )
378 {
379 g_object_set( G_OBJECT(action), "label-column", col, NULL );
380 }
382 gint ege_select_one_action_get_icon_column( EgeSelectOneAction* action )
383 {
384 g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
385 return action->private_data->iconColumn;
386 }
388 void ege_select_one_action_set_icon_column( EgeSelectOneAction* action, gint col )
389 {
390 g_object_set( G_OBJECT(action), "icon-column", col, NULL );
391 }
393 gint ege_select_one_action_get_icon_size( EgeSelectOneAction* action )
394 {
395 g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
396 return action->private_data->iconSize;
397 }
399 void ege_select_one_action_set_icon_size( EgeSelectOneAction* action, gint size )
400 {
401 g_object_set( G_OBJECT(action), "icon-size", size, NULL );
402 }
404 gint ege_select_one_action_get_tooltip_column( EgeSelectOneAction* action )
405 {
406 g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
407 return action->private_data->tooltipColumn;
408 }
410 void ege_select_one_action_set_tooltip_column( EgeSelectOneAction* action, gint col )
411 {
412 g_object_set( G_OBJECT(action), "tooltip-column", col, NULL );
413 }
415 gint ege_select_one_action_get_sensitive_column( EgeSelectOneAction* action )
416 {
417 g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
418 return action->private_data->sensitiveColumn;
419 }
421 void ege_select_one_action_set_sensitive_column( EgeSelectOneAction* action, gint col )
422 {
423 g_object_set( G_OBJECT(action), "sensitive-column", col, NULL );
424 }
426 void ege_select_one_action_set_appearance( EgeSelectOneAction* action, gchar const* val )
427 {
428 g_object_set( G_OBJECT(action), "appearance", val, NULL );
429 }
431 void ege_select_one_action_set_selection( EgeSelectOneAction* action, gchar const* val )
432 {
433 g_object_set( G_OBJECT(action), "selection", val, NULL );
434 }
436 void ege_select_one_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec )
437 {
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 }
483 }
485 void ege_select_one_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec )
486 {
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 }
581 }
583 GtkWidget* create_menu_item( GtkAction* action )
584 {
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;
632 }
635 void ege_select_one_action_set_radio_action_type( EgeSelectOneAction* action, GType radioActionType )
636 {
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 }
644 }
646 GtkWidget* create_tool_item( GtkAction* action )
647 {
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;
837 }
840 void connect_proxy( GtkAction *action, GtkWidget *proxy )
841 {
842 gParentClass->connect_proxy( action, proxy );
843 }
845 void disconnect_proxy( GtkAction *action, GtkWidget *proxy )
846 {
847 gParentClass->disconnect_proxy( action, proxy );
848 }
851 void resync_active( EgeSelectOneAction* act, gint active, gboolean override )
852 {
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 }
911 }
913 void resync_sensitive( EgeSelectOneAction* act )
914 {
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);
978 }
980 void combo_changed_cb( GtkComboBox* widget, gpointer user_data )
981 {
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 }
1009 }
1011 gboolean combo_entry_focus_lost_cb( GtkWidget *widget, GdkEventFocus *event, gpointer data )
1012 {
1013 EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(data);
1014 (void)widget;
1015 (void)event;
1017 commit_pending_change(act);
1019 return FALSE;
1020 }
1022 void combo_entry_changed_cb( GtkEntry* widget, gpointer user_data )
1023 {
1024 EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(user_data);
1025 (void)widget;
1026 commit_pending_change(act);
1027 }
1029 void commit_pending_change(EgeSelectOneAction *act)
1030 {
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 }
1056 }
1058 gint find_text_index(EgeSelectOneAction *act, gchar const* text)
1059 {
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;
1085 }
1087 void menu_toggled_cb( GtkWidget* obj, gpointer data )
1088 {
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 }
1095 }
1097 void proxy_action_chagned_cb( GtkRadioAction* action, GtkRadioAction* current, gpointer user_data )
1098 {
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 }
1107 }
1109 int scan_max_width( GtkTreeModel *model, gint labelColumn )
1110 {
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;
1129 }