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