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
103 {
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 )
135 {
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;
155 }
157 GtkTreeModel *ege_select_one_action_get_model(EgeSelectOneAction* action ){
158 return GTK_TREE_MODEL(action->private_data->model);
159 }
160 void ege_select_one_action_class_init( EgeSelectOneActionClass* klass )
161 {
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, G_MAXINT, 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, G_MAXINT, 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, G_MAXINT, -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, G_MAXINT, -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, G_MAXINT, -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 }
258 }
261 void ege_select_one_action_init( EgeSelectOneAction* action )
262 {
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 ); */
280 }
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 )
287 {
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;
301 }
304 gint ege_select_one_action_get_active( EgeSelectOneAction* action )
305 {
306 g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
307 return action->private_data->active;
308 }
310 gchar *ege_select_one_action_get_active_text( EgeSelectOneAction* action )
311 {
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;
327 }
329 void ege_select_one_action_set_active_text( EgeSelectOneAction* action, gchar const *text )
330 {
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 }
343 }
345 void ege_select_one_action_set_active( EgeSelectOneAction* action, gint val )
346 {
347 g_object_set( G_OBJECT(action), "active", val, NULL );
348 }
350 gint ege_select_one_action_get_label_column( EgeSelectOneAction* action )
351 {
352 g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
353 return action->private_data->labelColumn;
354 }
356 void ege_select_one_action_set_label_column( EgeSelectOneAction* action, gint col )
357 {
358 g_object_set( G_OBJECT(action), "label-column", col, NULL );
359 }
361 gint ege_select_one_action_get_icon_column( EgeSelectOneAction* action )
362 {
363 g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
364 return action->private_data->iconColumn;
365 }
367 void ege_select_one_action_set_icon_column( EgeSelectOneAction* action, gint col )
368 {
369 g_object_set( G_OBJECT(action), "icon-column", col, NULL );
370 }
372 gint ege_select_one_action_get_icon_size( EgeSelectOneAction* action )
373 {
374 g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
375 return action->private_data->iconSize;
376 }
378 void ege_select_one_action_set_icon_size( EgeSelectOneAction* action, gint size )
379 {
380 g_object_set( G_OBJECT(action), "icon-size", size, NULL );
381 }
383 gint ege_select_one_action_get_tooltip_column( EgeSelectOneAction* action )
384 {
385 g_return_val_if_fail( IS_EGE_SELECT_ONE_ACTION(action), 0 );
386 return action->private_data->tooltipColumn;
387 }
389 void ege_select_one_action_set_tooltip_column( EgeSelectOneAction* action, gint col )
390 {
391 g_object_set( G_OBJECT(action), "tooltip-column", col, NULL );
392 }
394 void ege_select_one_action_set_appearance( EgeSelectOneAction* action, gchar const* val )
395 {
396 g_object_set( G_OBJECT(action), "appearance", val, NULL );
397 }
399 void ege_select_one_action_set_selection( EgeSelectOneAction* action, gchar const* val )
400 {
401 g_object_set( G_OBJECT(action), "selection", val, NULL );
402 }
404 void ege_select_one_action_get_property( GObject* obj, guint propId, GValue* value, GParamSpec * pspec )
405 {
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 }
447 }
449 void ege_select_one_action_set_property( GObject* obj, guint propId, const GValue *value, GParamSpec* pspec )
450 {
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 }
539 }
541 GtkWidget* create_menu_item( GtkAction* action )
542 {
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;
590 }
593 void ege_select_one_action_set_radio_action_type( EgeSelectOneAction* action, GType radioActionType )
594 {
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 }
602 }
604 GtkWidget* create_tool_item( GtkAction* action )
605 {
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;
782 }
785 void connect_proxy( GtkAction *action, GtkWidget *proxy )
786 {
787 gParentClass->connect_proxy( action, proxy );
788 }
790 void disconnect_proxy( GtkAction *action, GtkWidget *proxy )
791 {
792 gParentClass->disconnect_proxy( action, proxy );
793 }
796 void resync_active( EgeSelectOneAction* act, gint active, gboolean override )
797 {
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 }
856 }
858 void combo_changed_cb( GtkComboBox* widget, gpointer user_data )
859 {
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 }
887 }
889 gboolean combo_entry_focus_lost_cb( GtkWidget *widget, GdkEventFocus *event, gpointer data )
890 {
891 EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(data);
892 (void)widget;
893 (void)event;
895 commit_pending_change(act);
897 return FALSE;
898 }
900 void combo_entry_changed_cb( GtkEntry* widget, gpointer user_data )
901 {
902 EgeSelectOneAction* act = EGE_SELECT_ONE_ACTION(user_data);
903 (void)widget;
904 commit_pending_change(act);
905 }
907 void commit_pending_change(EgeSelectOneAction *act)
908 {
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 }
934 }
936 gint find_text_index(EgeSelectOneAction *act, gchar const* text)
937 {
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;
963 }
965 void menu_toggled_cb( GtkWidget* obj, gpointer data )
966 {
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 }
973 }
975 void proxy_action_chagned_cb( GtkRadioAction* action, GtkRadioAction* current, gpointer user_data )
976 {
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 }
985 }
987 int scan_max_width( GtkTreeModel *model, gint labelColumn )
988 {
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;
1007 }