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 gboolean
261 gnc_run_report_id_string_with_error_handling (const char * id_string, char **data,
262  gchar **errmsg)
263 {
264  gint report_id;
265 
266  g_return_val_if_fail (id_string, FALSE);
267  g_return_val_if_fail (data, FALSE);
268  *data = NULL;
269 
270  if (strncmp ("id=", id_string, 3) != 0)
271  return FALSE;
272 
273  if (sscanf (id_string + 3, "%d", &report_id) != 1)
274  return FALSE;
275 
276  return gnc_run_report_with_error_handling (report_id, data, errmsg);
277 }
278 
279 gchar*
280 gnc_get_default_report_font_family(void)
281 {
282  GList *top_list;
283  GtkWidget *top_widget;
284  PangoFontDescription *font_desc;
285  GtkStyleContext *top_widget_style_c;
286  gchar *default_font_family;
287 
288  top_list = gtk_window_list_toplevels();
289  if (top_list == NULL)
290  return g_strdup ("Arial");
291  top_widget = GTK_WIDGET(top_list->data);
292  g_list_free(top_list);
293  top_widget_style_c = gtk_widget_get_style_context (top_widget);
294  gtk_style_context_get (top_widget_style_c, gtk_widget_get_state_flags (GTK_WIDGET(top_widget)),
295  "font", &font_desc, NULL);
296 
297  default_font_family = g_strdup(pango_font_description_get_family (font_desc));
298 
299  pango_font_description_free (font_desc);
300 
301  if (!default_font_family)
302  return g_strdup ("Arial");
303  else if (g_str_has_prefix (default_font_family, ".AppleSystemUIFont"))
304  {
305  g_free (default_font_family);
306  return g_strdup ("Arial");
307  }
308  else
309  return default_font_family;
310 }
311 
312 static gboolean
313 gnc_saved_reports_write_internal (const gchar *file, const gchar *contents, gboolean overwrite)
314 {
315  gboolean success = TRUE;
316  gint fd;
317  ssize_t written;
318  gint length;
319  gint flags = O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : O_APPEND);
320  /* Bug 764248: Keep write from adding \r to the line endings. On
321  * windows the file already has them set to \r\n and if we output
322  * in text mode we get \r\r\n.
323  */
324 #ifdef G_OS_WIN32
325  if (strstr(file, "backup"))
326  flags |= O_BINARY;
327 #endif
328  fd = g_open (file, flags, 0666);
329  if (fd == -1)
330  {
331  PWARN("Cannot open file %s: %s\n", file, strerror(errno));
332  return FALSE;
333  }
334 
335  length = strlen (contents);
336  written = write(fd, contents, length);
337  if (written == -1 )
338  {
339  success = FALSE;
340  PWARN("Cannot write to file %s: %s\n", file, strerror(errno));
341  close(fd);
342  }
343  else if (written != length)
344  {
345  success = FALSE;
346  PWARN("File %s truncated (provided %d, written %d)",
347  file, length, (int)written);
348  /* Ignore errors on close */
349  close(fd);
350  }
351  else if (close(fd) == -1)
352  PWARN("Close failed for file %s: %s", file, strerror(errno));
353 
354  return success;
355 }
356 
357 
358 gboolean gnc_saved_reports_backup (void)
359 {
360  gboolean success = FALSE;
361  gchar *saved_rpts_path = gnc_build_userdata_path (SAVED_REPORTS_FILE);
362  gchar *saved_rpts_bkp_path = gnc_build_userdata_path (SAVED_REPORTS_FILE "-backup");
363  gchar *contents = NULL;
364  GError *save_error = NULL;
365 
366  if (g_file_test (saved_rpts_path, G_FILE_TEST_EXISTS))
367  {
368  if (!g_file_get_contents (saved_rpts_path, &contents, NULL, &save_error))
369  {
370  PWARN ("Couldn't read contents of %s.\nReason: %s", saved_rpts_path, save_error->message);
371  g_error_free (save_error);
372  }
373  }
374 
375  if (contents)
376  {
377  DEBUG ("creating backup of file %s", saved_rpts_bkp_path);
378  success = gnc_saved_reports_write_internal (saved_rpts_bkp_path, contents, TRUE);
379  }
380 
381  g_free (saved_rpts_path);
382  g_free (saved_rpts_bkp_path);
383  g_free (contents);
384 
385  return success;
386 }
387 
388 gboolean
389 gnc_saved_reports_write_to_file (const gchar* report_def, gboolean overwrite)
390 {
391  gboolean success = FALSE;
392  gchar *saved_rpts_path = gnc_build_userdata_path (SAVED_REPORTS_FILE);
393 
394  if (report_def)
395  {
396  DEBUG ("writing to %s", saved_rpts_path);
397  success = gnc_saved_reports_write_internal (saved_rpts_path, report_def, overwrite);
398  }
399 
400  g_free (saved_rpts_path);
401 
402  return success;
403 }
404 
406 gnc_get_optiondb_from_dispatcher(SCM dispatcher)
407 {
408  SCM get_options = scm_c_eval_string("gnc:optiondb");
409  if (dispatcher == SCM_BOOL_F)
410  return nullptr;
411  auto scm_ptr{scm_call_1(get_options, dispatcher)};
412  auto smob{!scm_is_null(scm_ptr) && SCM_INSTANCEP(scm_ptr) &&
413  scm_is_true(scm_slot_exists_p(scm_ptr, SCM_EOL)) ?
414  scm_slot_ref(scm_ptr, SCM_EOL) : (scm_ptr)};
415 
416  void *c_ptr{nullptr};
417  if (!SCM_NULLP(smob))
418  {
419  if (SCM_POINTER_P(smob))
420  c_ptr = SCM_POINTER_VALUE(smob);
421  else
422  c_ptr = reinterpret_cast<void*>(SCM_CELL_WORD_1(smob));
423  }
424  else
425  return nullptr;
426 
427  auto u_ptr{static_cast<std::unique_ptr<GncOptionDB>*>(c_ptr)};
428  return u_ptr->get();
429 }
430 
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.