Code

60332e02ea72d2363dfa040758dfb96739dda6df
[inkscape.git] / src / ink-comboboxentry-action.cpp
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   }
107 static void ink_comboboxentry_action_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
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   }
142 static void
143 ink_comboboxentry_action_connect_proxy (GtkAction *action,
144                                         GtkWidget *proxy)
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);
154 static void ink_comboboxentry_action_class_init (Ink_ComboBoxEntry_ActionClass *klass)
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);
243 static void ink_comboboxentry_action_init (Ink_ComboBoxEntry_Action *action)
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;
253 GType ink_comboboxentry_action_get_type ()
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;
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 )
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);
302 // Create a widget for a toolbar.
303 GtkWidget* create_tool_item( GtkAction* action )
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;
375 // Create a drop-down menu.
376 GtkWidget* create_menu_item( GtkAction* action )
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;
386 // Setters/Getters ---------------------------------------------------
388 GtkTreeModel *ink_comboboxentry_action_get_model( Ink_ComboBoxEntry_Action* action ) {
390   return action->model;
393 GtkComboBoxEntry *ink_comboboxentry_action_get_comboboxentry( Ink_ComboBoxEntry_Action* action ) {
395   return action->combobox;
398 gchar* ink_comboboxentry_action_get_active_text( Ink_ComboBoxEntry_Action* action ) {
400   gchar* text = g_strdup( action->text );
401   return text;
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;
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   }
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   }
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   }
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   }
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   }
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;
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   }
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 );
601 static gboolean match_selected_cb( GtkEntryCompletion* /*widget*/, GtkTreeModel* model, GtkTreeIter* iter, gpointer data )
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;