60332e02ea72d2363dfa040758dfb96739dda6df
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 *
11 * Copyright (C) 2010 Authors
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
16 /*
17 * We must provide for both a toolbar item and a menu item.
18 * As we don't know which widgets are used (or even constructed),
19 * we must keep track of things like active entry ourselves.
20 */
22 #include <iostream>
23 #include <string.h>
25 #include <gtk/gtk.h>
26 #include <gtk/gtktoolitem.h>
27 #include <gtk/gtkcomboboxentry.h>
28 #include <gtk/gtkentrycompletion.h>
30 #include "ink-comboboxentry-action.h"
32 // Must handle both tool and menu items!
33 static GtkWidget* create_tool_item( GtkAction* action );
34 static GtkWidget* create_menu_item( GtkAction* action );
36 // Internal
37 static gint get_active_row_from_text( Ink_ComboBoxEntry_Action* action, const gchar* target_text );
39 // Callbacks
40 static void combo_box_changed_cb( GtkComboBoxEntry* widget, gpointer data );
41 static void entry_activate_cb( GtkEntry* widget, gpointer data );
42 static gboolean match_selected_cb( GtkEntryCompletion* widget, GtkTreeModel* model, GtkTreeIter* iter, gpointer data );
44 enum {
45 PROP_MODEL = 1,
46 PROP_COMBOBOX,
47 PROP_ENTRY,
48 PROP_WIDTH,
49 PROP_CELL_DATA_FUNC,
50 PROP_POPUP
51 };
53 enum {
54 CHANGED = 0,
55 ACTIVATED,
56 N_SIGNALS
57 };
58 static guint signals[N_SIGNALS] = {0};
60 static GtkActionClass *ink_comboboxentry_action_parent_class = NULL;
61 static GQuark gDataName = 0;
63 static void ink_comboboxentry_action_finalize (GObject *object)
64 {
65 // Free any allocated resources.
67 G_OBJECT_CLASS (ink_comboboxentry_action_parent_class)->finalize (object);
68 }
71 static void ink_comboboxentry_action_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
72 {
73 Ink_ComboBoxEntry_Action *action = INK_COMBOBOXENTRY_ACTION (object);
75 switch(property_id) {
77 case PROP_MODEL:
78 action->model = GTK_TREE_MODEL( g_value_get_object( value ));
79 break;
81 case PROP_COMBOBOX:
82 action->combobox = GTK_COMBO_BOX_ENTRY( g_value_get_object( value ));
83 break;
85 case PROP_ENTRY:
86 action->entry = GTK_ENTRY( g_value_get_object( value ));
87 break;
89 case PROP_WIDTH:
90 action->width = g_value_get_int( value );
91 break;
93 case PROP_CELL_DATA_FUNC:
94 action->cell_data_func = g_value_get_pointer( value );
95 break;
97 case PROP_POPUP:
98 action->popup = g_value_get_boolean( value );
99 break;
101 default:
102 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
103 }
104 }
107 static void ink_comboboxentry_action_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
108 {
109 Ink_ComboBoxEntry_Action *action = INK_COMBOBOXENTRY_ACTION (object);
111 switch(property_id) {
113 case PROP_MODEL:
114 g_value_set_object (value, action->model);
115 break;
117 case PROP_COMBOBOX:
118 g_value_set_object (value, action->combobox);
119 break;
121 case PROP_ENTRY:
122 g_value_set_object (value, action->entry);
123 break;
125 case PROP_WIDTH:
126 g_value_set_int (value, action->width);
127 break;
129 case PROP_CELL_DATA_FUNC:
130 g_value_set_pointer (value, action->cell_data_func);
131 break;
133 case PROP_POPUP:
134 g_value_set_boolean (value, action->popup);
135 break;
137 default:
138 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
139 }
140 }
142 static void
143 ink_comboboxentry_action_connect_proxy (GtkAction *action,
144 GtkWidget *proxy)
145 {
146 /* Override any proxy properties. */
147 // if (GTK_IS_MENU_ITEM (proxy)) {
148 // }
150 GTK_ACTION_CLASS (ink_comboboxentry_action_parent_class)->connect_proxy (action, proxy);
151 }
154 static void ink_comboboxentry_action_class_init (Ink_ComboBoxEntry_ActionClass *klass)
155 {
157 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
158 GtkActionClass *gtkaction_class = GTK_ACTION_CLASS (klass);
160 gtkaction_class->connect_proxy = ink_comboboxentry_action_connect_proxy;
162 gobject_class->finalize = ink_comboboxentry_action_finalize;
163 gobject_class->set_property = ink_comboboxentry_action_set_property;
164 gobject_class->get_property = ink_comboboxentry_action_get_property;
166 gDataName = g_quark_from_string("ink_comboboxentry-action");
168 klass->parent_class.create_tool_item = create_tool_item;
169 klass->parent_class.create_menu_item = create_menu_item;
171 ink_comboboxentry_action_parent_class = GTK_ACTION_CLASS(g_type_class_peek_parent (klass) );
173 g_object_class_install_property (
174 gobject_class,
175 PROP_MODEL,
176 g_param_spec_object ("model",
177 "Tree Model",
178 "Tree Model",
179 GTK_TYPE_TREE_MODEL,
180 (GParamFlags)G_PARAM_READWRITE));
181 g_object_class_install_property (
182 gobject_class,
183 PROP_COMBOBOX,
184 g_param_spec_object ("combobox",
185 "GtkComboBoxEntry",
186 "GtkComboBoxEntry",
187 GTK_TYPE_WIDGET,
188 (GParamFlags)G_PARAM_READABLE));
189 g_object_class_install_property (
190 gobject_class,
191 PROP_ENTRY,
192 g_param_spec_object ("entry",
193 "GtkEntry",
194 "GtkEntry",
195 GTK_TYPE_WIDGET,
196 (GParamFlags)G_PARAM_READABLE));
197 g_object_class_install_property (
198 gobject_class,
199 PROP_WIDTH,
200 g_param_spec_int ("width",
201 "EntryBox width",
202 "EntryBox width (characters)",
203 -1.0, 100, -1.0,
204 (GParamFlags)G_PARAM_READWRITE));
206 g_object_class_install_property (
207 gobject_class,
208 PROP_CELL_DATA_FUNC,
209 g_param_spec_pointer ("cell_data_func",
210 "Cell Data Func",
211 "Cell Deta Function",
212 (GParamFlags)G_PARAM_READWRITE));
214 g_object_class_install_property (
215 gobject_class,
216 PROP_POPUP,
217 g_param_spec_boolean ("popup",
218 "Entry Popup",
219 "Entry Popup",
220 false,
221 (GParamFlags)G_PARAM_READWRITE));
223 // We need to know when GtkComboBoxEvent or Menu ready for reading
224 signals[CHANGED] = g_signal_new( "changed",
225 G_TYPE_FROM_CLASS(klass),
226 G_SIGNAL_RUN_FIRST,
227 G_STRUCT_OFFSET(Ink_ComboBoxEntry_ActionClass, changed),
228 NULL, NULL,
229 g_cclosure_marshal_VOID__VOID,
230 G_TYPE_NONE, 0);
232 // Probably not needed... originally to keep track of key-presses.
233 signals[ACTIVATED] = g_signal_new( "activated",
234 G_TYPE_FROM_CLASS(klass),
235 G_SIGNAL_RUN_FIRST,
236 G_STRUCT_OFFSET(Ink_ComboBoxEntry_ActionClass, activated),
237 NULL, NULL,
238 g_cclosure_marshal_VOID__VOID,
239 G_TYPE_NONE, 0);
241 }
243 static void ink_comboboxentry_action_init (Ink_ComboBoxEntry_Action *action)
244 {
245 action->active = -1;
246 action->text = NULL;
247 action->entry_completion = NULL;
248 action->popup = false;
249 action->warning = NULL;
250 action->altx_name = NULL;
251 }
253 GType ink_comboboxentry_action_get_type ()
254 {
255 static GType ink_comboboxentry_action_type = 0;
257 if (!ink_comboboxentry_action_type) {
258 static const GTypeInfo ink_comboboxentry_action_info = {
259 sizeof(Ink_ComboBoxEntry_ActionClass),
260 NULL, /* base_init */
261 NULL, /* base_finalize */
262 (GClassInitFunc) ink_comboboxentry_action_class_init,
263 NULL, /* class_finalize */
264 NULL, /* class_data */
265 sizeof(Ink_ComboBoxEntry_Action),
266 0, /* n_preallocs */
267 (GInstanceInitFunc)ink_comboboxentry_action_init, /* instance_init */
268 NULL /* value_table */
269 };
271 ink_comboboxentry_action_type = g_type_register_static (GTK_TYPE_ACTION,
272 "Ink_ComboBoxEntry_Action",
273 &ink_comboboxentry_action_info,
274 (GTypeFlags)0 );
275 }
277 return ink_comboboxentry_action_type;
278 }
281 Ink_ComboBoxEntry_Action *ink_comboboxentry_action_new (const gchar *name,
282 const gchar *label,
283 const gchar *tooltip,
284 const gchar *stock_id,
285 GtkTreeModel *model,
286 gint width,
287 void *cell_data_func )
288 {
289 g_return_val_if_fail (name != NULL, NULL);
291 return (Ink_ComboBoxEntry_Action*)g_object_new (INK_COMBOBOXENTRY_TYPE_ACTION,
292 "name", name,
293 "label", label,
294 "tooltip", tooltip,
295 "stock-id", stock_id,
296 "model", model,
297 "width", width,
298 "cell_data_func", cell_data_func,
299 NULL);
300 }
302 // Create a widget for a toolbar.
303 GtkWidget* create_tool_item( GtkAction* action )
304 {
305 GtkWidget* item = 0;
307 if ( INK_COMBOBOXENTRY_IS_ACTION( action ) && INK_COMBOBOXENTRY_ACTION(action)->model ) {
309 Ink_ComboBoxEntry_Action* ink_comboboxentry_action = INK_COMBOBOXENTRY_ACTION( action );
311 item = GTK_WIDGET( gtk_tool_item_new() );
313 GtkWidget* comboBoxEntry = gtk_combo_box_entry_new_with_model( ink_comboboxentry_action->model, 0 );
315 {
316 GtkWidget *align = gtk_alignment_new(0, 0.5, 0, 0);
317 gtk_container_add( GTK_CONTAINER(align), comboBoxEntry );
318 gtk_container_add( GTK_CONTAINER(item), align );
319 }
321 ink_comboboxentry_action->combobox = GTK_COMBO_BOX_ENTRY(comboBoxEntry);
323 gtk_combo_box_set_active( GTK_COMBO_BOX( comboBoxEntry ), ink_comboboxentry_action->active );
325 g_signal_connect( G_OBJECT(comboBoxEntry), "changed", G_CALLBACK(combo_box_changed_cb), action );
327 // Optionally add formatting...
328 if( ink_comboboxentry_action->cell_data_func != NULL ) {
329 GtkCellRenderer *cell = gtk_cell_renderer_text_new();
330 gtk_cell_layout_clear( GTK_CELL_LAYOUT( comboBoxEntry ) );
331 gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( comboBoxEntry ), cell, true );
332 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT( comboBoxEntry ), cell,
333 GtkCellLayoutDataFunc (ink_comboboxentry_action->cell_data_func),
334 NULL, NULL );
335 }
337 // Get reference to GtkEntry and fiddle a bit with it.
338 GtkWidget *child = gtk_bin_get_child( GTK_BIN(comboBoxEntry) );
339 if( child && GTK_IS_ENTRY( child ) ) {
340 ink_comboboxentry_action->entry = GTK_ENTRY(child);
342 // Change width
343 if( ink_comboboxentry_action->width > 0 ) {
344 gtk_entry_set_width_chars (GTK_ENTRY (child), ink_comboboxentry_action->width );
345 }
347 // Add pop-up entry completion if required
348 if( ink_comboboxentry_action->popup ) {
349 ink_comboboxentry_action_popup_enable( ink_comboboxentry_action );
350 }
352 // Add altx_name if required
353 if( ink_comboboxentry_action->altx_name ) {
354 g_object_set_data( G_OBJECT( child ), ink_comboboxentry_action->altx_name, ink_comboboxentry_action->entry );
355 }
357 // Add signal for GtkEntry to check if finished typing.
358 g_signal_connect( G_OBJECT(child), "activate", G_CALLBACK(entry_activate_cb), action );
360 }
362 gtk_action_connect_proxy( GTK_ACTION( action ), item );
364 gtk_widget_show_all( item );
366 } else {
368 item = ink_comboboxentry_action_parent_class->create_tool_item( action );
370 }
372 return item;
373 }
375 // Create a drop-down menu.
376 GtkWidget* create_menu_item( GtkAction* action )
377 {
378 GtkWidget* item = 0;
380 item = ink_comboboxentry_action_parent_class->create_menu_item( action );
381 g_warning( "ink_comboboxentry_action: create_menu_item not implemented" );
382 // One can easily modify ege-select-one-action routine to implement this.
383 return item;
384 }
386 // Setters/Getters ---------------------------------------------------
388 GtkTreeModel *ink_comboboxentry_action_get_model( Ink_ComboBoxEntry_Action* action ) {
390 return action->model;
391 }
393 GtkComboBoxEntry *ink_comboboxentry_action_get_comboboxentry( Ink_ComboBoxEntry_Action* action ) {
395 return action->combobox;
396 }
398 gchar* ink_comboboxentry_action_get_active_text( Ink_ComboBoxEntry_Action* action ) {
400 gchar* text = g_strdup( action->text );
401 return text;
402 }
404 gboolean ink_comboboxentry_action_set_active_text( Ink_ComboBoxEntry_Action* ink_comboboxentry_action, const gchar* text ) {
406 g_free( ink_comboboxentry_action->text );
407 ink_comboboxentry_action->text = g_strdup( text );
409 // Get active row or -1 if none
410 ink_comboboxentry_action->active = get_active_row_from_text( ink_comboboxentry_action, ink_comboboxentry_action->text );
412 // Set active row, check that combobox has been created.
413 if( ink_comboboxentry_action->combobox ) {
414 gtk_combo_box_set_active( GTK_COMBO_BOX( ink_comboboxentry_action->combobox ), ink_comboboxentry_action->active );
415 }
417 // Fiddle with entry
418 if( ink_comboboxentry_action->entry ) {
420 // Explicitly set text in GtkEntry box (won't be set if text not in list).
421 gtk_entry_set_text( ink_comboboxentry_action->entry, text );
423 // Show or hide warning
424 if( ink_comboboxentry_action->active == -1 && ink_comboboxentry_action->warning != NULL ) {
425 #if GTK_CHECK_VERSION(2,16,0)
426 gtk_entry_set_icon_from_icon_name( ink_comboboxentry_action->entry,
427 GTK_ENTRY_ICON_SECONDARY,
428 GTK_STOCK_DIALOG_WARNING );
429 // Can't add tooltip until icon set
430 gtk_entry_set_icon_tooltip_text( ink_comboboxentry_action->entry,
431 GTK_ENTRY_ICON_SECONDARY,
432 ink_comboboxentry_action->warning );
434 #endif // GTK_CHECK_VERSION(2,16,0)
435 } else {
436 #if GTK_CHECK_VERSION(2,16,0)
437 gtk_entry_set_icon_from_icon_name( GTK_ENTRY(ink_comboboxentry_action->entry),
438 GTK_ENTRY_ICON_SECONDARY,
439 NULL );
440 #endif // GTK_CHECK_VERSION(2,16,0)
441 }
442 }
444 // Return if active text in list
445 gboolean found = ( ink_comboboxentry_action->active != -1 );
446 return found;
447 }
449 void ink_comboboxentry_action_set_width( Ink_ComboBoxEntry_Action* action, gint width ) {
451 action->width = width;
453 // Widget may not have been created....
454 if( action->entry ) {
455 gtk_entry_set_width_chars( GTK_ENTRY(action->entry), width );
456 }
457 }
459 void ink_comboboxentry_action_popup_enable( Ink_ComboBoxEntry_Action* action ) {
461 action->popup = true;
463 // Widget may not have been created....
464 if( action->entry ) {
466 // Check we don't already have a GtkEntryCompletion
467 if( action->entry_completion ) return;
469 action->entry_completion = gtk_entry_completion_new();
471 gtk_entry_set_completion( action->entry, action->entry_completion );
472 gtk_entry_completion_set_model( action->entry_completion, action->model );
473 gtk_entry_completion_set_text_column( action->entry_completion, 0 );
474 gtk_entry_completion_set_popup_completion( action->entry_completion, true );
475 gtk_entry_completion_set_inline_completion( action->entry_completion, false );
476 gtk_entry_completion_set_inline_selection( action->entry_completion, true );
478 g_signal_connect (G_OBJECT (action->entry_completion), "match-selected", G_CALLBACK (match_selected_cb), action );
480 }
481 }
483 void ink_comboboxentry_action_popup_disable( Ink_ComboBoxEntry_Action* action ) {
485 action->popup = false;
487 if( action->entry_completion ) {
488 gtk_object_destroy( GTK_OBJECT( action->entry_completion ) );
489 }
490 }
492 void ink_comboboxentry_action_set_warning( Ink_ComboBoxEntry_Action* action, const gchar* warning ) {
494 g_free( action->warning );
495 action->warning = g_strdup( warning );
497 // Widget may not have been created....
498 if( action->entry ) {
499 #if GTK_CHECK_VERSION(2,16,0)
500 gtk_entry_set_icon_tooltip_text( GTK_ENTRY(action->entry),
501 GTK_ENTRY_ICON_SECONDARY,
502 action->warning );
503 #endif // GTK_CHECK_VERSION(2,16,0)
504 }
505 }
507 void ink_comboboxentry_action_set_altx_name( Ink_ComboBoxEntry_Action* action, const gchar* altx_name ) {
509 g_free( action->altx_name );
510 action->altx_name = g_strdup( altx_name );
512 // Widget may not have been created....
513 if( action->entry ) {
514 g_object_set_data( G_OBJECT(action->entry), action->altx_name, action->entry );
515 }
516 }
518 // Internal ---------------------------------------------------
520 // Return row of active text or -1 if not found.
521 gint get_active_row_from_text( Ink_ComboBoxEntry_Action* action, const gchar* target_text ) {
523 // Check if text in list
524 gint row = 0;
525 gboolean found = false;
526 GtkTreeIter iter;
527 gboolean valid = gtk_tree_model_get_iter_first( action->model, &iter );
528 while ( valid ) {
530 // Get text from list entry
531 gchar* text = 0;
532 gtk_tree_model_get( action->model, &iter, 0, &text, -1 ); // Column 0
534 // Check for match
535 if( strcmp( target_text, text ) == 0 ){
536 found = true;
537 break;
538 }
539 ++row;
540 valid = gtk_tree_model_iter_next( action->model, &iter );
541 }
543 if( !found ) row = -1;
545 return row;
547 }
550 // Callbacks ---------------------------------------------------
552 static void combo_box_changed_cb( GtkComboBoxEntry* widget, gpointer data ) {
554 // Two things can happen to get here:
555 // An item is selected in the drop-down menu.
556 // Text is typed.
557 // We only react here if an item is selected.
559 // Get action
560 Ink_ComboBoxEntry_Action *act = INK_COMBOBOXENTRY_ACTION( data );
562 // Check if item selected:
563 gint newActive = gtk_combo_box_get_active( GTK_COMBO_BOX( widget ));
564 if( newActive >= 0 ) {
566 if( newActive != act->active ) {
567 act->active = newActive;
568 g_free( act->text );
569 act->text = gtk_combo_box_get_active_text( GTK_COMBO_BOX( widget ));
571 // Now let the world know
572 g_signal_emit( G_OBJECT(act), signals[CHANGED], 0 );
574 }
575 }
576 }
578 static void entry_activate_cb( GtkEntry* widget, gpointer data ) {
580 // Get text from entry box.. check if it matches a menu entry.
582 // Get action
583 Ink_ComboBoxEntry_Action *ink_comboboxentry_action = INK_COMBOBOXENTRY_ACTION( data );
585 // Get text
586 g_free( ink_comboboxentry_action->text );
587 ink_comboboxentry_action->text = g_strdup( gtk_entry_get_text( widget ) );
589 // Get row
590 ink_comboboxentry_action->active =
591 get_active_row_from_text( ink_comboboxentry_action, ink_comboboxentry_action->text );
593 // Set active row
594 gtk_combo_box_set_active( GTK_COMBO_BOX( ink_comboboxentry_action->combobox), ink_comboboxentry_action->active );
596 // Now let the world know
597 g_signal_emit( G_OBJECT(ink_comboboxentry_action), signals[CHANGED], 0 );
599 }
601 static gboolean match_selected_cb( GtkEntryCompletion* /*widget*/, GtkTreeModel* model, GtkTreeIter* iter, gpointer data )
602 {
603 // Get action
604 Ink_ComboBoxEntry_Action *ink_comboboxentry_action = INK_COMBOBOXENTRY_ACTION( data );
605 GtkEntry *entry = ink_comboboxentry_action->entry;
607 if( entry) {
608 gchar *family = 0;
609 gtk_tree_model_get(model, iter, 0, &family, -1);
611 // Set text in GtkEntry
612 gtk_entry_set_text (GTK_ENTRY (entry), family );
614 // Set text in GtkAction
615 g_free( ink_comboboxentry_action->text );
616 ink_comboboxentry_action->text = family;
618 // Get row
619 ink_comboboxentry_action->active =
620 get_active_row_from_text( ink_comboboxentry_action, ink_comboboxentry_action->text );
622 // Set active row
623 gtk_combo_box_set_active( GTK_COMBO_BOX( ink_comboboxentry_action->combobox), ink_comboboxentry_action->active );
625 // Now let the world know
626 g_signal_emit( G_OBJECT(ink_comboboxentry_action), signals[CHANGED], 0 );
628 return true;
629 }
630 return false;
631 }