GnuCash  5.6-150-g038405b370+
gnucash-item-list.c
1 /********************************************************************\
2  * gnucash-item-list.c -- A scrollable list box *
3  * *
4  * Initial copyright not recorded. *
5  * Copyright (c) 2006 David Hampton <hampton@employees.org> *
6  * *
7  * This program is free software; you can redistribute it and/or *
8  * modify it under the terms of the GNU General Public License as *
9  * published by the Free Software Foundation; either version 2 of *
10  * the License, or (at your option) any later version. *
11  * *
12  * This program is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU General Public License*
18  * along with this program; if not, contact: *
19  * *
20  * Free Software Foundation Voice: +1-617-542-5942 *
21  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
22  * Boston, MA 02110-1301, USA gnu@gnu.org *
23  * *
24 \********************************************************************/
25 
26 /*
27  * A scrollable list box.
28  */
29 
30 #include <config.h>
31 
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
34 
35 #include "gnc-engine.h"
36 #include "gnucash-item-list.h"
37 
38 /* Item list signals */
39 enum
40 {
41  SELECT_ITEM,
42  CHANGE_ITEM,
43  ACTIVATE_ITEM,
44  LAST_SIGNAL
45 };
46 
47 static guint gnc_item_list_signals[LAST_SIGNAL];
48 
49 gboolean _gnc_item_find_selection (GtkTreeModel* model, GtkTreePath* path,
50  GtkTreeIter* iter, gpointer data);
51 
52 G_DEFINE_TYPE (GncItemList, gnc_item_list, GTK_TYPE_EVENT_BOX);
53 
54 gint
55 gnc_item_list_num_entries (GncItemList* item_list)
56 {
57  GtkTreeModel* model;
58 
59  g_return_val_if_fail (item_list != NULL, 0);
60  g_return_val_if_fail (IS_GNC_ITEM_LIST (item_list), 0);
61 
62  model = gnc_item_list_using_temp (item_list) ?
63  GTK_TREE_MODEL (item_list->temp_store) :
64  GTK_TREE_MODEL (item_list->list_store);
65  return gtk_tree_model_iter_n_children (model, NULL);
66 }
67 
68 
69 void
70 gnc_item_list_clear (GncItemList* item_list)
71 {
72  GtkTreeSelection* selection;
73 
74  g_return_if_fail (IS_GNC_ITEM_LIST (item_list));
75  g_return_if_fail (item_list->list_store != NULL);
76 
77  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (item_list->tree_view));
78 
79  g_signal_handlers_block_matched (G_OBJECT (selection), G_SIGNAL_MATCH_DATA,
80  0, 0, NULL, NULL, item_list);
81  gtk_list_store_clear (item_list->list_store);
82  g_signal_handlers_unblock_matched (G_OBJECT (selection), G_SIGNAL_MATCH_DATA,
83  0, 0, NULL, NULL, item_list);
84 }
85 
86 
87 void
88 gnc_item_list_append (GncItemList* item_list, const char* string)
89 {
90  GtkTreeIter iter;
91 
92  g_return_if_fail (IS_GNC_ITEM_LIST (item_list));
93  g_return_if_fail (item_list->list_store != NULL);
94  g_return_if_fail (string != NULL);
95  gtk_list_store_append (item_list->list_store, &iter);
96  gtk_list_store_set (item_list->list_store, &iter, 0, string, -1);
97 }
98 
99 
100 void
101 gnc_item_list_set_sort_column (GncItemList* item_list, gint column_id)
102 {
103  g_return_if_fail (IS_GNC_ITEM_LIST (item_list));
104 
105  gtk_tree_sortable_set_sort_column_id
106  (GTK_TREE_SORTABLE (item_list->list_store),
107  column_id,
108  GTK_SORT_ASCENDING);
109 }
110 
111 
112 typedef struct _findSelectionData
113 {
114  GncItemList* item_list;
115  const char* string_to_find;
116  GtkTreePath* found_path;
118 
119 gboolean
120 _gnc_item_find_selection (GtkTreeModel* model, GtkTreePath* path,
121  GtkTreeIter* iter, gpointer data)
122 {
123  FindSelectionData* to_find = (FindSelectionData*)data;
124  gchar* iterStr;
125  gboolean found;
126 
127  gtk_tree_model_get (model, iter, 0, &iterStr, -1);
128  found = g_strcmp0 (to_find->string_to_find, iterStr) == 0;
129  g_free (iterStr);
130  if (found)
131  {
132  to_find->found_path = gtk_tree_path_copy (path);
133  return TRUE;
134  }
135  return FALSE;
136 }
137 
138 gboolean
139 gnc_item_in_list (GncItemList* item_list, const char* string)
140 {
141  FindSelectionData* to_find_data;
142  gboolean result;
143 
144  g_return_val_if_fail (item_list != NULL, FALSE);
145  g_return_val_if_fail (IS_GNC_ITEM_LIST (item_list), FALSE);
146 
147  to_find_data = (FindSelectionData*)g_new0 (FindSelectionData, 1);
148  to_find_data->item_list = item_list;
149  to_find_data->string_to_find = string;
150 
151  gtk_tree_model_foreach (GTK_TREE_MODEL (item_list->list_store),
152  _gnc_item_find_selection,
153  to_find_data);
154 
155  result = (to_find_data->found_path != NULL);
156  g_free (to_find_data);
157  return result;
158 }
159 
160 
161 void
162 gnc_item_list_select (GncItemList* item_list, const char* string)
163 {
164  GtkTreeSelection* tree_sel = NULL;
165  FindSelectionData* to_find_data;
166 
167  g_return_if_fail (item_list != NULL);
168  g_return_if_fail (IS_GNC_ITEM_LIST (item_list));
169 
170  tree_sel = gtk_tree_view_get_selection (item_list->tree_view);
171 
172  if (string == NULL)
173  {
174  gtk_tree_selection_unselect_all (tree_sel);
175  return;
176  }
177 
178  to_find_data = (FindSelectionData*)g_new0 (FindSelectionData, 1);
179  to_find_data->item_list = item_list;
180  to_find_data->string_to_find = string;
181 
182  gtk_tree_model_foreach (GTK_TREE_MODEL (item_list->list_store),
183  _gnc_item_find_selection,
184  to_find_data);
185 
186  if (to_find_data->found_path != NULL)
187  {
188  gtk_tree_view_set_cursor (item_list->tree_view, to_find_data->found_path, NULL,
189  FALSE);
190  gtk_tree_path_free (to_find_data->found_path);
191 
192  gnc_item_list_show_selected (item_list);
193  }
194 
195  g_free (to_find_data);
196 }
197 
198 
199 char*
201 {
202  GtkTreeIter iter;
203  GtkTreeModel* model;
204  gchar* string;
205 
206  GtkTreeSelection *selection =
207  gtk_tree_view_get_selection (item_list->tree_view);
208  if (!gtk_tree_selection_get_selected (selection, &model, &iter))
209  return NULL;
210 
211  gtk_tree_model_get (model, &iter, 0, &string, -1);
212  return string;
213 }
214 
215 
216 void
217 gnc_item_list_show_selected (GncItemList* item_list)
218 {
219  GtkTreeSelection* selection;
220  GtkTreeIter iter;
221  GtkTreeModel* model;
222 
223  g_return_if_fail (item_list != NULL);
224  g_return_if_fail (IS_GNC_ITEM_LIST (item_list));
225 
226  selection = gtk_tree_view_get_selection (item_list->tree_view);
227 
228  if (gtk_tree_selection_get_selected (selection, &model, &iter))
229  {
230  GtkTreePath* path = gtk_tree_model_get_path (model, &iter);
231 
232  gtk_tree_view_scroll_to_cell (item_list->tree_view,
233  path, NULL, TRUE, 0.5, 0.0);
234  gtk_tree_path_free (path);
235  }
236 }
237 
238 int
239 gnc_item_list_autosize (GncItemList* item_list)
240 {
241  g_return_val_if_fail (item_list != NULL, 0);
242  g_return_val_if_fail (IS_GNC_ITEM_LIST (item_list), 0);
243 
244  return 150;
245 }
246 
247 void
248 gnc_item_list_set_temp_store (GncItemList *item_list, GtkListStore *store)
249 {
250 
251  g_return_if_fail (item_list != 0);
252 
253  item_list->temp_store = store;
254  if (store)
255  {
256  gtk_tree_view_set_model (item_list->tree_view,
257  GTK_TREE_MODEL (item_list->temp_store));
258  }
259  else
260  {
261  gtk_tree_view_set_model (item_list->tree_view,
262  GTK_TREE_MODEL (item_list->list_store));
263  item_list->temp_store = NULL;
264  }
265 }
266 
267 gboolean
268 gnc_item_list_using_temp (GncItemList *item_list)
269 {
270  return item_list && item_list->temp_store;
271 }
272 
273 GtkListStore *
274 gnc_item_list_disconnect_store (GncItemList *item_list)
275 {
276  GtkListStore *store;
277 
278  g_return_val_if_fail (item_list != NULL, NULL);
279 
280  store = GTK_LIST_STORE(gtk_tree_view_get_model (item_list->tree_view));
281 
282  gtk_tree_view_set_model (item_list->tree_view, NULL);
283 
284  return store;
285 }
286 
287 void
288 gnc_item_list_connect_store (GncItemList *item_list, GtkListStore *list_store)
289 {
290  g_return_if_fail (item_list != 0);
291 
292  gtk_tree_view_set_model (item_list->tree_view,
293  GTK_TREE_MODEL (list_store));
294 }
295 
296 static void
297 gnc_item_list_init (GncItemList* item_list)
298 {
299  item_list->scrollwin = NULL;
300  item_list->tree_view = NULL;
301  item_list->list_store = NULL;
302  item_list->temp_store = NULL;
303  item_list->cell_height = 0;
304 }
305 
306 
307 static gboolean
308 gnc_item_list_button_event (GtkWidget* widget, GdkEventButton* event,
309  gpointer data)
310 {
311  GncItemList* item_list;
312  GtkTreeIter iter;
313  GtkTreePath* path;
314  GtkTreeModel* model;
315  gchar* string;
316  gboolean success;
317 
318  g_return_val_if_fail (IS_GNC_ITEM_LIST (data), FALSE);
319 
320  item_list = GNC_ITEM_LIST (data);
321 
322  switch (event->button)
323  {
324  case 1:
325  if (!gtk_tree_view_get_path_at_pos (item_list->tree_view,
326  event->x,
327  event->y,
328  &path,
329  NULL,
330  NULL,
331  NULL))
332  {
333  return FALSE;
334  }
335 
336  gtk_tree_view_set_cursor (item_list->tree_view, path, NULL, FALSE);
337 
338  model = GTK_TREE_MODEL (item_list->list_store);
339  success = gtk_tree_model_get_iter (model, &iter, path);
340 
341  gtk_tree_path_free (path);
342 
343  if (!success)
344  return FALSE;
345 
346  gtk_tree_model_get (model, &iter, 0, &string, -1);
347 
348  g_signal_emit (G_OBJECT (item_list),
349  gnc_item_list_signals[ACTIVATE_ITEM],
350  0,
351  string);
352  g_free (string);
353  return TRUE;
354  default:
355  return FALSE;
356  }
357 
358  return FALSE;
359 }
360 
361 static gboolean
362 gnc_item_list_key_event (GtkWidget* widget, GdkEventKey* event, gpointer data)
363 {
364  GncItemList* item_list = GNC_ITEM_LIST (data);
365  gchar* string;
366  gboolean retval;
367 
368  switch (event->keyval)
369  {
370  case GDK_KEY_Return:
371  string = gnc_item_list_get_selection (item_list);
372  if (!string) // Nothing selected, might be new value
373  break; // Let the sheet deal with it.
374  g_signal_emit (G_OBJECT (item_list),
375  gnc_item_list_signals[ACTIVATE_ITEM],
376  0,
377  string);
378  g_signal_emit (G_OBJECT (item_list), gnc_item_list_signals[CHANGE_ITEM], 0,
379  string);
380  g_free (string);
381  return TRUE;
382 
383  case GDK_KEY_Page_Up:
384  case GDK_KEY_Page_Down:
385  case GDK_KEY_Up:
386  case GDK_KEY_Down:
387  case GDK_KEY_KP_Up:
388  case GDK_KEY_KP_Down:
389  case GDK_KEY_KP_Page_Up:
390  case GDK_KEY_KP_Page_Down:
391  /* These go to the clist */
392  return FALSE;
393  }
394 
395  /* These go to the sheet */
396  g_signal_stop_emission_by_name (G_OBJECT (widget), "key_press_event");
397 
398  g_signal_emit_by_name (G_OBJECT (item_list), "key_press_event", event,
399  &retval);
400 
401  return retval;
402 }
403 
404 
405 static void
406 gnc_item_list_class_init (GncItemListClass* item_list_class)
407 {
408  GObjectClass* object_class = G_OBJECT_CLASS (item_list_class);
409 
410  gtk_widget_class_set_css_name (GTK_WIDGET_CLASS(item_list_class), "gnc-id-sheet-list");
411 
412  gnc_item_list_signals[SELECT_ITEM] =
413  g_signal_new ("select_item",
414  G_OBJECT_CLASS_TYPE (object_class),
415  G_SIGNAL_RUN_LAST,
416  G_STRUCT_OFFSET (GncItemListClass, select_item),
417  NULL, NULL,
418  g_cclosure_marshal_VOID__POINTER,
419  G_TYPE_NONE, 1,
420  G_TYPE_POINTER);
421 
422  gnc_item_list_signals[CHANGE_ITEM] =
423  g_signal_new ("change_item",
424  G_OBJECT_CLASS_TYPE (object_class),
425  G_SIGNAL_RUN_LAST,
426  G_STRUCT_OFFSET (GncItemListClass, change_item),
427  NULL, NULL,
428  g_cclosure_marshal_VOID__POINTER,
429  G_TYPE_NONE, 1,
430  G_TYPE_POINTER);
431 
432  gnc_item_list_signals[ACTIVATE_ITEM] =
433  g_signal_new ("activate_item",
434  G_OBJECT_CLASS_TYPE (object_class),
435  G_SIGNAL_RUN_LAST,
436  G_STRUCT_OFFSET (GncItemListClass, activate_item),
437  NULL, NULL,
438  g_cclosure_marshal_VOID__POINTER,
439  G_TYPE_NONE, 1,
440  G_TYPE_POINTER);
441 
442  item_list_class->select_item = NULL;
443  item_list_class->change_item = NULL;
444  item_list_class->activate_item = NULL;
445 }
446 
447 static void
448 tree_view_selection_changed (GtkTreeSelection* selection,
449  gpointer data)
450 {
451  GncItemList* item_list = GNC_ITEM_LIST (data);
452  GtkTreeModel* model;
453  GtkTreeIter iter;
454  char* string;
455 
456  g_return_if_fail (data);
457  g_return_if_fail (selection);
458 
459  if (!gtk_tree_selection_get_selected (selection, &model, &iter))
460  return;
461 
462  gtk_tree_model_get (model, &iter, 0, &string, -1);
463 
464  g_signal_emit (G_OBJECT (item_list), gnc_item_list_signals[CHANGE_ITEM], 0,
465  string);
466 
467  g_free (string);
468 }
469 
470 
471 static gint
472 gnc_item_list_get_cell_height (GncItemList *item_list)
473 {
474 
475  gint min_height, nat_height;
476  gtk_cell_renderer_get_preferred_height (item_list->renderer,
477  GTK_WIDGET(item_list->tree_view),
478  &min_height,
479  &nat_height);
480 
481  return min_height;
482 }
483 
484 gint
485 gnc_item_list_get_popup_height (GncItemList *item_list)
486 {
487  GtkWidget *hsbar = gtk_scrolled_window_get_hscrollbar (GTK_SCROLLED_WINDOW(item_list->scrollwin));
488  GtkStyleContext *context = gtk_widget_get_style_context (hsbar);
489  /* Note: gtk_scrolled_window_get_overlay_scrolling (scrollwin) always returns
490  TRUE so look for style class "overlay-indicator" on the scrollbar. */
491  gboolean overlay = gtk_style_context_has_class (context, "overlay-indicator");
492  int count = gnc_item_list_num_entries (item_list);
493  int height = count * (gnc_item_list_get_cell_height (item_list) + 2);
494 
495  if (!overlay)
496  {
497  gint minh, nath;
498  gtk_widget_get_preferred_height (hsbar, &minh, &nath);
499  height = height + minh;
500  }
501  return height;
502 }
503 
504 
505 GtkWidget*
506 gnc_item_list_new (GtkListStore* list_store)
507 {
508  GtkWidget* tree_view;
509  GtkTreeViewColumn* column;
510 
511  GncItemList* item_list =
512  GNC_ITEM_LIST (g_object_new (GNC_TYPE_ITEM_LIST,
513  NULL));
514 
515  item_list->scrollwin =
516  GTK_SCROLLED_WINDOW (gtk_scrolled_window_new(NULL, NULL));
517  gtk_container_add (GTK_CONTAINER (item_list),
518  GTK_WIDGET (item_list->scrollwin));
519 
520  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (item_list->scrollwin),
521  GTK_POLICY_AUTOMATIC,
522  GTK_POLICY_AUTOMATIC);
523 
524  if (NULL == list_store)
525  list_store = gtk_list_store_new (1, G_TYPE_STRING);
526  else
527  g_object_ref (list_store);
528  tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (list_store));
529  g_object_unref (list_store);
530 
531  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree_view), FALSE);
532  gtk_tree_selection_set_mode (
533  gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)),
534  GTK_SELECTION_BROWSE);
535  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list_store),
536  0, GTK_SORT_ASCENDING);
537 
538  item_list->renderer = gtk_cell_renderer_text_new();
539  column = gtk_tree_view_column_new_with_attributes (_ ("List"),
540  item_list->renderer,
541  "text", 0,
542  NULL);
543  gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
544 
545  gtk_container_add (GTK_CONTAINER (item_list->scrollwin), tree_view);
546 
547  item_list->tree_view = GTK_TREE_VIEW (tree_view);
548  item_list->list_store = list_store;
549 
550  g_signal_connect (G_OBJECT (tree_view), "button_press_event",
551  G_CALLBACK (gnc_item_list_button_event), item_list);
552 
553  g_signal_connect (G_OBJECT (tree_view), "key_press_event",
554  G_CALLBACK (gnc_item_list_key_event), item_list);
555 
556  g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (
557  GTK_TREE_VIEW (tree_view))), "changed",
558  G_CALLBACK (tree_view_selection_changed), item_list);
559 
560  return GTK_WIDGET (item_list);
561 }
Public Declarations for GncItemList class.
All type declarations for the whole Gnucash engine.
char * gnc_item_list_get_selection(GncItemList *item_list)
Retrieve the selected string from the item_list&#39;s active GtkListStore.