GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-report.cpp
1/********************************************************************
2 * gnc-report.c -- C functions for reports. *
3 * *
4 * Copyright (C) 2001 Linux Developers Group *
5 * Copyright (C) 2006 Chris Shoemaker <c.shoemaker@cox.net> *
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#include <config.h>
26#ifdef __MINGW32__
27#define _GL_UNISTD_H //Deflect poisonous define in Guile's GnuLib
28#endif
29#include <gnc-optiondb.hpp>
30#include <glib.h>
31#include <glib/gstdio.h>
32#include <gtk/gtk.h>
33#include <libguile.h>
34#include <stdio.h>
35#include <string.h>
36#include <string.h>
37#include <errno.h>
38#include <fcntl.h>
39
40#include <gfec.h>
41#include <gnc-filepath-utils.h>
42#include <gnc-guile-utils.h>
43#include <gnc-engine.h>
44#include "gnc-report.h"
45#include <charconv>
46
47extern "C" SCM scm_init_sw_report_module(void);
48
49static QofLogModule log_module = GNC_MOD_GUI;
50
51/* Fow now, this is global, like it was in guile. It _should_ be per-book. */
52static GHashTable *reports = NULL;
53static gint report_next_serial_id = 0;
54
55static gboolean
56try_load_config_array(const gchar *fns[])
57{
58 gchar *filename;
59 int i;
60
61 for (i = 0; fns[i]; i++)
62 {
63 filename = gnc_build_userdata_path(fns[i]);
64 if (gfec_try_load(filename))
65 {
66 g_free(filename);
67 return TRUE;
68 }
69 g_free(filename);
70 }
71 return FALSE;
72}
73
74static void
75update_message(const gchar *msg)
76{
77 //gnc_update_splash_screen(msg, GNC_SPLASH_PERCENTAGE_UNKNOWN);
78 PINFO("%s", msg);
79}
80
81static void
82load_custom_reports_stylesheets(void)
83{
84 /* Don't continue adding to this list. When 3.0 rolls around bump
85 * the 2.4 files off the list. */
86 static const gchar *saved_report_files[] =
87 {
88 SAVED_REPORTS_FILE, SAVED_REPORTS_FILE_OLD_REV, NULL
89 };
90 static const gchar *stylesheet_files[] = { "stylesheets-2.0", NULL};
91 static int is_user_config_loaded = FALSE;
92
93 if (is_user_config_loaded)
94 return;
95 else is_user_config_loaded = TRUE;
96
97 update_message("loading saved reports");
98 try_load_config_array(saved_report_files);
99 update_message("loading stylesheets");
100 try_load_config_array(stylesheet_files);
101}
102
103void
104gnc_report_init (void)
105{
106 scm_init_sw_report_module();
107 scm_c_use_module ("gnucash report");
108 scm_c_use_module ("gnucash reports");
109 scm_c_eval_string("(report-module-loader (list '(gnucash report stylesheets)))");
110
111 load_custom_reports_stylesheets();
112}
113
114
115static void
116gnc_report_init_table(void)
117{
118 if (!reports)
119 {
120 reports = g_hash_table_new_full(
121 g_int_hash, g_int_equal,
122 g_free, (GDestroyNotify) scm_gc_unprotect_object);
123 }
124}
125
126void
127gnc_report_remove_by_id(gint id)
128{
129 if (reports)
130 g_hash_table_remove(reports, &id);
131}
132
133SCM gnc_report_find(gint id)
134{
135 SCM report = nullptr;
136
137 if (reports)
138 {
139 report = static_cast<SCM>(g_hash_table_lookup(reports, &id));
140 }
141
142 if (!report)
143 return SCM_BOOL_F;
144
145 return report;
146}
147
148gint gnc_report_add(SCM report)
149{
150 SCM get_id = scm_c_eval_string("gnc:report-id");
151 SCM value;
152 gint id, *key;
153
154 gnc_report_init_table();
155
156 value = scm_call_1(get_id, report);
157 if (scm_is_number(value))
158 {
159 id = scm_to_int(value);
160 if (!g_hash_table_lookup(reports, &id))
161 {
162 key = g_new(gint, 1);
163 *key = id;
164 g_hash_table_insert(reports, key, (gpointer)report);
165 scm_gc_protect_object(report);
166 return id;
167 }
168 g_warning("Report specified id of %d is already is use. "
169 "Using generated id.", id);
170 }
171
172 id = report_next_serial_id++;
173 while (id < G_MAXINT)
174 {
175 if (!g_hash_table_lookup(reports, &id))
176 {
177 key = g_new(gint, 1);
178 *key = id;
179 g_hash_table_insert(reports, key, (gpointer)report);
180 scm_gc_protect_object(report);
181 return id;
182 }
183 id = report_next_serial_id++;
184 }
185
186 g_warning("Unable to add report to table. %d reports in use.", G_MAXINT);
187 report_next_serial_id = G_MAXINT;
188 return G_MAXINT;
189}
190
191static gboolean
192yes_remove(gpointer key, gpointer val, gpointer data)
193{
194 return TRUE;
195}
196
197void
198gnc_reports_flush_global(void)
199{
200 if (reports)
201 g_hash_table_foreach_remove(reports, yes_remove, NULL);
202}
203
204void
205gnc_reports_foreach (GHFunc func, gpointer user_data)
206{
207 gnc_report_init_table();
208 if (reports)
209 g_hash_table_foreach (reports, func, user_data);
210}
211
212gboolean
213gnc_run_report_with_error_handling (gint report_id, gchar ** data, gchar **errmsg)
214{
215 SCM report, res, html, captured_error;
216
217 report = gnc_report_find (report_id);
218 g_return_val_if_fail (data, FALSE);
219 g_return_val_if_fail (errmsg, FALSE);
220 g_return_val_if_fail (!scm_is_false (report), FALSE);
221
222 res = scm_call_1 (scm_c_eval_string ("gnc:render-report"), report);
223 html = scm_car (res);
224 captured_error = scm_cadr (res);
225
226 if (!scm_is_false (html))
227 {
228 *data = gnc_scm_to_utf8_string (html);
229 *errmsg = NULL;
230 return TRUE;
231 }
232 else
233 {
234 constexpr const char* with_err = "Report %s failed to generate html: %s";
235 constexpr const char* without_err = "Report %s Failed to generate html but didn't raise a Scheme exception.";
236 auto scm_err = scm_is_string (captured_error) ? gnc_scm_to_utf8_string (captured_error) :
237 g_strdup ("");
238
239 if (scm_err && *scm_err)
240 *errmsg = g_strdup_printf (with_err, gnc_report_name (report), scm_err);
241 else
242 *errmsg = g_strdup_printf (without_err, gnc_report_name (report));
243
244 *data = nullptr;
245 g_free (scm_err);
246 return FALSE;
247 }
248}
249
250gchar*
251gnc_report_name( SCM report )
252{
253 SCM get_name = scm_c_eval_string("gnc:report-name");
254
255 if (report == SCM_BOOL_F)
256 return NULL;
257
258 return gnc_scm_call_1_to_string(get_name, report);
259}
260
261gint
262gnc_report_id_string_to_report_id (const char *id_string)
263{
264 g_return_val_if_fail (id_string, -1);
265
266 const char *end_ptr = id_string + std::strlen (id_string);
267 gint rpt_id;
268 auto res = std::from_chars (id_string, end_ptr, rpt_id);
269 if (res.ec != std::errc() || rpt_id < 0) return -1;
270 if (res.ptr == end_ptr) return rpt_id;
271 if (*res.ptr != '|') return -1;
272
273 gint anchor_id;
274 res = std::from_chars (res.ptr + 1, end_ptr, anchor_id);
275 if (res.ec != std::errc() || *res.ptr != '\0' || anchor_id < 0) return -1;
276
277 const SCM get_linked = scm_c_eval_string ("gnc:report-get-linked-report");
278
279 auto id = scm_call_2 (get_linked, scm_from_int (rpt_id), scm_from_int (anchor_id));
280 return scm_is_number (id) ? scm_to_int (id) : -1;
281}
282
283gboolean
284gnc_run_report_id_string_with_error_handling (const char * id_string, char **data,
285 gchar **errmsg)
286{
287 g_return_val_if_fail (id_string, FALSE);
288 g_return_val_if_fail (data, FALSE);
289 *data = NULL;
290
291 if (strncmp ("id=", id_string, 3) != 0)
292 return FALSE;
293
294 gint report_id = gnc_report_id_string_to_report_id (id_string + 3);
295 if (report_id < 0)
296 return FALSE;
297
298 return gnc_run_report_with_error_handling (report_id, data, errmsg);
299}
300
301gchar*
302gnc_get_default_report_font_family(void)
303{
304 GList *top_list;
305 GtkWidget *top_widget;
306 PangoFontDescription *font_desc;
307 GtkStyleContext *top_widget_style_c;
308 gchar *default_font_family;
309
310 top_list = gtk_window_list_toplevels();
311 if (top_list == NULL)
312 return g_strdup ("Arial");
313 top_widget = GTK_WIDGET(top_list->data);
314 g_list_free(top_list);
315 top_widget_style_c = gtk_widget_get_style_context (top_widget);
316 gtk_style_context_get (top_widget_style_c, gtk_widget_get_state_flags (GTK_WIDGET(top_widget)),
317 "font", &font_desc, NULL);
318
319 default_font_family = g_strdup(pango_font_description_get_family (font_desc));
320
321 pango_font_description_free (font_desc);
322
323 if (!default_font_family)
324 return g_strdup ("Arial");
325 else if (g_str_has_prefix (default_font_family, ".AppleSystemUIFont"))
326 {
327 g_free (default_font_family);
328 return g_strdup ("Arial");
329 }
330 else
331 return default_font_family;
332}
333
334static gboolean
335gnc_saved_reports_write_internal (const gchar *file, const gchar *contents, gboolean overwrite)
336{
337 gboolean success = TRUE;
338 gint fd;
339 ssize_t written;
340 gint length;
341 gint flags = O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : O_APPEND);
342 /* Bug 764248: Keep write from adding \r to the line endings. On
343 * windows the file already has them set to \r\n and if we output
344 * in text mode we get \r\r\n.
345 */
346#ifdef G_OS_WIN32
347 if (strstr(file, "backup"))
348 flags |= O_BINARY;
349#endif
350 fd = g_open (file, flags, 0666);
351 if (fd == -1)
352 {
353 PWARN("Cannot open file %s: %s\n", file, strerror(errno));
354 return FALSE;
355 }
356
357 length = strlen (contents);
358 written = write(fd, contents, length);
359 if (written == -1 )
360 {
361 success = FALSE;
362 PWARN("Cannot write to file %s: %s\n", file, strerror(errno));
363 close(fd);
364 }
365 else if (written != length)
366 {
367 success = FALSE;
368 PWARN("File %s truncated (provided %d, written %d)",
369 file, length, (int)written);
370 /* Ignore errors on close */
371 close(fd);
372 }
373 else if (close(fd) == -1)
374 PWARN("Close failed for file %s: %s", file, strerror(errno));
375
376 return success;
377}
378
379
380gboolean gnc_saved_reports_backup (void)
381{
382 gboolean success = FALSE;
383 gchar *saved_rpts_path = gnc_build_userdata_path (SAVED_REPORTS_FILE);
384 gchar *saved_rpts_bkp_path = gnc_build_userdata_path (SAVED_REPORTS_FILE "-backup");
385 gchar *contents = NULL;
386 GError *save_error = NULL;
387
388 if (g_file_test (saved_rpts_path, G_FILE_TEST_EXISTS))
389 {
390 if (!g_file_get_contents (saved_rpts_path, &contents, NULL, &save_error))
391 {
392 PWARN ("Couldn't read contents of %s.\nReason: %s", saved_rpts_path, save_error->message);
393 g_error_free (save_error);
394 }
395 }
396
397 if (contents)
398 {
399 DEBUG ("creating backup of file %s", saved_rpts_bkp_path);
400 success = gnc_saved_reports_write_internal (saved_rpts_bkp_path, contents, TRUE);
401 }
402
403 g_free (saved_rpts_path);
404 g_free (saved_rpts_bkp_path);
405 g_free (contents);
406
407 return success;
408}
409
410gboolean
411gnc_saved_reports_write_to_file (const gchar* report_def, gboolean overwrite)
412{
413 gboolean success = FALSE;
414 gchar *saved_rpts_path = gnc_build_userdata_path (SAVED_REPORTS_FILE);
415
416 if (report_def)
417 {
418 DEBUG ("writing to %s", saved_rpts_path);
419 success = gnc_saved_reports_write_internal (saved_rpts_path, report_def, overwrite);
420 }
421
422 g_free (saved_rpts_path);
423
424 return success;
425}
426
428gnc_get_optiondb_from_dispatcher(SCM dispatcher)
429{
430 SCM get_options = scm_c_eval_string("gnc:optiondb");
431 if (dispatcher == SCM_BOOL_F)
432 return nullptr;
433 auto scm_ptr{scm_call_1(get_options, dispatcher)};
434 auto smob{!scm_is_null(scm_ptr) && SCM_INSTANCEP(scm_ptr) &&
435 scm_is_true(scm_slot_exists_p(scm_ptr, SCM_EOL)) ?
436 scm_slot_ref(scm_ptr, SCM_EOL) : (scm_ptr)};
437
438 void *c_ptr{nullptr};
439 if (!SCM_NULLP(smob))
440 {
441 if (SCM_POINTER_P(smob))
442 c_ptr = SCM_POINTER_VALUE(smob);
443 else
444 c_ptr = reinterpret_cast<void*>(SCM_CELL_WORD_1(smob));
445 }
446 else
447 return nullptr;
448
449 auto u_ptr{static_cast<std::unique_ptr<GncOptionDB>*>(c_ptr)};
450 return u_ptr->get();
451}
452
Holds all of the options for a book, report, or stylesheet, organized by GncOptionSections.
All type declarations for the whole Gnucash engine.
File path resolution utility functions.
The primary C++ interface to options for books, reports, and stylesheets.
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250