GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
gnc-combott.c
1 /********************************************************************\
2  * gnc-combott.c -- Basic simulation of ComboBox with tooltips for *
3  * each item. *
4  * Copyright (c) 2012 Robert Fewell *
5  * *
6  *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*
7  * This widget requires external ListStore which has two columns. *
8  * By default, column 0 holds the text to display and column 1 the *
9  * per item tooltip but these can be specified if the liststore has *
10  * a different format. *
11  *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*
12  * *
13  * This program is free software; you can redistribute it and/or *
14  * modify it under the terms of the GNU General Public License as *
15  * published by the Free Software Foundation; either version 2 of *
16  * the License, or (at your option) any later version. *
17  * *
18  * This program is distributed in the hope that it will be useful, *
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
21  * GNU General Public License for more details. *
22  * *
23  * You should have received a copy of the GNU General Public License*
24  * along with this program; if not, contact: *
25  * *
26  * Free Software Foundation Voice: +1-617-542-5942 *
27  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
28  * Boston, MA 02110-1301, USA [email protected] *
29 \********************************************************************/
30 #include <gtk/gtk.h>
31 #include "gnc-combott.h"
32 #include <strings.h>
33 #include <string.h>
34 
35 enum
36 {
37  CHANGED,
38  LAST_SIGNAL
39 };
40 
41 enum
42 {
43  PROP_0,
44  PROP_MODEL,
45  PROP_ACTIVE,
46  PROP_TEXT_COL,
47  PROP_TIP_COL,
48 };
49 
50 #define GNC_COMBOTT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GNC_TYPE_COMBOTT, GncCombottPrivate))
51 
52 static guint combott_signals[LAST_SIGNAL] = {0,};
53 
54 typedef struct GncCombottPrivate
55 {
56  GtkTreeModel *model;
57  GtkWidget *button;
58  GtkWidget *label;
59  GtkWidget *menu;
60  GtkTreeIter active_iter;
61  gint active;
62 
63  gint text_col;
64  gint tip_col;
65 
66  gint max_number_char;
67  gint num_items;
68 
69  gint x;
70  gint y;
71  gint width;
72  gint height;
73 
75 
77 static void gctt_init (GncCombott *combott);
78 
79 static void gctt_class_init (GncCombottClass *klass);
80 
81 static void gctt_set_property (GObject *object,
82  guint param_id,
83  const GValue *value,
84  GParamSpec *pspec);
85 
86 static void gctt_get_property (GObject *object,
87  guint param_id,
88  GValue *value,
89  GParamSpec *pspec);
90 
91 static void gctt_finalize (GObject *object);
92 
93 static void gctt_combott_menu_position (GtkMenu *menu,
94  gint *x,
95  gint *y,
96  gint *push_in,
97  gpointer user_data);
98 
99 static void gctt_changed (GncCombott *combott);
100 static void gctt_set_model (GncCombott *combott, GtkTreeModel *model);
101 static void gctt_refresh_menu (GncCombott *combott, GtkTreeModel *model);
102 static void gctt_rebuild_menu (GncCombott *combott, GtkTreeModel *model);
103 
104 static gboolean which_tooltip_cb (GtkWidget *widget, gint x, gint y,
105  gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data);
106 static gboolean button_press_cb (GtkWidget *widget, GdkEvent *event, gpointer *user_data );
107 static void button_getsize_cb (GtkWidget *widget, GtkAllocation *allocation, gpointer *user_data);
108 static void menu_getsize_cb (GtkWidget *widget, GtkAllocation *allocation, gpointer *user_data);
109 static void menuitem_response_cb (GtkMenuItem *item, gpointer *user_data);
110 
111 
112 /************************************************************/
113 /* g_object required functions */
114 /************************************************************/
115 static GObjectClass *parent_class = NULL;
116 
117 GType
118 gnc_combott_get_type (void)
119 {
120  static GType combott_type = 0;
121 
122  if (!combott_type)
123  {
124  static const GTypeInfo combott_info =
125  {
126  sizeof (GncCombottClass),
127  NULL, /* base_init */
128  NULL, /* base_finalize */
129  (GClassInitFunc) gctt_class_init,
130  NULL, /* class_finalize */
131  NULL, /* class_data */
132  sizeof (GncCombott),
133  0, /* n_preallocs */
134  (GInstanceInitFunc) gctt_init,
135  };
136 
137  combott_type = g_type_register_static (GTK_TYPE_HBOX,
138  "GncCombott",
139  &combott_info, 0);
140  }
141  return combott_type;
142 }
143 
144 
145 static void
146 gctt_class_init (GncCombottClass *klass)
147 {
148  GObjectClass *gobject_class;
149 
150  parent_class = g_type_class_peek_parent (klass);
151  gobject_class = G_OBJECT_CLASS (klass);
152 
153  gobject_class->set_property = gctt_set_property;
154  gobject_class->get_property = gctt_get_property;
155  gobject_class->finalize = gctt_finalize;
156 
157  klass->changed = gctt_changed;
158 
159  combott_signals[CHANGED] =
160  g_signal_new ("changed",
161  G_OBJECT_CLASS_TYPE (klass),
162  G_SIGNAL_RUN_LAST,
163  G_STRUCT_OFFSET (GncCombottClass, changed),
164  NULL, NULL,
165  g_cclosure_marshal_VOID__VOID,
166  G_TYPE_NONE, 0);
167 
168  g_object_class_install_property (
169  gobject_class,
170  PROP_MODEL,
171  g_param_spec_object ("model",
172  "Combott model",
173  "The model for the combo tooltip",
174  GTK_TYPE_TREE_MODEL,
175  G_PARAM_READWRITE));
176 
177  g_object_class_install_property (
178  gobject_class,
179  PROP_TEXT_COL,
180  g_param_spec_int ("text-col",
181  "text column",
182  "Column for the text",
183  0,
184  G_MAXINT,
185  0,
186  G_PARAM_READWRITE));
187 
188  g_object_class_install_property (
189  gobject_class,
190  PROP_TIP_COL,
191  g_param_spec_int ("tip-col",
192  "tip column",
193  "Column for the tip",
194  0,
195  G_MAXINT,
196  1,
197  G_PARAM_READWRITE));
198 
199  g_type_class_add_private(klass, sizeof(GncCombottPrivate));
200 }
201 
202 
203 static void
204 gctt_init (GncCombott *combott)
205 {
206  GtkWidget *hbox;
207  GtkWidget *label;
208  GtkWidget *arrow;
209  GtkWidget *button;
210  GtkWidget *sep;
211 
212  GncCombottPrivate *priv = GNC_COMBOTT_GET_PRIVATE (combott);
213 
214  priv->active = 0;
215  priv->text_col = 0;
216  priv->tip_col = 1;
217 
218  hbox = gtk_hbox_new(FALSE, 0);
219 
220  arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
221  gtk_box_pack_end (GTK_BOX (hbox), arrow, FALSE, FALSE, 0);
222 
223  sep = gtk_vseparator_new();
224  gtk_box_pack_end (GTK_BOX (hbox), sep, FALSE, FALSE, 0);
225 
226  label = gtk_label_new(NULL);
227  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
228  priv->label = label;
229 
230  button = gtk_button_new();
231  gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(hbox));
232  priv->button = button;
233 
234  gtk_container_add(GTK_CONTAINER(combott), GTK_WIDGET(button));
235 
236  g_signal_connect (button, "event",
237  G_CALLBACK (button_press_cb), combott);
238 
239  gtk_widget_set_has_tooltip (GTK_WIDGET(combott), TRUE);
240 
241  g_signal_connect(G_OBJECT(combott), "query-tooltip",
242  G_CALLBACK(which_tooltip_cb), combott);
243 
244  g_signal_connect(G_OBJECT(combott), "size-allocate",
245  G_CALLBACK(button_getsize_cb), combott);
246 
247  gtk_widget_show(GTK_WIDGET(priv->button));
248 }
249 
250 
251 static void
252 gctt_set_property (GObject *object,
253  guint param_id,
254  const GValue *value,
255  GParamSpec *pspec)
256 {
257  GncCombott *combott = GNC_COMBOTT (object);
258  GncCombottPrivate *priv = GNC_COMBOTT_GET_PRIVATE (combott);
259 
260  switch (param_id)
261  {
262  case PROP_MODEL:
263  gctt_set_model (combott, g_value_get_object (value));
264  break;
265 
266  case PROP_ACTIVE:
267  gnc_combott_set_active (combott, g_value_get_int (value));
268  break;
269 
270  case PROP_TEXT_COL:
271  priv->text_col = g_value_get_int (value);
272  break;
273 
274  case PROP_TIP_COL:
275  priv->tip_col = g_value_get_int (value);
276  gctt_refresh_menu(combott, priv->model);
277  break;
278 
279  default:
280  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
281  break;
282  }
283 }
284 
285 /* Note that g_value_set_object() refs the object, as does
286  * g_object_get(). But g_object_get() only unrefs once when it disgorges
287  * the object, leaving an unbalanced ref, which leaks. So instead of
288  * using g_value_set_object(), use g_value_take_object() which doesn't
289  * ref the object when used in get_property().
290  */
291 static void
292 gctt_get_property (GObject *object,
293  guint param_id,
294  GValue *value,
295  GParamSpec *pspec)
296 {
297  GncCombott *combott = GNC_COMBOTT (object);
298  GncCombottPrivate *priv = GNC_COMBOTT_GET_PRIVATE (combott);
299 
300  switch (param_id)
301  {
302  case PROP_MODEL:
303  g_value_take_object (value, priv->model);
304  break;
305 
306  case PROP_ACTIVE:
307  g_value_set_int (value, gnc_combott_get_active (combott));
308  break;
309 
310  case PROP_TEXT_COL:
311  g_value_set_int (value, priv->text_col);
312  break;
313 
314  case PROP_TIP_COL:
315  g_value_set_int (value, priv->tip_col);
316  break;
317 
318  default:
319  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
320  break;
321  }
322 }
323 
324 
325 static void
326 gctt_finalize (GObject *object)
327 {
328  GncCombott *combott;
329  GncCombottPrivate *priv;
330 
331  g_return_if_fail (object != NULL);
332  g_return_if_fail (GNC_IS_COMBOTT (object));
333 
334  combott = GNC_COMBOTT (object);
335  priv = GNC_COMBOTT_GET_PRIVATE (combott);
336 
337  if (priv->model)
338  {
339  priv->model = NULL;
340  }
341 
342  if (priv->menu)
343  {
344  priv->menu = NULL;
345  }
346 
347  G_OBJECT_CLASS (parent_class)->finalize (object);
348 }
349 
350 
351 static void
352 gctt_set_model (GncCombott *combott, GtkTreeModel *model)
353 {
354  GncCombottPrivate *priv;
355 
356  g_return_if_fail (GNC_IS_COMBOTT (combott));
357  g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
358 
359  priv = GNC_COMBOTT_GET_PRIVATE (combott);
360 
361  gctt_rebuild_menu(combott, model);
362 
363  priv->model = model;
364  g_object_ref (priv->model);
365 }
366 
367 
368 static void
369 gctt_rebuild_menu (GncCombott *combott, GtkTreeModel *model)
370 {
371  GncCombottPrivate *priv;
372  GtkTreeIter iter;
373  GtkWidget *menu_items;
374  gboolean valid;
375  gint num = 1;
376  gint items = 0;
377 
378  g_return_if_fail (GNC_IS_COMBOTT (combott));
379  g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
380 
381  priv = GNC_COMBOTT_GET_PRIVATE (combott);
382 
383  priv->menu = NULL;
384 
385  priv->menu = gtk_menu_new();
386 
387  valid = gtk_tree_model_get_iter_first (model, &iter);
388  while (valid)
389  {
390  GtkWidget *label;
391 
392  /* Walk through the list, reading each row */
393  gchar *str_data;
394  gchar *tip_data;
395  gtk_tree_model_get (model, &iter,
396  priv->text_col, &str_data,
397  priv->tip_col, &tip_data,
398  -1);
399 
400  /* Create a new menu-item with a name... */
401  menu_items = gtk_menu_item_new_with_label (str_data);
402 
403  /* Get widget width to max number of characters in list */
404  if(strlen(str_data) > num)
405  num = strlen(str_data);
406 
407  /* Add the tooltip to the child label */
408  label = gtk_bin_get_child(GTK_BIN(menu_items));
409  gtk_widget_set_tooltip_text (label, tip_data);
410  gtk_misc_set_alignment (GTK_MISC(label), 0, 0.5);
411 
412  /* ...and add it to the menu. */
413  gtk_menu_shell_append (GTK_MENU_SHELL (priv->menu), menu_items);
414  g_signal_connect (menu_items, "activate",
415  G_CALLBACK (menuitem_response_cb),
416  combott);
417 
418  /* Show the widget */
419  gtk_widget_show (menu_items);
420 
421  g_free (str_data);
422  g_free (tip_data);
423  items++;
424  valid = gtk_tree_model_iter_next (model, &iter);
425  }
426 
427  g_signal_connect(G_OBJECT(priv->menu), "size-allocate", G_CALLBACK(menu_getsize_cb), combott);
428 
429  /* Set widget width to max number of characters in list */
430  priv->max_number_char = num;
431  gtk_label_set_width_chars(GTK_LABEL(priv->label), priv->max_number_char);
432 
433  priv->num_items = items;
434 }
435 
436 
437 static void
438 gctt_refresh_menu (GncCombott *combott, GtkTreeModel *model)
439 {
440  GncCombottPrivate *priv;
441 
442  g_return_if_fail (GNC_IS_COMBOTT (combott));
443  g_return_if_fail (model == NULL || GTK_IS_TREE_MODEL (model));
444 
445  priv = GNC_COMBOTT_GET_PRIVATE (combott);
446 
447  gctt_rebuild_menu(combott, model);
448 }
449 
450 
451 static void
452 gctt_changed(GncCombott *combott)
453 {
454  /*
455  g_print("Changed Signal\n");
456  */
457 }
458 
459 
460 static void
461 gctt_combott_menu_position (GtkMenu *menu,
462  gint *x,
463  gint *y,
464  gint *push_in,
465  gpointer user_data)
466 {
467  GncCombott *combott = GNC_COMBOTT (user_data);
468  GncCombottPrivate *priv = GNC_COMBOTT_GET_PRIVATE (combott);
469  gint sx, sy;
470  GtkWidget *child;
471  GtkRequisition req;
472 
473  child = GTK_BIN (priv->button)->child;
474 
475  sx = sy = 0;
476 
477  if (!gtk_widget_get_has_window (child))
478  {
479  sx += child->allocation.x;
480  sy += child->allocation.y;
481  }
482 
483  gdk_window_get_root_coords (child->window, sx, sy, &sx, &sy);
484 
485  sx -= GTK_WIDGET (priv->button)->style->xthickness;
486 
487  gtk_widget_size_request (GTK_WIDGET (menu), &req);
488 
489  if (gtk_widget_get_direction (GTK_WIDGET (priv->button)) == GTK_TEXT_DIR_LTR)
490  *x = sx;
491  else
492  *x = sx + child->allocation.width - req.width;
493 
494  if(priv->active == -1 || priv->active == 0)
495  *y = sy;
496  else
497  *y = sy - ((req.height / priv->num_items) * (priv->active - 1));
498 
499  *push_in = FALSE;
500 }
501 
502 
503 static void
504 button_getsize_cb (GtkWidget *widget, GtkAllocation *allocation, gpointer *user_data)
505 {
506  GncCombott *combott = GNC_COMBOTT (user_data);
507  GncCombottPrivate *priv = GNC_COMBOTT_GET_PRIVATE (combott);
508 
509  priv->width = allocation->width;
510  priv->height = allocation->height;
511  priv->x = allocation->x;
512  priv->y = allocation->y;
513 }
514 
515 
516 static void
517 menu_getsize_cb (GtkWidget *widget, GtkAllocation *allocation, gpointer *user_data)
518 {
519  GncCombott *combott = GNC_COMBOTT (user_data);
520  GncCombottPrivate *priv = GNC_COMBOTT_GET_PRIVATE (combott);
521 
522  /* Set the menu width */
523  gtk_widget_set_size_request (widget, priv->width - 6, allocation->height);
524 }
525 
526 
527 static gboolean
528 which_tooltip_cb (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data)
529 {
530  gchar *text = "";
531 
532  GncCombott *combott = GNC_COMBOTT (user_data);
533  GncCombottPrivate *priv = GNC_COMBOTT_GET_PRIVATE (combott);
534 
535  if(!priv->active == 0)
536  {
537  gtk_tree_model_get( priv->model, &priv->active_iter, priv->tip_col, &text, -1 );
538  if(g_strcmp0(text, "") && (text != NULL))
539  {
540  gchar *label = "";
541  gtk_tooltip_set_text (tooltip, text);
542  g_free(text);
543  return TRUE;
544  }
545  else
546  {
547  g_free(text);
548  return FALSE;
549  }
550  }
551  return FALSE;
552 }
553 
554 
555 static gboolean
556 button_press_cb (GtkWidget *widget, GdkEvent *event, gpointer *user_data )
557 {
558  GncCombott *combott = GNC_COMBOTT (user_data);
559  GncCombottPrivate *priv = GNC_COMBOTT_GET_PRIVATE (combott);
560 
561  if(priv->model != NULL)
562  {
563  if (event->type == GDK_BUTTON_PRESS)
564  {
565  GdkEventButton *bevent = (GdkEventButton *) event;
566 
567  gtk_menu_popup (GTK_MENU (priv->menu),
568  NULL, NULL,
569  gctt_combott_menu_position, combott,
570  bevent->button, bevent->time);
571 
572  /* Tell calling code that we have handled this event; the buck
573  * stops here. */
574  return TRUE;
575  }
576  }
577  /* Tell calling code that we have not handled this event; pass it on. */
578  return FALSE;
579 }
580 
581 
582 static void
583 menuitem_response_cb (GtkMenuItem *item, gpointer *user_data )
584 {
585  const gchar *label_text;
586  GtkTreeIter iter, iter_now = {0, NULL, NULL, NULL};
587  gboolean valid;
588  gint active = 1;
589  gint active_now = 1;
590 
591  GncCombott *combott = GNC_COMBOTT (user_data);
592  GncCombottPrivate *priv = GNC_COMBOTT_GET_PRIVATE (combott);
593 
594  label_text = gtk_menu_item_get_label (item);
595 
596  /* Set the button Label */
597  gtk_label_set_text(GTK_LABEL(priv->label), label_text);
598  gtk_misc_set_alignment (GTK_MISC(priv->label), 0, 0.5);
599 
600  /* Get the coresponding entry in the list store */
601  valid = gtk_tree_model_get_iter_first (priv->model, &iter);
602  while (valid)
603  {
604  /* Walk through the list, reading each row */
605  gchar *str_data;
606  gchar *tip_data;
607  gtk_tree_model_get (priv->model, &iter,
608  priv->text_col, &str_data,
609  priv->tip_col, &tip_data,
610  -1);
611  if(!g_strcmp0(str_data, label_text))
612  {
613  active_now = active;
614  iter_now = iter;
615  }
616 
617  g_free (str_data);
618  g_free (tip_data);
619  active ++;
620  valid = gtk_tree_model_iter_next (priv->model, &iter);
621  }
622 
623  /* Emit Changed signal if we have selected a new entry */
624  if(priv->active != active_now)
625  {
626  priv->active = active_now;
627  priv->active_iter = iter_now;
628 
629  g_signal_emit (combott, combott_signals[CHANGED], 0);
630  }
631 }
632 
633 
634 GncCombott *
635 gnc_combott_new (void)
636 {
637  GObject *hbox;
638  hbox = g_object_new (GNC_TYPE_COMBOTT, NULL);
639  return GNC_COMBOTT (hbox);
640 }
641 
642 
643 gint
644 gnc_combott_get_active (GncCombott *combott)
645 {
646  GncCombottPrivate *priv;
647  gint result;
648 
649  g_return_val_if_fail (GNC_IS_COMBOTT (combott), 0);
650 
651  priv = GNC_COMBOTT_GET_PRIVATE (combott);
652 
653  result = priv->active - 1;
654 
655  return result;
656 }
657 
658 
659 void
660 gnc_combott_set_active (GncCombott *combott, gint index)
661 {
662  GncCombottPrivate *priv;
663  GtkTreeIter iter;
664  gboolean valid = TRUE;
665  gint active = 1;
666  gint num = 1;
667 
668  g_return_if_fail (GNC_IS_COMBOTT (combott));
669  g_return_if_fail (index >= -1);
670 
671  priv = GNC_COMBOTT_GET_PRIVATE (combott);
672 
673  /* Do we have a valid model */
674  if (priv->model != NULL)
675  {
676  /* Is index the same as current option */
677  if(index + 1 != priv->active)
678  {
679  /* Set button label to blank for no selection */
680  if(index == -1)
681  {
682  priv->active = 0;
683  gtk_label_set_text(GTK_LABEL(priv->label), "");
684  g_signal_emit (combott, combott_signals[CHANGED], 0);
685  }
686  else
687  {
688  /* Get the coresponding entry in the list store */
689  valid = gtk_tree_model_get_iter_first (priv->model, &iter);
690  while (valid)
691  {
692  /* Walk through the list, reading each row */
693  gchar *str_data;
694  gchar *tip_data;
695  /* Make sure you terminate calls to gtk_tree_model_get()
696  * with a '-1' value */
697  gtk_tree_model_get (priv->model, &iter,
698  priv->text_col, &str_data,
699  priv->tip_col, &tip_data,
700  -1);
701 
702  if(index + 1 == active)
703  {
704  priv->active = index + 1;
705  priv->active_iter = iter;
706  gtk_label_set_text(GTK_LABEL(priv->label), str_data);
707  gtk_misc_set_alignment (GTK_MISC(priv->label), 0, 0.5);
708  g_signal_emit (combott, combott_signals[CHANGED], 0);
709  }
710 
711  g_free (str_data);
712  g_free (tip_data);
713  active ++;
714  valid = gtk_tree_model_iter_next (priv->model, &iter);
715  }
716  }
717  }
718  }
719 }