1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2014 Maxime DOYEN
4 * This file is part of HomeBank.
6 * HomeBank is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * HomeBank 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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "hb-category.h"
24 /****************************************************************************/
26 /****************************************************************************/
35 /* our global datas */
36 extern struct HomeBank
*GLOBALS
;
38 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
41 da_cat_clone(Category
*src_item
)
43 Category
*new_item
= g_memdup(src_item
, sizeof(Category
));
45 DB( g_print("da_cat_clone\n") );
48 //duplicate the string
49 new_item
->name
= g_strdup(src_item
->name
);
56 da_cat_free(Category
*item
)
58 DB( g_print("da_cat_free\n") );
61 DB( g_print(" => %d, %s\n", item
->key
, item
->name
) );
72 DB( g_print("da_cat_malloc\n") );
73 return g_malloc0(sizeof(Category
));
80 DB( g_print("da_cat_destroy\n") );
81 g_hash_table_destroy(GLOBALS
->h_cat
);
90 DB( g_print("da_cat_new\n") );
91 GLOBALS
->h_cat
= g_hash_table_new_full(g_int_hash
, g_int_equal
, (GDestroyNotify
)g_free
, (GDestroyNotify
)da_cat_free
);
93 // insert our 'no category'
94 item
= da_cat_malloc();
95 item
->name
= g_strdup("");
100 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
105 * Return value: the number of elements
110 return g_hash_table_size(GLOBALS
->h_cat
);
116 * da_cat_remove_grfunc:
118 * GRFunc to get the max id
120 * Return value: TRUE if the key/value must be removed
124 da_cat_remove_grfunc(gpointer key
, Category
*cat
, guint32
*remkey
)
126 if(cat
->key
== *remkey
|| cat
->parent
== *remkey
)
136 * remove a category from the GHashTable
138 * Return value: TRUE if the key was found and removed
142 da_cat_remove(guint32 key
)
144 DB( g_print("da_cat_remove %d\n", key
) );
146 return g_hash_table_foreach_remove(GLOBALS
->h_cat
, (GHRFunc
)da_cat_remove_grfunc
, &key
);
152 * insert a category into the GHashTable
154 * Return value: TRUE if inserted
158 da_cat_insert(Category
*item
)
162 DB( g_print("da_cat_insert\n") );
164 new_key
= g_new0(guint32
, 1);
165 *new_key
= item
->key
;
166 g_hash_table_insert(GLOBALS
->h_cat
, new_key
, item
);
175 * append a category into the GHashTable
177 * Return value: TRUE if inserted
181 da_cat_append(Category
*cat
)
187 DB( g_print("da_cat_append\n") );
189 if( cat
->name
!= NULL
)
192 fullname
= da_cat_get_fullname(cat
);
193 existitem
= da_cat_get_by_fullname( fullname
);
196 if( existitem
== NULL
)
198 new_key
= g_new0(guint32
, 1);
199 *new_key
= da_cat_get_max_key() + 1;
202 DB( g_print(" -> insert id: %d\n", *new_key
) );
204 g_hash_table_insert(GLOBALS
->h_cat
, new_key
, cat
);
210 DB( g_print(" -> %s already exist\n", cat
->name
) );
217 * da_cat_max_key_ghfunc:
219 * GHFunc for biggest key
223 da_cat_max_key_ghfunc(gpointer key
, Category
*cat
, guint32
*max_key
)
226 *max_key
= MAX(*max_key
, cat
->key
);
230 * da_cat_get_max_key:
232 * Get the biggest key from the GHashTable
234 * Return value: the biggest key value
238 da_cat_get_max_key(void)
242 g_hash_table_foreach(GLOBALS
->h_cat
, (GHFunc
)da_cat_max_key_ghfunc
, &max_key
);
247 * da_cat_get_fullname:
249 * Get category the fullname 'xxxx:yyyyy'
251 * Return value: the category fullname (free it with g_free)
255 da_cat_get_fullname(Category
*cat
)
259 if( cat
->parent
== 0)
260 return g_strdup(cat
->name
);
263 parent
= da_cat_get(cat
->parent
);
266 return g_strdup_printf("%s:%s", parent
->name
, cat
->name
);
275 * da_cat_name_grfunc:
277 * GRFunc to get the max id
279 * Return value: TRUE if the key/value pair match our name
283 da_cat_name_grfunc(gpointer key
, Category
*cat
, gchar
*name
)
286 // DB( g_print("%s == %s\n", name, cat->name) );
287 if( name
&& cat
->name
)
289 if(!strcasecmp(name
, cat
->name
))
296 * da_cat_get_key_by_name:
298 * Get a category key by its name
300 * Return value: the category key or -1 if not found
304 da_cat_get_key_by_name(gchar
*name
)
308 DB( g_print("da_cat_get_key_by_name\n") );
310 cat
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_name_grfunc
, name
);
318 * da_cat_get_by_name:
320 * Get a category structure by its name
322 * Return value: Category * or NULL if not found
326 da_cat_get_by_name(gchar
*name
)
328 DB( g_print("da_cat_get_by_name\n") );
330 return g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_name_grfunc
, name
);
334 /* fullname i.e. car:refuel */
335 struct fullcatcontext
343 da_cat_fullname_grfunc(gpointer key
, Category
*item
, struct fullcatcontext
*ctx
)
346 //DB( g_print("'%s' == '%s'\n", ctx->name, item->name) );
347 if( item
->parent
== ctx
->parent
)
349 if(!strcasecmp(ctx
->name
, item
->name
))
356 da_cat_get_by_fullname(gchar
*fullname
)
358 struct fullcatcontext ctx
;
360 Category
*item
= NULL
;
362 DB( g_print("da_cat_get_by_fullname\n") );
364 typestr
= g_strsplit(fullname
, ":", 2);
365 if( g_strv_length(typestr
) == 2 )
368 ctx
.name
= typestr
[0];
369 DB( g_print(" [x:x] try to find the parent : '%s'\n", typestr
[0]) );
371 Category
*parent
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
374 ctx
.parent
= parent
->key
;
375 ctx
.name
= typestr
[1];
377 DB( g_print(" [x:x] and searching sub %d '%s'\n", ctx
.parent
, ctx
.name
) );
379 item
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
387 DB( g_print(" [x] try to '%s'\n", fullname
) );
389 item
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
394 DB( g_print(" return value %p\n", item
) );
401 * da_cat_append_ifnew_by_fullname:
403 * append a category if it is new by fullname
409 da_cat_append_ifnew_by_fullname(gchar
*fullname
, gboolean imported
)
411 struct fullcatcontext ctx
;
413 Category
*newcat
, *item
, *retval
= NULL
;
416 DB( g_print("da_cat_append_ifnew_by_fullname\n") );
418 DB( g_print(" -> fullname: '%s' %d\n", fullname
, strlen(fullname
)) );
420 if( strlen(fullname
) > 0 )
422 typestr
= g_strsplit(fullname
, ":", 2);
424 /* if we have a subcategory : aaaa:bbb */
425 if( g_strv_length(typestr
) == 2 )
428 ctx
.name
= typestr
[0];
429 DB( g_print(" try to find the parent:'%s'\n", typestr
[0]) );
431 Category
*parent
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
434 DB( g_print(" -> not found\n") );
436 // append a new category
437 new_key
= g_new0(guint32
, 1);
438 *new_key
= da_cat_get_max_key() + 1;
440 newcat
= da_cat_malloc();
441 newcat
->key
= *new_key
;
442 newcat
->name
= g_strdup(typestr
[0]);
443 newcat
->imported
= imported
;
447 DB( g_print(" -> insert cat '%s' id: %d\n", newcat
->name
, newcat
->key
) );
449 g_hash_table_insert(GLOBALS
->h_cat
, new_key
, newcat
);
452 ctx
.parent
= parent
->key
;
453 ctx
.name
= typestr
[1];
454 DB( g_print(" searching %d '%s'\n", ctx
.parent
, ctx
.name
) );
456 item
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
459 // append a new subcategory
460 new_key
= g_new0(guint32
, 1);
461 *new_key
= da_cat_get_max_key() + 1;
463 newcat
= da_cat_malloc();
464 newcat
->key
= *new_key
;
465 newcat
->parent
= parent
->key
;
466 newcat
->name
= g_strdup(typestr
[1]);
467 newcat
->imported
= imported
;
469 newcat
->flags
|= GF_SUB
;
471 DB( g_print(" -> insert subcat '%s' id: %d\n", newcat
->name
, newcat
->key
) );
473 g_hash_table_insert(GLOBALS
->h_cat
, new_key
, newcat
);
480 /* this a single category : aaaa */
484 ctx
.name
= typestr
[0];
485 DB( g_print(" searching %d '%s'\n", ctx
.parent
, ctx
.name
) );
487 item
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
490 // append a new category
491 new_key
= g_new0(guint32
, 1);
492 *new_key
= da_cat_get_max_key() + 1;
494 newcat
= da_cat_malloc();
495 newcat
->key
= *new_key
;
496 newcat
->name
= g_strdup(typestr
[0]);
497 newcat
->imported
= imported
;
499 DB( g_print(" -> insert cat '%s' id: %d\n", newcat
->name
, newcat
->key
) );
501 g_hash_table_insert(GLOBALS
->h_cat
, new_key
, newcat
);
521 * Get a category structure by key
523 * Return value: Category * or NULL if not found
527 da_cat_get(guint32 key
)
529 //DB( g_print("da_cat_get\n") );
531 return g_hash_table_lookup(GLOBALS
->h_cat
, &key
);
535 void da_cat_consistency(Category
*item
)
539 // ensure type equal for categories and its children
540 if(!(item
->flags
& GF_SUB
) && item
->key
> 0)
542 isIncome
= (item
->flags
& GF_INCOME
) ? TRUE
: FALSE
;
543 category_change_type(item
, isIncome
);
545 g_strstrip(item
->name
);
550 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
555 da_cat_debug_list_ghfunc(gpointer key
, gpointer value
, gpointer user_data
)
558 Category
*cat
= value
;
560 DB( g_print(" %d :: %s (parent=%d\n", *id
, cat
->name
, cat
->parent
) );
565 da_cat_debug_list(void)
568 DB( g_print("\n** debug **\n") );
570 g_hash_table_foreach(GLOBALS
->h_cat
, da_cat_debug_list_ghfunc
, NULL
);
572 DB( g_print("\n** end debug **\n") );
580 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
583 category_is_used(guint32 key
)
588 list
= g_list_first(GLOBALS
->ope_list
);
591 Transaction
*entry
= list
->data
;
592 if( key
== entry
->kcat
)
595 // check split category #1340142
596 nbsplit
= da_transaction_splits_count(entry
);
597 for(i
=0;i
<nbsplit
;i
++)
599 Split
*split
= entry
->splits
[i
];
601 if( key
== split
->kcat
)
605 list
= g_list_next(list
);
608 list
= g_list_first(GLOBALS
->arc_list
);
611 Archive
*entry
= list
->data
;
612 if( key
== entry
->kcat
)
614 list
= g_list_next(list
);
617 //todo: add budget use here
619 lrul
= list
= g_hash_table_get_values(GLOBALS
->h_rul
);
622 Assign
*entry
= list
->data
;
624 if( key
== entry
->kcat
)
626 list
= g_list_next(list
);
634 category_move(guint32 key1
, guint32 key2
)
639 list
= g_list_first(GLOBALS
->ope_list
);
642 Transaction
*entry
= list
->data
;
643 if(entry
->kcat
== key1
)
646 entry
->flags
|= OF_CHANGED
;
649 // move split category #1340142
650 nbsplit
= da_transaction_splits_count(entry
);
651 for(i
=0;i
<nbsplit
;i
++)
653 Split
*split
= entry
->splits
[i
];
655 if( split
->kcat
== key1
)
658 entry
->flags
|= OF_CHANGED
;
662 list
= g_list_next(list
);
665 list
= g_list_first(GLOBALS
->arc_list
);
668 Archive
*entry
= list
->data
;
669 if(entry
->kcat
== key1
)
673 list
= g_list_next(list
);
676 lrul
= list
= g_hash_table_get_values(GLOBALS
->h_rul
);
679 Assign
*entry
= list
->data
;
681 if(entry
->kcat
== key1
)
685 list
= g_list_next(list
);
693 category_rename(Category
*item
, const gchar
*newname
)
695 Category
*parent
, *existitem
;
696 gchar
*fullname
= NULL
;
700 DB( g_print("(category) rename\n") );
702 stripname
= g_strdup(newname
);
703 g_strstrip(stripname
);
705 if( item
->parent
== 0)
706 fullname
= g_strdup(stripname
);
709 parent
= da_cat_get(item
->parent
);
712 fullname
= g_strdup_printf("%s:%s", parent
->name
, stripname
);
716 DB( g_print(" - search: %s\n", fullname
) );
718 existitem
= da_cat_get_by_fullname( fullname
);
720 if( existitem
!= NULL
&& existitem
->key
!= item
->key
)
722 DB( g_print("error, same name already exist with other key %d <> %d\n",existitem
->key
, item
->key
) );
727 DB( g_print(" -renaming\n") );
730 item
->name
= g_strdup(stripname
);
741 static gint
category_glist_name_compare_func(Category
*c1
, Category
*c2
)
743 gchar
*name1
, *name2
;
746 if( c1
!= NULL
&& c2
!= NULL
)
748 name1
= da_cat_get_fullname(c1
);
749 name2
= da_cat_get_fullname(c2
);
751 retval
= hb_string_utf8_compare(name1
, name2
);
760 static gint
category_glist_key_compare_func(Category
*a
, Category
*b
)
762 gint ka
, kb
, retval
= 0;
764 if(a
->parent
== 0 && b
->parent
== a
->key
)
767 if(b
->parent
== 0 && a
->parent
== b
->key
)
771 ka
= a
->parent
!= 0 ? a
->parent
: a
->key
;
772 kb
= b
->parent
!= 0 ? b
->parent
: b
->key
;
789 DB( g_print("compare a=%2d:%2d to b=%2d:%2d :: %d [%s]\n", a
->key
, a
->parent
, b
->key
, b
->parent
, retval
, str
) );
796 GList
*category_glist_sorted(gint column
)
798 GList
*list
= g_hash_table_get_values(GLOBALS
->h_cat
);
801 return g_list_sort(list
, (GCompareFunc
)category_glist_key_compare_func
);
803 return g_list_sort(list
, (GCompareFunc
)category_glist_name_compare_func
);
808 category_load_csv(gchar
*filename
, gchar
**error
)
815 gchar
*lastcatname
= NULL
;
820 const gchar
*encoding
;
822 encoding
= homebank_file_getencoding(filename
);
824 DB( g_print(" -> encoding should be %s\n", encoding
) );
829 io
= g_io_channel_new_file(filename
, "r", NULL
);
833 if( encoding
!= NULL
)
835 g_io_channel_set_encoding(io
, encoding
, NULL
);
842 io_stat
= g_io_channel_read_line(io
, &tmpstr
, NULL
, NULL
, &err
);
844 DB( g_print(" + iostat %d\n", io_stat
) );
846 if( io_stat
== G_IO_STATUS_ERROR
)
848 DB (g_print(" + ERROR %s\n",err
->message
));
851 if( io_stat
== G_IO_STATUS_EOF
)
853 if( io_stat
== G_IO_STATUS_NORMAL
)
857 DB( g_print(" + strip %s\n", tmpstr
) );
859 hb_string_strip_crlf(tmpstr
);
861 DB( g_print(" + split\n") );
863 str_array
= g_strsplit (tmpstr
, ";", 3);
866 if( g_strv_length (str_array
) != 3 )
868 *error
= _("invalid csv format");
870 DB( g_print(" + error %s\n", *error
) );
874 DB( g_print(" + read %s : %s : %s\n", str_array
[0], str_array
[1], str_array
[2]) );
877 if( g_str_has_prefix(str_array
[0], "1") )
879 fullcatname
= g_strdup(str_array
[2]);
881 lastcatname
= g_strdup(str_array
[2]);
883 type
= g_str_has_prefix(str_array
[1], "+") ? GF_INCOME
: 0;
885 DB( g_print(" + type = %d\n", type
) );
889 if( g_str_has_prefix(str_array
[0], "2") )
891 fullcatname
= g_strdup_printf("%s:%s", lastcatname
, str_array
[2]);
894 DB( g_print(" + fullcatname %s\n", fullcatname
) );
896 item
= da_cat_append_ifnew_by_fullname(fullcatname
, FALSE
);
898 DB( g_print(" + item %p\n", item
) );
902 DB( g_print(" + assign flags: '%c'\n", type
) );
909 g_strfreev (str_array
);
917 g_io_channel_unref (io
);
930 category_save_csv(gchar
*filename
, gchar
**error
)
932 gboolean retval
= FALSE
;
938 io
= g_io_channel_new_file(filename
, "w", NULL
);
941 lcat
= list
= category_glist_sorted(1);
945 Category
*item
= list
->data
;
951 if( item
->parent
== 0)
954 type
= (item
->flags
& GF_INCOME
) ? '+' : '-';
962 outstr
= g_strdup_printf("%c;%c;%s\n", lvel
, type
, item
->name
);
964 DB( g_print(" + export %s\n", outstr
) );
966 g_io_channel_write_chars(io
, outstr
, -1, NULL
, NULL
);
970 list
= g_list_next(list
);
977 g_io_channel_unref (io
);
985 gint
category_change_type(Category
*item
, gboolean isIncome
)
990 item
->flags
&= ~(GF_INCOME
); //remove flag
992 item
->flags
|= GF_INCOME
;
994 // change also childs
995 lcat
= list
= g_hash_table_get_values(GLOBALS
->h_cat
);
998 Category
*child
= list
->data
;
1000 if(child
->parent
== item
->key
)
1002 child
->flags
&= ~(GF_INCOME
); //remove flag
1003 if(isIncome
== TRUE
)
1004 child
->flags
|= GF_INCOME
;
1007 list
= g_list_next(list
);
1020 * category_find_preset:
1022 * find a user language compatible file for category preset
1024 * Return value: a pathname to the file or NULL
1027 gchar
*category_find_preset(gchar
**lang
)
1034 DB( g_print("** category_find_preset **\n") );
1036 langs
= (gchar
**)g_get_language_names ();
1038 DB( g_print(" -> %d languages detected\n", g_strv_length(langs
)) );
1040 for(i
=0;i
<g_strv_length(langs
);i
++)
1042 DB( g_print(" -> %d '%s'\n", i
, langs
[i
]) );
1043 filename
= g_strdup_printf("hb-categories-%s.csv", langs
[i
]);
1044 gchar
*pathfilename
= g_build_filename(homebank_app_get_datas_dir(), filename
, NULL
);
1045 exists
= g_file_test(pathfilename
, G_FILE_TEST_EXISTS
);
1046 DB( g_print(" -> '%s' exists=%d\n", pathfilename
, exists
) );
1051 return pathfilename
;
1054 g_free(pathfilename
);
1057 DB( g_print("return NULL\n") );