de0af347c7699e1f117e151f53da8c63f15964c0
1 /*
2 * A subclass of GtkAction that wraps a GtkComboBoxEntry.
3 * Features:
4 * Setting GtkEntryBox width in characters.
5 * Passing a function for formatting cells.
6 * Displaying a warning if text isn't in list.
7 *
8 * Author(s):
9 * Tavmjong Bah
10 * Jon A. Cruz <jon@joncruz.org>
11 *
12 * Copyright (C) 2010 Authors
13 *
14 * Released under GNU GPL, read the file 'COPYING' for more information
15 */
17 /*
18 * We must provide for both a toolbar item and a menu item.
19 * As we don't know which widgets are used (or even constructed),
20 * we must keep track of things like active entry ourselves.
21 */
23 #include <iostream>
24 #include <string.h>
26 #include <gtk/gtk.h>
27 #include <gtk/gtktoolitem.h>
28 #include <gtk/gtkcomboboxentry.h>
29 #include <gtk/gtkentrycompletion.h>
31 #include "ink-comboboxentry-action.h"
33 // Must handle both tool and menu items!
34 static GtkWidget* create_tool_item( GtkAction* action );
35 static GtkWidget* create_menu_item( GtkAction* action );
37 // Internal
38 static gint get_active_row_from_text( Ink_ComboBoxEntry_Action* action, const gchar* target_text );
40 // Callbacks
41 static void combo_box_changed_cb( GtkComboBoxEntry* widget, gpointer data );
42 static void entry_activate_cb( GtkEntry* widget, gpointer data );
43 static gboolean match_selected_cb( GtkEntryCompletion* widget, GtkTreeModel* model, GtkTreeIter* iter, gpointer data );
45 enum {
46 PROP_MODEL = 1,
47 PROP_COMBOBOX,
48 PROP_ENTRY,
49 PROP_WIDTH,
50 PROP_CELL_DATA_FUNC,
51 PROP_POPUP
52 };
54 enum {
55 CHANGED = 0,
56 ACTIVATED,
57 N_SIGNALS
58 };
59 static guint signals[N_SIGNALS] = {0};
61 static GtkActionClass *ink_comboboxentry_action_parent_class = NULL;
62 static GQuark gDataName = 0;
64 static void ink_comboboxentry_action_finalize (GObject *object)
65 {
66 // Free any allocated resources.
68 G_OBJECT_CLASS (ink_comboboxentry_action_parent_class)->finalize (object);
69 }
72 static void ink_comboboxentry_action_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
73 {
74 Ink_ComboBoxEntry_Action *action = INK_COMBOBOXENTRY_ACTION (object);
76 switch(property_id) {
78 case PROP_MODEL:
79 action->model = GTK_TREE_MODEL( g_value_get_object( value ));
80 break;
82 case PROP_COMBOBOX:
83 action->combobox = GTK_COMBO_BOX_ENTRY( g_value_get_object( value ));
84 break;
86 case PROP_ENTRY:
87 action->entry = GTK_ENTRY( g_value_get_object( value ));
88 break;
90 case PROP_WIDTH:
91 action->width = g_value_get_int( value );
92 break;
94 case PROP_CELL_DATA_FUNC:
95 action->cell_data_func = g_value_get_pointer( value );
96 break;
98 case PROP_POPUP:
99 action->popup = g_value_get_boolean( value );
100 break;
102 default:
103 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
104 }
105 }
108 static void ink_comboboxentry_action_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
109 {
110 Ink_ComboBoxEntry_Action *action = INK_COMBOBOXENTRY_ACTION (object);
112 switch(property_id) {
114 case PROP_MODEL:
115 g_value_set_object (value, action->model);
116 break;
118 case PROP_COMBOBOX:
119 g_value_set_object (value, action->combobox);
120 break;
122 case PROP_ENTRY:
123 g_value_set_object (value, action->entry);
124 break;
126 case PROP_WIDTH:
127 g_value_set_int (value, action->width);
128 break;
130 case PROP_CELL_DATA_FUNC:
131 g_value_set_pointer (value, action->cell_data_func);
132 break;
134 case PROP_POPUP:
135 g_value_set_boolean (value, action->popup);
136 break;
138 default:
139 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
140 }
141 }
143 static void
144 ink_comboboxentry_action_connect_proxy (GtkAction *action,
145 GtkWidget *proxy)
146 {
147 /* Override any proxy properties. */
148 // if (GTK_IS_MENU_ITEM (proxy)) {
149 // }
151 GTK_ACTION_CLASS (ink_comboboxentry_action_parent_class)->connect_proxy (action, proxy);
152 }
155 static void ink_comboboxentry_action_class_init (Ink_ComboBoxEntry_ActionClass *klass)
156 {
158 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
159 GtkActionClass *gtkaction_class = GTK_ACTION_CLASS (klass);
161 gtkaction_class->connect_proxy = ink_comboboxentry_action_connect_proxy;
163 gobject_class->finalize = ink_comboboxentry_action_finalize;
164 gobject_class->set_property = ink_comboboxentry_action_set_property;
165 gobject_class->get_property = ink_comboboxentry_action_get_property;
167 gDataName = g_quark_from_string("ink_comboboxentry-action");
169 klass->parent_class.create_tool_item = create_tool_item;
170 klass->parent_class.create_menu_item = create_menu_item;
172 ink_comboboxentry_action_parent_class = GTK_ACTION_CLASS(g_type_class_peek_parent (klass) );
174 g_object_class_install_property (
175 gobject_class,
176 PROP_MODEL,
177 g_param_spec_object ("model",
178 "Tree Model",
179 "Tree Model",
180 GTK_TYPE_TREE_MODEL,
181 (GParamFlags)G_PARAM_READWRITE));
182 g_object_class_install_property (
183 gobject_class,
184 PROP_COMBOBOX,
185 g_param_spec_object ("combobox",
186 "GtkComboBoxEntry",
187 "GtkComboBoxEntry",
188 GTK_TYPE_WIDGET,
189 (GParamFlags)G_PARAM_READABLE));
190 g_object_class_install_property (
191 gobject_class,
192 PROP_ENTRY,
193 g_param_spec_object ("entry",
194 "GtkEntry",
195 "GtkEntry",
196 GTK_TYPE_WIDGET,
197 (GParamFlags)G_PARAM_READABLE));
198 g_object_class_install_property (
199 gobject_class,
200 PROP_WIDTH,
201 g_param_spec_int ("width",
202 "EntryBox width",
203 "EntryBox width (characters)",
204 -1.0, 100, -1.0,
205 (GParamFlags)G_PARAM_READWRITE));
207 g_object_class_install_property (
208 gobject_class,
209 PROP_CELL_DATA_FUNC,
210 g_param_spec_pointer ("cell_data_func",
211 "Cell Data Func",
212 "Cell Deta Function",
213 (GParamFlags)G_PARAM_READWRITE));
215 g_object_class_install_property (
216 gobject_class,
217 PROP_POPUP,
218 g_param_spec_boolean ("popup",
219 "Entry Popup",
220 "Entry Popup",
221 false,
222 (GParamFlags)G_PARAM_READWRITE));
224 // We need to know when GtkComboBoxEvent or Menu ready for reading
225 signals[CHANGED] = g_signal_new( "changed",
226 G_TYPE_FROM_CLASS(klass),
227 G_SIGNAL_RUN_FIRST,
228 G_STRUCT_OFFSET(Ink_ComboBoxEntry_ActionClass, changed),
229 NULL, NULL,
230 g_cclosure_marshal_VOID__VOID,
231 G_TYPE_NONE, 0);
233 // Probably not needed... originally to keep track of key-presses.
234 signals[ACTIVATED] = g_signal_new( "activated",
235 G_TYPE_FROM_CLASS(klass),
236 G_SIGNAL_RUN_FIRST,
237 G_STRUCT_OFFSET(Ink_ComboBoxEntry_ActionClass, activated),
238 NULL, NULL,
239 g_cclosure_marshal_VOID__VOID,
240 G_TYPE_NONE, 0);
242 }
244 static void ink_comboboxentry_action_init (Ink_ComboBoxEntry_Action *action)
245 {
246 action->active = -1;
247 action->text = NULL;
248 action->entry_completion = NULL;
249 #if !GTK_CHECK_VERSION(2,16,0)
250 action->indicator = NULL;
251 #endif
252 action->popup = false;
253 action->warning = NULL;
254 action->altx_name = NULL;
255 }
257 GType ink_comboboxentry_action_get_type ()
258 {
259 static GType ink_comboboxentry_action_type = 0;
261 if (!ink_comboboxentry_action_type) {
262 static const GTypeInfo ink_comboboxentry_action_info = {
263 sizeof(Ink_ComboBoxEntry_ActionClass),
264 NULL, /* base_init */
265 NULL, /* base_finalize */
266 (GClassInitFunc) ink_comboboxentry_action_class_init,
267 NULL, /* class_finalize */
268 NULL, /* class_data */
269 sizeof(Ink_ComboBoxEntry_Action),
270 0, /* n_preallocs */
271 (GInstanceInitFunc)ink_comboboxentry_action_init, /* instance_init */
272 NULL /* value_table */
273 };
275 ink_comboboxentry_action_type = g_type_register_static (GTK_TYPE_ACTION,
276 "Ink_ComboBoxEntry_Action",
277 &ink_comboboxentry_action_info,
278 (GTypeFlags)0 );
279 }
281 return ink_comboboxentry_action_type;
282 }
285 Ink_ComboBoxEntry_Action *ink_comboboxentry_action_new (const gchar *name,
286 const gchar *label,
287 const gchar *tooltip,
288 const gchar *stock_id,
289 GtkTreeModel *model,
290 gint width,
291 void *cell_data_func )
292 {
293 g_return_val_if_fail (name != NULL, NULL);
295 return (Ink_ComboBoxEntry_Action*)g_object_new (INK_COMBOBOXENTRY_TYPE_ACTION,
296 "name", name,
297 "label", label,
298 "tooltip", tooltip,
299 "stock-id", stock_id,
300 "model", model,
301 "width", width,
302 "cell_data_func", cell_data_func,
303 NULL);
304 }
306 // Create a widget for a toolbar.
307 GtkWidget* create_tool_item( GtkAction* action )
308 {
309 GtkWidget* item = 0;
311 if ( INK_COMBOBOXENTRY_IS_ACTION( action ) && INK_COMBOBOXENTRY_ACTION(action)->model ) {
313 Ink_ComboBoxEntry_Action* ink_comboboxentry_action = INK_COMBOBOXENTRY_ACTION( action );
315 item = GTK_WIDGET( gtk_tool_item_new() );
317 GtkWidget* comboBoxEntry = gtk_combo_box_entry_new_with_model( ink_comboboxentry_action->model, 0 );
319 {
320 GtkWidget *align = gtk_alignment_new(0, 0.5, 0, 0);
321 #if GTK_CHECK_VERSION(2,16,0)
322 gtk_container_add( GTK_CONTAINER(align), comboBoxEntry );
323 #else // GTK_CHECK_VERSION(2,16,0)
324 GtkWidget *hbox = gtk_hbox_new( FALSE, 0 );
325 ink_comboboxentry_action->indicator = gtk_image_new_from_stock(GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_SMALL_TOOLBAR);
326 gtk_box_pack_start( GTK_BOX(hbox), comboBoxEntry, TRUE, TRUE, 0 );
327 gtk_box_pack_start( GTK_BOX(hbox), ink_comboboxentry_action->indicator, FALSE, FALSE, 0 );
328 gtk_container_add( GTK_CONTAINER(align), hbox );
329 #endif // GTK_CHECK_VERSION(2,16,0)
330 gtk_container_add( GTK_CONTAINER(item), align );
331 }
333 ink_comboboxentry_action->combobox = GTK_COMBO_BOX_ENTRY(comboBoxEntry);
335 gtk_combo_box_set_active( GTK_COMBO_BOX( comboBoxEntry ), ink_comboboxentry_action->active );
337 g_signal_connect( G_OBJECT(comboBoxEntry), "changed", G_CALLBACK(combo_box_changed_cb), action );
339 // Optionally add formatting...
340 if( ink_comboboxentry_action->cell_data_func != NULL ) {
341 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
342 gtk_cell_layout_clear( GTK_CELL_LAYOUT( comboBoxEntry ) );
343 gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( comboBoxEntry ), cell, true );
344 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT( comboBoxEntry ), cell,
345 GtkCellLayoutDataFunc (ink_comboboxentry_action->cell_data_func),
346 NULL, NULL );
347 }
349 // Get reference to GtkEntry and fiddle a bit with it.
350 GtkWidget *child = gtk_bin_get_child( GTK_BIN(comboBoxEntry) );
351 if( child && GTK_IS_ENTRY( child ) ) {
352 ink_comboboxentry_action->entry = GTK_ENTRY(child);
354 // Change width
355 if( ink_comboboxentry_action->width > 0 ) {
356 gtk_entry_set_width_chars (GTK_ENTRY (child), ink_comboboxentry_action->width );
357 }
359 // Add pop-up entry completion if required
360 if( ink_comboboxentry_action->popup ) {
361 ink_comboboxentry_action_popup_enable( ink_comboboxentry_action );
362 }
364 // Add altx_name if required
365 if( ink_comboboxentry_action->altx_name ) {
366 g_object_set_data( G_OBJECT( child ), ink_comboboxentry_action->altx_name, ink_comboboxentry_action->entry );
367 }
369 // Add signal for GtkEntry to check if finished typing.
370 g_signal_connect( G_OBJECT(child), "activate", G_CALLBACK(entry_activate_cb), action );
372 }
374 #if GTK_CHECK_VERSION(2,16,0)
375 gtk_action_connect_proxy( GTK_ACTION( action ), item );
376 #endif
378 gtk_widget_show_all( item );
380 } else {
382 item = ink_comboboxentry_action_parent_class->create_tool_item( action );
384 }
386 return item;
387 }
389 // Create a drop-down menu.
390 GtkWidget* create_menu_item( GtkAction* action )
391 {
392 GtkWidget* item = 0;
394 item = ink_comboboxentry_action_parent_class->create_menu_item( action );
395 g_warning( "ink_comboboxentry_action: create_menu_item not implemented" );
396 // One can easily modify ege-select-one-action routine to implement this.
397 return item;
398 }
400 // Setters/Getters ---------------------------------------------------
402 GtkTreeModel *ink_comboboxentry_action_get_model( Ink_ComboBoxEntry_Action* action ) {
404 return action->model;
405 }
407 GtkComboBoxEntry *ink_comboboxentry_action_get_comboboxentry( Ink_ComboBoxEntry_Action* action ) {
409 return action->combobox;
410 }
412 gchar* ink_comboboxentry_action_get_active_text( Ink_ComboBoxEntry_Action* action ) {
414 gchar* text = g_strdup( action->text );
415 return text;
416 }
418 gboolean ink_comboboxentry_action_set_active_text( Ink_ComboBoxEntry_Action* ink_comboboxentry_action, const gchar* text ) {
420 g_free( ink_comboboxentry_action->text );
421 ink_comboboxentry_action->text = g_strdup( text );
423 // Get active row or -1 if none
424 ink_comboboxentry_action->active = get_active_row_from_text( ink_comboboxentry_action, ink_comboboxentry_action->text );
426 // Set active row, check that combobox has been created.
427 if( ink_comboboxentry_action->combobox ) {
428 gtk_combo_box_set_active( GTK_COMBO_BOX( ink_comboboxentry_action->combobox ), ink_comboboxentry_action->active );
429 }
431 // Fiddle with entry
432 if( ink_comboboxentry_action->entry ) {
434 // Explicitly set text in GtkEntry box (won't be set if text not in list).
435 gtk_entry_set_text( ink_comboboxentry_action->entry, text );
437 // Show or hide warning
438 if( ink_comboboxentry_action->active == -1 && ink_comboboxentry_action->warning != NULL ) {
439 #if GTK_CHECK_VERSION(2,16,0)
440 gtk_entry_set_icon_from_icon_name( ink_comboboxentry_action->entry,
441 GTK_ENTRY_ICON_SECONDARY,
442 GTK_STOCK_DIALOG_WARNING );
443 // Can't add tooltip until icon set
444 gtk_entry_set_icon_tooltip_text( ink_comboboxentry_action->entry,
445 GTK_ENTRY_ICON_SECONDARY,
446 ink_comboboxentry_action->warning );
447 #else // GTK_CHECK_VERSION(2,16,0)
448 gtk_image_set_from_stock( GTK_IMAGE(ink_comboboxentry_action->indicator), GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_SMALL_TOOLBAR);
449 gtk_widget_set_tooltip_text( ink_comboboxentry_action->indicator, ink_comboboxentry_action->warning );
450 #endif // GTK_CHECK_VERSION(2,16,0)
451 } else {
452 #if GTK_CHECK_VERSION(2,16,0)
453 gtk_entry_set_icon_from_icon_name( GTK_ENTRY(ink_comboboxentry_action->entry),
454 GTK_ENTRY_ICON_SECONDARY,
455 NULL );
456 #else // GTK_CHECK_VERSION(2,16,0)
457 gtk_image_set_from_stock( GTK_IMAGE(ink_comboboxentry_action->indicator), NULL, GTK_ICON_SIZE_SMALL_TOOLBAR);
458 gtk_widget_set_tooltip_text( ink_comboboxentry_action->indicator, NULL );
459 #endif // GTK_CHECK_VERSION(2,16,0)
460 }
461 }
463 // Return if active text in list
464 gboolean found = ( ink_comboboxentry_action->active != -1 );
465 return found;
466 }
468 void ink_comboboxentry_action_set_width( Ink_ComboBoxEntry_Action* action, gint width ) {
470 action->width = width;
472 // Widget may not have been created....
473 if( action->entry ) {
474 gtk_entry_set_width_chars( GTK_ENTRY(action->entry), width );
475 }
476 }
478 void ink_comboboxentry_action_popup_enable( Ink_ComboBoxEntry_Action* action ) {
480 action->popup = true;
482 // Widget may not have been created....
483 if( action->entry ) {
485 // Check we don't already have a GtkEntryCompletion
486 if( action->entry_completion ) return;
488 action->entry_completion = gtk_entry_completion_new();
490 gtk_entry_set_completion( action->entry, action->entry_completion );
491 gtk_entry_completion_set_model( action->entry_completion, action->model );
492 gtk_entry_completion_set_text_column( action->entry_completion, 0 );
493 gtk_entry_completion_set_popup_completion( action->entry_completion, true );
494 gtk_entry_completion_set_inline_completion( action->entry_completion, false );
495 gtk_entry_completion_set_inline_selection( action->entry_completion, true );
497 g_signal_connect (G_OBJECT (action->entry_completion), "match-selected", G_CALLBACK (match_selected_cb), action );
499 }
500 }
502 void ink_comboboxentry_action_popup_disable( Ink_ComboBoxEntry_Action* action ) {
504 action->popup = false;
506 if( action->entry_completion ) {
507 gtk_object_destroy( GTK_OBJECT( action->entry_completion ) );
508 action->entry_completion = 0;
509 }
510 }
512 void ink_comboboxentry_action_set_warning( Ink_ComboBoxEntry_Action* action, const gchar* warning ) {
514 g_free( action->warning );
515 action->warning = g_strdup( warning );
517 // Widget may not have been created....
518 if( action->entry ) {
519 #if GTK_CHECK_VERSION(2,16,0)
520 gtk_entry_set_icon_tooltip_text( GTK_ENTRY(action->entry),
521 GTK_ENTRY_ICON_SECONDARY,
522 action->warning );
523 #else // GTK_CHECK_VERSION(2,16,0)
524 gtk_image_set_from_stock( GTK_IMAGE(action->indicator), action->warning ? GTK_STOCK_DIALOG_WARNING : 0, GTK_ICON_SIZE_SMALL_TOOLBAR );
525 #endif // GTK_CHECK_VERSION(2,16,0)
526 }
527 }
529 void ink_comboboxentry_action_set_altx_name( Ink_ComboBoxEntry_Action* action, const gchar* altx_name ) {
531 g_free( action->altx_name );
532 action->altx_name = g_strdup( altx_name );
534 // Widget may not have been created....
535 if( action->entry ) {
536 g_object_set_data( G_OBJECT(action->entry), action->altx_name, action->entry );
537 }
538 }
540 // Internal ---------------------------------------------------
542 // Return row of active text or -1 if not found.
543 gint get_active_row_from_text( Ink_ComboBoxEntry_Action* action, const gchar* target_text ) {
545 // Check if text in list
546 gint row = 0;
547 gboolean found = false;
548 GtkTreeIter iter;
549 gboolean valid = gtk_tree_model_get_iter_first( action->model, &iter );
550 while ( valid ) {
552 // Get text from list entry
553 gchar* text = 0;
554 gtk_tree_model_get( action->model, &iter, 0, &text, -1 ); // Column 0
556 // Check for match
557 if( strcmp( target_text, text ) == 0 ){
558 found = true;
559 break;
560 }
561 ++row;
562 valid = gtk_tree_model_iter_next( action->model, &iter );
563 }
565 if( !found ) row = -1;
567 return row;
569 }
572 // Callbacks ---------------------------------------------------
574 static void combo_box_changed_cb( GtkComboBoxEntry* widget, gpointer data ) {
576 // Two things can happen to get here:
577 // An item is selected in the drop-down menu.
578 // Text is typed.
579 // We only react here if an item is selected.
581 // Get action
582 Ink_ComboBoxEntry_Action *act = INK_COMBOBOXENTRY_ACTION( data );
584 // Check if item selected:
585 gint newActive = gtk_combo_box_get_active( GTK_COMBO_BOX( widget ));
586 if( newActive >= 0 ) {
588 if( newActive != act->active ) {
589 act->active = newActive;
590 g_free( act->text );
591 act->text = gtk_combo_box_get_active_text( GTK_COMBO_BOX( widget ));
593 // Now let the world know
594 g_signal_emit( G_OBJECT(act), signals[CHANGED], 0 );
596 }
597 }
598 }
600 static void entry_activate_cb( GtkEntry* widget, gpointer data ) {
602 // Get text from entry box.. check if it matches a menu entry.
604 // Get action
605 Ink_ComboBoxEntry_Action *ink_comboboxentry_action = INK_COMBOBOXENTRY_ACTION( data );
607 // Get text
608 g_free( ink_comboboxentry_action->text );
609 ink_comboboxentry_action->text = g_strdup( gtk_entry_get_text( widget ) );
611 // Get row
612 ink_comboboxentry_action->active =
613 get_active_row_from_text( ink_comboboxentry_action, ink_comboboxentry_action->text );
615 // Set active row
616 gtk_combo_box_set_active( GTK_COMBO_BOX( ink_comboboxentry_action->combobox), ink_comboboxentry_action->active );
618 // Now let the world know
619 g_signal_emit( G_OBJECT(ink_comboboxentry_action), signals[CHANGED], 0 );
621 }
623 static gboolean match_selected_cb( GtkEntryCompletion* /*widget*/, GtkTreeModel* model, GtkTreeIter* iter, gpointer data )
624 {
625 // Get action
626 Ink_ComboBoxEntry_Action *ink_comboboxentry_action = INK_COMBOBOXENTRY_ACTION( data );
627 GtkEntry *entry = ink_comboboxentry_action->entry;
629 if( entry) {
630 gchar *family = 0;
631 gtk_tree_model_get(model, iter, 0, &family, -1);
633 // Set text in GtkEntry
634 gtk_entry_set_text (GTK_ENTRY (entry), family );
636 // Set text in GtkAction
637 g_free( ink_comboboxentry_action->text );
638 ink_comboboxentry_action->text = family;
640 // Get row
641 ink_comboboxentry_action->active =
642 get_active_row_from_text( ink_comboboxentry_action, ink_comboboxentry_action->text );
644 // Set active row
645 gtk_combo_box_set_active( GTK_COMBO_BOX( ink_comboboxentry_action->combobox), ink_comboboxentry_action->active );
647 // Now let the world know
648 g_signal_emit( G_OBJECT(ink_comboboxentry_action), signals[CHANGED], 0 );
650 return true;
651 }
652 return false;
653 }