GnuCash  5.6-150-g038405b370+
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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 
47 extern "C" SCM scm_init_sw_report_module(void);
48 
49 static QofLogModule log_module = GNC_MOD_GUI;
50 
51 /* Fow now, this is global, like it was in guile. It _should_ be per-book. */
52 static GHashTable *reports = NULL;
53 static gint report_next_serial_id = 0;
54 
55 static gboolean
56 try_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 
74 static void
75 update_message(const gchar *msg)
76 {
77  //gnc_update_splash_screen(msg, GNC_SPLASH_PERCENTAGE_UNKNOWN);
78  PINFO("%s", msg);
79 }
80 
81 static void
82 load_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 
103 void
104 gnc_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 
115 static void
116 gnc_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 
126 void
127 gnc_report_remove_by_id(gint id)
128 {
129  if (reports)
130  g_hash_table_remove(reports, &id);
131 }
132 
133 SCM 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 
148 gint 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 
191 static gboolean
192 yes_remove(gpointer key, gpointer val, gpointer data)
193 {
194  return TRUE;
195 }
196 
197 void
198 gnc_reports_flush_global(void)
199 {
200  if (reports)
201  g_hash_table_foreach_remove(reports, yes_remove, NULL);
202 }
203 
204 void
205 gnc_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 
212 gboolean
213 gnc_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 
250 gchar*
251 gnc_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 
261 gint
262 gnc_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 
283 gboolean
284 gnc_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 
301 gchar*
302 gnc_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 
334 static gboolean
335 gnc_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 
380 gboolean 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 
410 gboolean
411 gnc_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 
428 gnc_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.
gchar * gnc_build_userdata_path(const gchar *filename)
Make a path to filename in the user&#39;s gnucash data directory.
#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
All type declarations for the whole Gnucash engine.
The primary C++ interface to options for books, reports, and stylesheets.
File path resolution utility functions.