1 /********************************************************************\
2  * gnc-tree-view-price.c -- GtkTreeView implementation to display   *
3  *                            prices in a GtkTreeView.              *
4  * Copyright (C) 2003,2005 David Hampton <hampton@employees.org>    *
5  *                                                                  *
6  * This program is free software; you can redistribute it and/or    *
7  * modify it under the terms of the GNU General Public License as   *
8  * published by the Free Software Foundation; either version 2 of   *
9  * the License, or (at your option) any later version.              *
10  *                                                                  *
11  * This program is distributed in the hope that it will be useful,  *
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
14  * GNU General Public License for more details.                     *
15  *                                                                  *
16  * You should have received a copy of the GNU General Public License*
17  * along with this program; if not, contact:                        *
18  *                                                                  *
19  * Free Software Foundation           Voice:  +1-617-542-5942       *
20  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
21  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
22  *                                                                  *
23 \********************************************************************/
24 
25 #include <config.h>
26 
27 #include <gtk/gtk.h>
28 #include <glib/gi18n.h>
29 #include <string.h>
30 
31 #include "gnc-tree-view.h"
32 #include "gnc-tree-model-price.h"
33 #include "gnc-tree-view-price.h"
34 
35 #include "gnc-pricedb.h"
36 #include "gnc-component-manager.h"
37 #include "gnc-engine.h"
38 #include "gnc-glib-utils.h"
39 #include "gnc-gnome-utils.h"
40 #include "gnc-icons.h"
41 #include "gnc-ui-util.h"
42 
43 
44 /** Static Globals *******************************************************/
45 
46 /* This static indicates the debugging module that this .o belongs to.  */
47 static QofLogModule log_module = GNC_MOD_GUI;
48 
49 /** Declarations *********************************************************/
50 static void gnc_tree_view_price_class_init (GncTreeViewPriceClass *klass);
51 static void gnc_tree_view_price_init (GncTreeViewPrice *view);
52 static void gnc_tree_view_price_finalize (GObject *object);
53 static void gnc_tree_view_price_destroy (GtkWidget *widget);
54 
55 typedef struct GncTreeViewPricePrivate
56 {
57     gpointer dummy;
58 } GncTreeViewPricePrivate;
59 
60 #define GNC_TREE_VIEW_PRICE_GET_PRIVATE(o)  \
61    ((GncTreeViewPricePrivate*)g_type_instance_get_private((GTypeInstance*)o, GNC_TYPE_TREE_VIEW_PRICE))
62 
63 
64 /************************************************************/
65 /*               g_object required functions                */
66 /************************************************************/
67 
68 static GObjectClass *parent_class = NULL;
69 
G_DEFINE_TYPE_WITH_PRIVATE(GncTreeViewPrice,gnc_tree_view_price,GNC_TYPE_TREE_VIEW)70 G_DEFINE_TYPE_WITH_PRIVATE(GncTreeViewPrice, gnc_tree_view_price, GNC_TYPE_TREE_VIEW)
71 
72 static void
73 gnc_tree_view_price_class_init (GncTreeViewPriceClass *klass)
74 {
75     GObjectClass *o_class;
76     GtkWidgetClass *widget_class;
77 
78     parent_class = g_type_class_peek_parent (klass);
79 
80     o_class = G_OBJECT_CLASS (klass);
81     widget_class = GTK_WIDGET_CLASS (klass);
82 
83     /* GObject signals */
84     o_class->finalize = gnc_tree_view_price_finalize;
85 
86     /* GtkWidget signals */
87     widget_class->destroy = gnc_tree_view_price_destroy;
88 }
89 
90 static void
gnc_tree_view_price_init(GncTreeViewPrice * view)91 gnc_tree_view_price_init (GncTreeViewPrice *view)
92 {
93 }
94 
95 static void
gnc_tree_view_price_finalize(GObject * object)96 gnc_tree_view_price_finalize (GObject *object)
97 {
98     ENTER("view %p", object);
99     gnc_leave_return_if_fail (object != NULL);
100     gnc_leave_return_if_fail (GNC_IS_TREE_VIEW_PRICE (object));
101 
102     if (G_OBJECT_CLASS (parent_class)->finalize)
103         (* G_OBJECT_CLASS (parent_class)->finalize) (object);
104     LEAVE(" ");
105 }
106 
107 static void
gnc_tree_view_price_destroy(GtkWidget * widget)108 gnc_tree_view_price_destroy (GtkWidget *widget)
109 {
110     ENTER("view %p", widget);
111     gnc_leave_return_if_fail (widget != NULL);
112     gnc_leave_return_if_fail (GNC_IS_TREE_VIEW_PRICE (widget));
113 
114     if (GTK_WIDGET_CLASS (parent_class)->destroy)
115         (* GTK_WIDGET_CLASS (parent_class)->destroy) (widget);
116     LEAVE(" ");
117 }
118 
119 
120 /************************************************************/
121 /*                      sort functions                      */
122 /************************************************************/
123 
124 static gboolean
get_prices(GtkTreeModel * f_model,GtkTreeIter * f_iter_a,GtkTreeIter * f_iter_b,GNCPrice ** price_a,GNCPrice ** price_b)125 get_prices (GtkTreeModel *f_model,
126             GtkTreeIter *f_iter_a,
127             GtkTreeIter *f_iter_b,
128             GNCPrice **price_a,
129             GNCPrice **price_b)
130 {
131     GncTreeModelPrice *model;
132     GtkTreeModel *tree_model;
133     GtkTreeIter iter_a, iter_b;
134 
135     tree_model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(f_model));
136     model = GNC_TREE_MODEL_PRICE(tree_model);
137 
138     gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER(f_model),
139             &iter_a,
140             f_iter_a);
141 
142     /* The iters must point to prices for this to be meaningful */
143     if (!gnc_tree_model_price_iter_is_price (model, &iter_a))
144         return FALSE;
145 
146     gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER(f_model),
147             &iter_b,
148             f_iter_b);
149 
150     *price_a = gnc_tree_model_price_get_price (model, &iter_a);
151     *price_b = gnc_tree_model_price_get_price (model, &iter_b);
152     return TRUE;
153 }
154 
155 static gint
sort_ns_or_cm(GtkTreeModel * f_model,GtkTreeIter * f_iter_a,GtkTreeIter * f_iter_b)156 sort_ns_or_cm (GtkTreeModel *f_model,
157                GtkTreeIter *f_iter_a,
158                GtkTreeIter *f_iter_b)
159 {
160     GncTreeModelPrice *model;
161     GtkTreeModel *tree_model;
162     GtkTreeIter iter_a, iter_b;
163     gnc_commodity_namespace *ns_a, *ns_b;
164     gnc_commodity *comm_a, *comm_b;
165 
166     tree_model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(f_model));
167     model = GNC_TREE_MODEL_PRICE(tree_model);
168 
169     gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER(f_model),
170             &iter_a,
171             f_iter_a);
172     gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER(f_model),
173             &iter_b,
174             f_iter_b);
175 
176     if (gnc_tree_model_price_iter_is_namespace (model, &iter_a))
177     {
178         ns_a = gnc_tree_model_price_get_namespace (model, &iter_a);
179         ns_b = gnc_tree_model_price_get_namespace (model, &iter_b);
180         return safe_utf8_collate (gnc_commodity_namespace_get_gui_name (ns_a),
181                                   gnc_commodity_namespace_get_gui_name (ns_b));
182     }
183 
184     comm_a = gnc_tree_model_price_get_commodity (model, &iter_a);
185     comm_b = gnc_tree_model_price_get_commodity (model, &iter_b);
186     return safe_utf8_collate (gnc_commodity_get_mnemonic (comm_a),
187                               gnc_commodity_get_mnemonic (comm_b));
188 }
189 
190 static gint
default_sort(GNCPrice * price_a,GNCPrice * price_b)191 default_sort (GNCPrice *price_a, GNCPrice *price_b)
192 {
193     gnc_commodity *curr_a, *curr_b;
194     time64 time_a, time_b;
195     gint result;
196 
197     /* Primary sort (i.e. commodity name) handled by the tree structure.  */
198 
199     /* secondary sort: currency */
200     curr_a = gnc_price_get_currency (price_a);
201     curr_b = gnc_price_get_currency (price_b);
202 
203     result = safe_utf8_collate (gnc_commodity_get_namespace (curr_a),
204                                 gnc_commodity_get_namespace (curr_b));
205     if (result != 0) return result;
206 
207     result = safe_utf8_collate (gnc_commodity_get_mnemonic (curr_a),
208                                 gnc_commodity_get_mnemonic (curr_b));
209     if (result != 0) return result;
210 
211     /* tertiary sort: time */
212     time_a = gnc_price_get_time64 (price_a);
213     time_b = gnc_price_get_time64 (price_b);
214     result = time_a < time_b ? -1 : time_a > time_b ? 1 : 0;
215     if (result)
216         /* Reverse the result to present the most recent quote first. */
217         return -result;
218 
219     /* last sort: value */
220     return gnc_numeric_compare (gnc_price_get_value (price_a),
221                                 gnc_price_get_value (price_b));
222 }
223 
224 static gint
sort_by_name(GtkTreeModel * f_model,GtkTreeIter * f_iter_a,GtkTreeIter * f_iter_b,gpointer user_data)225 sort_by_name (GtkTreeModel *f_model,
226               GtkTreeIter *f_iter_a,
227               GtkTreeIter *f_iter_b,
228               gpointer user_data)
229 {
230     GNCPrice *price_a, *price_b;
231 
232     if (!get_prices (f_model, f_iter_a, f_iter_b, &price_a, &price_b))
233         return sort_ns_or_cm (f_model, f_iter_a, f_iter_b);
234 
235     return default_sort (price_a, price_b);
236 }
237 
238 static gint
sort_by_date(GtkTreeModel * f_model,GtkTreeIter * f_iter_a,GtkTreeIter * f_iter_b,gpointer user_data)239 sort_by_date (GtkTreeModel *f_model,
240               GtkTreeIter *f_iter_a,
241               GtkTreeIter *f_iter_b,
242               gpointer user_data)
243 {
244     GNCPrice *price_a, *price_b;
245     time64 time_a, time_b;
246     gboolean result;
247 
248     if (!get_prices (f_model, f_iter_a, f_iter_b, &price_a, &price_b))
249         return sort_ns_or_cm (f_model, f_iter_a, f_iter_b);
250 
251     /* sort by time first */
252     time_a = gnc_price_get_time64 (price_a);
253     time_b = gnc_price_get_time64 (price_b);
254     result = time_a < time_b ? -1 : time_a > time_b ? 1 : 0;
255     if (result)
256         /* Reverse the result to present the most recent quote first. */
257         return -result;
258 
259     return default_sort (price_a, price_b);
260 }
261 
262 static gint
sort_by_source(GtkTreeModel * f_model,GtkTreeIter * f_iter_a,GtkTreeIter * f_iter_b,gpointer user_data)263 sort_by_source (GtkTreeModel *f_model,
264                 GtkTreeIter *f_iter_a,
265                 GtkTreeIter *f_iter_b,
266                 gpointer user_data)
267 {
268     GNCPrice *price_a, *price_b;
269     gint result;
270 
271     if (!get_prices (f_model, f_iter_a, f_iter_b, &price_a, &price_b))
272         return sort_ns_or_cm (f_model, f_iter_a, f_iter_b);
273 
274     /* sort by source first */
275     result = gnc_price_get_source (price_a) < gnc_price_get_source (price_b);
276     if (result != 0)
277         return result;
278 
279     return default_sort (price_a, price_b);
280 }
281 
282 static gint
sort_by_type(GtkTreeModel * f_model,GtkTreeIter * f_iter_a,GtkTreeIter * f_iter_b,gpointer user_data)283 sort_by_type (GtkTreeModel *f_model,
284               GtkTreeIter *f_iter_a,
285               GtkTreeIter *f_iter_b,
286               gpointer user_data)
287 {
288     GNCPrice *price_a, *price_b;
289     gint result;
290 
291     if (!get_prices (f_model, f_iter_a, f_iter_b, &price_a, &price_b))
292         return sort_ns_or_cm (f_model, f_iter_a, f_iter_b);
293 
294     /* sort by source first */
295     result = safe_utf8_collate (gnc_price_get_typestr (price_a),
296                                 gnc_price_get_typestr (price_b));
297     if (result != 0)
298         return result;
299 
300     return default_sort (price_a, price_b);
301 }
302 
303 static gint
sort_by_value(GtkTreeModel * f_model,GtkTreeIter * f_iter_a,GtkTreeIter * f_iter_b,gpointer user_data)304 sort_by_value (GtkTreeModel *f_model,
305                GtkTreeIter *f_iter_a,
306                GtkTreeIter *f_iter_b,
307                gpointer user_data)
308 {
309     gnc_commodity *comm_a, *comm_b;
310     GNCPrice *price_a, *price_b;
311     gboolean result;
312     gint value;
313 
314     if (!get_prices (f_model, f_iter_a, f_iter_b, &price_a, &price_b))
315         return sort_ns_or_cm (f_model, f_iter_a, f_iter_b);
316 
317     /*
318      * Sorted by commodity because of the tree structure.  Now sort by
319      * currency so we're only comparing numbers in the same currency
320      * denomination.
321      */
322     comm_a = gnc_price_get_currency (price_a);
323     comm_b = gnc_price_get_currency (price_b);
324     if (comm_a && comm_b)
325     {
326         value = safe_utf8_collate (gnc_commodity_get_namespace (comm_a),
327                                    gnc_commodity_get_namespace (comm_b));
328         if (value != 0)
329             return value;
330         value = safe_utf8_collate (gnc_commodity_get_mnemonic (comm_a),
331                                    gnc_commodity_get_mnemonic (comm_b));
332         if (value != 0)
333             return value;
334     }
335 
336     /*
337      * Now do the actual price comparison now we're sure that its an
338      * apples to apples comparison.
339      */
340     result = gnc_numeric_compare (gnc_price_get_value (price_a),
341                                   gnc_price_get_value (price_b));
342     if (result)
343         return result;
344 
345     return default_sort (price_a, price_b);
346 }
347 
348 
349 /************************************************************/
350 /*                    New View Creation                     */
351 /************************************************************/
352 
353 /*
354  * Create a new price tree view with (optional) top level root node.
355  * This view will be based on a model that is common to all view of
356  * the same set of books, but will have its own private filter on that
357  * model.
358  */
359 GtkTreeView *
gnc_tree_view_price_new(QofBook * book,const gchar * first_property_name,...)360 gnc_tree_view_price_new (QofBook *book,
361                          const gchar *first_property_name,
362                          ...)
363 {
364     GncTreeView *view;
365     GtkTreeModel *model, *f_model, *s_model;
366     GtkTreeViewColumn *col;
367     GNCPriceDB *price_db;
368     va_list var_args;
369     const gchar *sample_text;
370     gchar *sample_text2;
371 
372     ENTER(" ");
373     /* Create/get a pointer to the existing model for this set of books. */
374     price_db = gnc_pricedb_get_db(book);
375     model = gnc_tree_model_price_new (book, price_db);
376 
377     /* Set up the view private filter on the common model. */
378     f_model = gtk_tree_model_filter_new (model, NULL);
379     g_object_unref(G_OBJECT(model));
380     s_model = gtk_tree_model_sort_new_with_model (f_model);
381     g_object_unref(G_OBJECT(f_model));
382 
383     /* Create our view */
384     view = g_object_new (GNC_TYPE_TREE_VIEW_PRICE,
385                          "name", "gnc-id-price-tree", NULL);
386     gtk_tree_view_set_model (GTK_TREE_VIEW (view), s_model);
387     g_object_unref(G_OBJECT(s_model));
388 
389     DEBUG("model ref count is %d",   G_OBJECT(model)->ref_count);
390     DEBUG("f_model ref count is %d", G_OBJECT(f_model)->ref_count);
391     DEBUG("s_model ref count is %d", G_OBJECT(s_model)->ref_count);
392 
393     sample_text = gnc_commodity_get_printname(gnc_default_currency());
394     sample_text2 = g_strdup_printf("%s%s", sample_text, sample_text);
395     gnc_tree_view_add_text_column (
396               view, _("Security"), "security", NULL, sample_text2,
397               GNC_TREE_MODEL_PRICE_COL_COMMODITY,
398               GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS,
399               sort_by_name);
400     g_free(sample_text2);
401     col = gnc_tree_view_add_text_column (
402               view, _("Currency"), "currency", NULL, sample_text,
403               GNC_TREE_MODEL_PRICE_COL_CURRENCY,
404               GNC_TREE_MODEL_PRICE_COL_VISIBILITY,
405               sort_by_name);
406     g_object_set_data(G_OBJECT(col), DEFAULT_VISIBLE, GINT_TO_POINTER(1));
407     col = gnc_tree_view_add_text_column (
408               view, _("Date"), "date", NULL, "2005-05-20",
409               GNC_TREE_MODEL_PRICE_COL_DATE,
410               GNC_TREE_MODEL_PRICE_COL_VISIBILITY,
411               sort_by_date);
412     g_object_set_data(G_OBJECT(col), DEFAULT_VISIBLE, GINT_TO_POINTER(1));
413     col = gnc_tree_view_add_text_column (
414               view, _("Source"), "source", NULL, "Finance::Quote",
415               GNC_TREE_MODEL_PRICE_COL_SOURCE,
416               GNC_TREE_MODEL_PRICE_COL_VISIBILITY,
417               sort_by_source);
418     g_object_set_data(G_OBJECT(col), DEFAULT_VISIBLE, GINT_TO_POINTER(1));
419     col = gnc_tree_view_add_text_column (
420               view, _("Type"), "type", NULL, "last",
421               GNC_TREE_MODEL_PRICE_COL_TYPE,
422               GNC_TREE_MODEL_PRICE_COL_VISIBILITY,
423               sort_by_type);
424     g_object_set_data(G_OBJECT(col), DEFAULT_VISIBLE, GINT_TO_POINTER(1));
425     col = gnc_tree_view_add_numeric_column (
426               view, _("Price"), "price", "100.00000",
427               GNC_TREE_MODEL_PRICE_COL_VALUE,
428               GNC_TREE_VIEW_COLUMN_COLOR_NONE,
429               GNC_TREE_MODEL_PRICE_COL_VISIBILITY,
430               sort_by_value);
431     g_object_set_data(G_OBJECT(col), DEFAULT_VISIBLE, GINT_TO_POINTER(1));
432 
433     gnc_tree_view_configure_columns(view);
434 
435     /* Set properties */
436     va_start (var_args, first_property_name);
437     g_object_set_valist (G_OBJECT(view), first_property_name, var_args);
438     va_end (var_args);
439 
440     /* Sort on the commodity column by default. This allows for a consistent
441      * sort if commodities are removed and re-added from the model. */
442     if (!gtk_tree_sortable_get_sort_column_id(GTK_TREE_SORTABLE(s_model),
443             NULL, NULL))
444     {
445         gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(s_model),
446                                              GNC_TREE_MODEL_PRICE_COL_COMMODITY,
447                                              GTK_SORT_ASCENDING);
448     }
449 
450     gtk_widget_show(GTK_WIDGET(view));
451     LEAVE(" %p", view);
452     return GTK_TREE_VIEW(view);
453 }
454 
455 /************************************************************/
456 /*                   Auxiliary Functions                    */
457 /************************************************************/
458 
459 #define debug_path(fn, path) {				\
460     gchar *path_string = gtk_tree_path_to_string(path); \
461     fn("tree path %s", path_string);			\
462     g_free(path_string);				\
463   }
464 
465 #if 0 /* Not Used */
466 static gboolean
467 gnc_tree_view_price_get_iter_from_price (GncTreeViewPrice *view,
468         GNCPrice *price,
469         GtkTreeIter *s_iter)
470 {
471     GtkTreeModel *model, *f_model, *s_model;
472     GtkTreeIter iter, f_iter;
473 
474     g_return_val_if_fail(GNC_IS_TREE_VIEW_PRICE(view), FALSE);
475     g_return_val_if_fail(price != NULL, FALSE);
476     g_return_val_if_fail(s_iter != NULL, FALSE);
477 
478     ENTER("view %p, price %p", view, price);
479 
480     /* Reach down to the real model and get an iter for this price */
481     s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
482     f_model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(s_model));
483     model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(f_model));
484     if (!gnc_tree_model_price_get_iter_from_price (GNC_TREE_MODEL_PRICE(model), price, &iter))
485     {
486         LEAVE("model_get_iter_from_price failed");
487         return FALSE;
488     }
489 
490     /* convert back to a sort iter */
491     gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER(f_model),
492             &f_iter, &iter);
493     gtk_tree_model_sort_convert_child_iter_to_iter (GTK_TREE_MODEL_SORT(s_model),
494             s_iter, &f_iter);
495     LEAVE(" ");
496     return TRUE;
497 }
498 #endif /* Not Used */
499 
500 /************************************************************/
501 /*            Price Tree View Filter Functions            */
502 /************************************************************/
503 
504 /************************************************************/
505 /*          Price Tree View Visibility Filter           */
506 /************************************************************/
507 
508 typedef struct
509 {
510     gnc_tree_view_price_ns_filter_func user_ns_fn;
511     gnc_tree_view_price_cm_filter_func user_cm_fn;
512     gnc_tree_view_price_pc_filter_func user_pc_fn;
513     gpointer                           user_data;
514     GDestroyNotify                     user_destroy;
515 } filter_user_data;
516 
517 static void
gnc_tree_view_price_filter_destroy(gpointer data)518 gnc_tree_view_price_filter_destroy (gpointer data)
519 {
520     filter_user_data *fd = data;
521 
522     if (fd->user_destroy)
523         fd->user_destroy(fd->user_data);
524     g_free(fd);
525 }
526 
527 static gboolean
gnc_tree_view_price_filter_helper(GtkTreeModel * model,GtkTreeIter * iter,gpointer data)528 gnc_tree_view_price_filter_helper (GtkTreeModel *model,
529                                    GtkTreeIter *iter,
530                                    gpointer data)
531 {
532     gnc_commodity_namespace *name_space;
533     gnc_commodity *commodity;
534     GNCPrice *price;
535     filter_user_data *fd = data;
536 
537     g_return_val_if_fail (GNC_IS_TREE_MODEL_PRICE (model), FALSE);
538     g_return_val_if_fail (iter != NULL, FALSE);
539 
540     if (gnc_tree_model_price_iter_is_namespace (GNC_TREE_MODEL_PRICE(model), iter))
541     {
542         if (fd->user_ns_fn)
543         {
544             name_space = gnc_tree_model_price_get_namespace (GNC_TREE_MODEL_PRICE(model), iter);
545             return fd->user_ns_fn(name_space, fd->user_data);
546         }
547         return TRUE;
548     }
549 
550     if (gnc_tree_model_price_iter_is_commodity (GNC_TREE_MODEL_PRICE(model), iter))
551     {
552         if (fd->user_cm_fn)
553         {
554             commodity = gnc_tree_model_price_get_commodity (GNC_TREE_MODEL_PRICE(model), iter);
555             return fd->user_cm_fn(commodity, fd->user_data);
556         }
557         return TRUE;
558     }
559 
560     if (gnc_tree_model_price_iter_is_price (GNC_TREE_MODEL_PRICE(model), iter))
561     {
562         if (fd->user_pc_fn)
563         {
564             price = gnc_tree_model_price_get_price (GNC_TREE_MODEL_PRICE(model), iter);
565             return fd->user_pc_fn(price, fd->user_data);
566         }
567         return TRUE;
568     }
569 
570     return FALSE;
571 }
572 
573 /*
574  * Set an GtkTreeModel visible filter on this price.  This filter will be
575  * called for each price that the tree is about to show, and the
576  * price will be passed to the callback function.
577  */
578 void
gnc_tree_view_price_set_filter(GncTreeViewPrice * view,gnc_tree_view_price_ns_filter_func ns_func,gnc_tree_view_price_cm_filter_func cm_func,gnc_tree_view_price_pc_filter_func pc_func,gpointer data,GDestroyNotify destroy)579 gnc_tree_view_price_set_filter (GncTreeViewPrice *view,
580                                 gnc_tree_view_price_ns_filter_func ns_func,
581                                 gnc_tree_view_price_cm_filter_func cm_func,
582                                 gnc_tree_view_price_pc_filter_func pc_func,
583                                 gpointer data,
584                                 GDestroyNotify destroy)
585 {
586     GtkTreeModel *f_model, *s_model;
587     filter_user_data *fd = data;
588 
589     ENTER("view %p, ns func %p, cm func %p, pc func %p, data %p, destroy %p",
590           view, ns_func, cm_func, pc_func, data, destroy);
591 
592     g_return_if_fail(GNC_IS_TREE_VIEW_PRICE(view));
593     g_return_if_fail((ns_func != NULL) || (cm_func != NULL));
594 
595     fd = g_malloc(sizeof(filter_user_data));
596     fd->user_ns_fn   = ns_func;
597     fd->user_cm_fn   = cm_func;
598     fd->user_pc_fn   = pc_func;
599     fd->user_data    = data;
600     fd->user_destroy = destroy;
601 
602     s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
603     f_model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(s_model));
604 
605     /* disconnect model from view */
606     g_object_ref (G_OBJECT(s_model));
607     gtk_tree_view_set_model (GTK_TREE_VIEW(view), NULL);
608 
609     gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (f_model),
610                                             gnc_tree_view_price_filter_helper,
611                                             fd,
612                                             gnc_tree_view_price_filter_destroy);
613 
614     /* Whack any existing levels. The top two levels have been created
615      * before this routine can be called.  Unfortunately, if the just
616      * applied filter filters out all the nodes in the tree, the gtk
617      * code throws a critical error.  This occurs when there are no
618      * prices in the price database.  Once the very first price has been
619      * added this error message goes away. */
620     gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (f_model));
621 
622     /* connect model to view */
623     gtk_tree_view_set_model (GTK_TREE_VIEW(view), s_model);
624     g_object_unref (G_OBJECT(s_model));
625 
626     LEAVE(" ");
627 }
628 
629 /************************************************************/
630 /*           Price Tree View Get/Set Functions            */
631 /************************************************************/
632 
633 /*
634  * Retrieve the selected price from an price tree view.  The
635  * price tree must be in single selection mode.
636  */
637 GNCPrice *
gnc_tree_view_price_get_selected_price(GncTreeViewPrice * view)638 gnc_tree_view_price_get_selected_price (GncTreeViewPrice *view)
639 {
640     GtkTreeSelection *selection;
641     GtkTreeModel *model, *f_model, *s_model;
642     GtkTreeIter iter, f_iter, s_iter;
643     GNCPrice *price;
644 
645     ENTER("view %p", view);
646     g_return_val_if_fail (GNC_IS_TREE_VIEW_PRICE (view), NULL);
647 
648     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
649     if (!gtk_tree_selection_get_selected (selection, &s_model, &s_iter))
650     {
651         LEAVE("no price, get_selected failed");
652         return FALSE;
653     }
654 
655     gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (s_model),
656             &f_iter, &s_iter);
657 
658     f_model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(s_model));
659     gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (f_model),
660             &iter, &f_iter);
661 
662     model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(f_model));
663     price = gnc_tree_model_price_get_price (GNC_TREE_MODEL_PRICE(model),
664                                             &iter);
665     LEAVE("price %p", price);
666     return price;
667 }
668 
669 /*
670  * Selects a single price in the price tree view.  The price
671  * tree must be in single selection mode.
672  */
673 void
gnc_tree_view_price_set_selected_price(GncTreeViewPrice * view,GNCPrice * price)674 gnc_tree_view_price_set_selected_price (GncTreeViewPrice *view,
675                                         GNCPrice *price)
676 {
677     GtkTreeModel *model, *f_model, *s_model;
678     GtkTreePath *path, *f_path, *s_path, *parent_path;
679     GtkTreeSelection *selection;
680 
681     ENTER("view %p, price %p", view, price);
682 
683     /* Clear any existing selection. */
684     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
685     gtk_tree_selection_unselect_all (selection);
686 
687     if (price == NULL)
688         return;
689 
690     s_model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
691     f_model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(s_model));
692     model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (f_model));
693 
694     path = gnc_tree_model_price_get_path_from_price (GNC_TREE_MODEL_PRICE(model), price);
695     if (path == NULL)
696     {
697         LEAVE("get_path_from_price failed");
698         return;
699     }
700     debug_path(DEBUG, path);
701 
702     f_path = gtk_tree_model_filter_convert_child_path_to_path (GTK_TREE_MODEL_FILTER (f_model),
703              path);
704     gtk_tree_path_free(path);
705     if (f_path == NULL)
706     {
707         LEAVE("no filter path");
708         return;
709     }
710     debug_path(DEBUG, f_path);
711 
712     s_path = gtk_tree_model_sort_convert_child_path_to_path (GTK_TREE_MODEL_SORT (s_model),
713              f_path);
714     gtk_tree_path_free(f_path);
715     if (s_path == NULL)
716     {
717         LEAVE("no sort path");
718         return;
719     }
720 
721     /* gtk_tree_view requires that a row be visible before it can be selected */
722     parent_path = gtk_tree_path_copy (s_path);
723     if (gtk_tree_path_up (parent_path))
724     {
725         /* This function is misnamed.  It expands the actual item
726          * specified, not the path to the item specified. I.E. It expands
727          * one level too many, thus the get of the parent. */
728         gtk_tree_view_expand_to_path(GTK_TREE_VIEW(view), parent_path);
729     }
730     gtk_tree_path_free(parent_path);
731 
732     gtk_tree_selection_select_path (selection, s_path);
733     gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(view), s_path, NULL, FALSE, 0.0, 0.0);
734     debug_path(LEAVE, s_path);
735     gtk_tree_path_free(s_path);
736 }
737 
738 /*
739  * This helper function is called once for each row in the tree view
740  * that is currently selected.  Its task is to add the corresponding
741  * price to the end of a glist.
742  */
743 static void
get_selected_prices_helper(GtkTreeModel * s_model,GtkTreePath * s_path,GtkTreeIter * s_iter,gpointer data)744 get_selected_prices_helper (GtkTreeModel *s_model,
745                             GtkTreePath *s_path,
746                             GtkTreeIter *s_iter,
747                             gpointer data)
748 {
749     GList **return_list = data;
750     GtkTreeModel *model, *f_model;
751     GtkTreeIter iter, f_iter;
752     GNCPrice *price;
753 
754     gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (s_model),
755             &f_iter, s_iter);
756 
757     f_model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(s_model));
758     gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (f_model),
759             &iter, &f_iter);
760 
761     model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(f_model));
762     price = gnc_tree_model_price_get_price (GNC_TREE_MODEL_PRICE(model),
763                                             &iter);
764     if (price)
765         *return_list = g_list_append(*return_list, price);
766 }
767 
768 /*
769  * Given a price tree view, return a list of the selected prices. The
770  * price tree must be in multiple selection mode.
771  *
772  * Note: It is the responsibility of the caller to free the returned
773  * list.
774  */
775 GList *
gnc_tree_view_price_get_selected_prices(GncTreeViewPrice * view)776 gnc_tree_view_price_get_selected_prices (GncTreeViewPrice *view)
777 {
778     GtkTreeSelection *selection;
779     GList *return_list = NULL;
780 
781     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
782     gtk_tree_selection_selected_foreach(selection, get_selected_prices_helper, &return_list);
783     return return_list;
784 }
785 
786 static void
get_selected_commodity_helper(GtkTreeModel * s_model,GtkTreePath * s_path,GtkTreeIter * s_iter,gpointer data)787 get_selected_commodity_helper (GtkTreeModel *s_model,
788                                GtkTreePath *s_path,
789                                GtkTreeIter *s_iter,
790                                gpointer data)
791 {
792     GList **return_list = data;
793     GtkTreeModel *model, *f_model;
794     GtkTreeIter iter, f_iter;
795     gnc_commodity *commodity;
796 
797     gtk_tree_model_sort_convert_iter_to_child_iter (GTK_TREE_MODEL_SORT (s_model),
798             &f_iter, s_iter);
799 
800     f_model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(s_model));
801     gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (f_model),
802             &iter, &f_iter);
803 
804     model = gtk_tree_model_filter_get_model(GTK_TREE_MODEL_FILTER(f_model));
805     commodity = gnc_tree_model_price_get_commodity (GNC_TREE_MODEL_PRICE(model), &iter);
806 
807     if (commodity)
808         *return_list = g_list_append(*return_list, commodity);
809 }
810 
811 /*
812  * Given a price tree view, return a list of the selected rows that have
813  * commodities but are not prices, the parent rows for prices. The
814  * price tree must be in multiple selection mode.
815  *
816  * Note: It is the responsibility of the caller to free the returned
817  * list.
818  */
819 GList *
gnc_tree_view_price_get_selected_commodities(GncTreeViewPrice * view)820 gnc_tree_view_price_get_selected_commodities (GncTreeViewPrice *view)
821 {
822     GtkTreeSelection *selection;
823     GList *return_list = NULL;
824 
825     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(view));
826     gtk_tree_selection_selected_foreach (selection, get_selected_commodity_helper, &return_list);
827     return return_list;
828 }
829