GnuCash  5.6-150-g038405b370+
dialog-doclink-utils.c
1 /********************************************************************\
2  * dialog-doclink-utils.c -- Document link dialog Utils *
3  * Copyright (C) 2020 Robert Fewell *
4  * *
5  * This program is free software; you can redistribute it and/or *
6  * modify it under the terms of the GNU General Public License as *
7  * published by the Free Software Foundation; either version 2 of *
8  * the License, or (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License*
16  * along with this program; if not, contact: *
17  * *
18  * Free Software Foundation Voice: +1-617-542-5942 *
19  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20  * Boston, MA 02110-1301, USA gnu@gnu.org *
21 \********************************************************************/
22 
23 #include <config.h>
24 
25 #include <gtk/gtk.h>
26 #include <glib/gi18n.h>
27 
28 #include "dialog-doclink-utils.h"
29 
30 #include "dialog-utils.h"
31 #include "Transaction.h"
32 #include "gncInvoice.h"
33 
34 #include "gnc-prefs.h"
35 #include "gnc-ui.h"
36 #include "gnc-ui-util.h"
37 #include "gnc-gnome-utils.h"
38 #include "gnc-uri-utils.h"
39 #include "gnc-filepath-utils.h"
40 #include "Account.h"
41 
42 /* This static indicates the debugging module that this .o belongs to. */
43 static QofLogModule log_module = GNC_MOD_GUI;
44 
45 /* =================================================================== */
46 
47 static gchar *
48 convert_uri_to_abs_path (const gchar *path_head, const gchar *uri,
49  gchar *uri_scheme, gboolean return_uri)
50 {
51  gchar *ret_value = NULL;
52 
53  if (!uri_scheme) // relative path
54  {
55  gchar *path = gnc_uri_get_path (path_head);
56  gchar *file_path = gnc_file_path_absolute (path, uri);
57 
58  if (return_uri)
59  ret_value = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, file_path);
60  else
61  ret_value = g_strdup (file_path);
62 
63  g_free (path);
64  g_free (file_path);
65  }
66 
67  if (g_strcmp0 (uri_scheme, "file") == 0) // absolute path
68  {
69  if (return_uri)
70  ret_value = g_strdup (uri);
71  else
72  ret_value = gnc_uri_get_path (uri);
73  }
74  return ret_value;
75 }
76 
77 gchar *
78 gnc_doclink_get_unescape_uri (const gchar *path_head, const gchar *uri, gchar *uri_scheme)
79 {
80  gchar *display_str = NULL;
81 
82  if (uri && *uri)
83  {
84  // if scheme is null or 'file' we should get a file path
85  gchar *file_path = convert_uri_to_abs_path (path_head, uri, uri_scheme, FALSE);
86 
87  if (file_path)
88  display_str = g_uri_unescape_string (file_path, NULL);
89  else
90  display_str = g_uri_unescape_string (uri, NULL);
91 
92  g_free (file_path);
93 
94 #ifdef G_OS_WIN32 // make path look like a traditional windows path
95  g_strdelimit (display_str, "/", '\\');
96 #endif
97  }
98  DEBUG("Return display string is '%s'", display_str);
99  return display_str;
100 }
101 
102 gchar *
103 gnc_doclink_get_use_uri (const gchar *path_head, const gchar *uri, gchar *uri_scheme)
104 {
105  gchar *use_str = NULL;
106 
107  if (uri && *uri)
108  {
109  // if scheme is null or 'file' we should get a file path
110  gchar *file_path = convert_uri_to_abs_path (path_head, uri, uri_scheme, TRUE);
111 
112  if (file_path)
113  use_str = g_strdup (file_path);
114  else
115  use_str = g_strdup (uri);
116 
117  g_free (file_path);
118  }
119  DEBUG("Return use string is '%s'", use_str);
120  return use_str;
121 }
122 
123 gchar *
124 gnc_doclink_get_unescaped_just_uri (const gchar *uri)
125 {
126  gchar *path_head = gnc_doclink_get_path_head ();
127  gchar *uri_scheme = gnc_uri_get_scheme (uri);
128  gchar *ret_uri = gnc_doclink_get_unescape_uri (path_head, uri, uri_scheme);
129 
130  g_free (path_head);
131  g_free (uri_scheme);
132  return ret_uri;
133 }
134 
135 gchar *
136 gnc_doclink_convert_trans_link_uri (gpointer trans, gboolean book_ro)
137 {
138  const gchar *uri = xaccTransGetDocLink (trans); // get the existing uri
139  const gchar *part = NULL;
140 
141  if (!uri)
142  return NULL;
143 
144  if (g_str_has_prefix (uri, "file:") && !g_str_has_prefix (uri,"file://"))
145  {
146  /* fix an earlier error when storing relative paths before version 3.5
147  * they were stored starting as 'file:' or 'file:/' depending on OS
148  * relative paths are stored without a leading "/" and in native form
149  */
150  if (g_str_has_prefix (uri,"file:/"))
151  part = uri + strlen ("file:/");
152  else if (g_str_has_prefix (uri,"file:"))
153  part = uri + strlen ("file:");
154 
155  if (!xaccTransGetReadOnly (trans) && !book_ro)
156  xaccTransSetDocLink (trans, part);
157 
158  return g_strdup (part);
159  }
160  return g_strdup (uri);
161 }
162 
163 /* =================================================================== */
164 
165 static gchar *
166 doclink_get_path_head_and_set (gboolean *path_head_set)
167 {
168  gchar *ret_path = NULL;
169  gchar *path_head = gnc_prefs_get_string (GNC_PREFS_GROUP_GENERAL, GNC_DOC_LINK_PATH_HEAD);
170  *path_head_set = FALSE;
171 
172  if (path_head && *path_head) // not default entry
173  {
174  *path_head_set = TRUE;
175  ret_path = g_strdup (path_head);
176  }
177  else
178  {
179  const gchar *doc = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS);
180 
181  if (doc)
182  ret_path = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, doc);
183  else
184  ret_path = gnc_uri_create_uri ("file", NULL, 0, NULL, NULL, gnc_userdata_dir ());
185  }
186  // make sure there is a trailing '/'
187  if (!g_str_has_suffix (ret_path, "/"))
188  {
189  gchar *folder_with_slash = g_strconcat (ret_path, "/", NULL);
190  g_free (ret_path);
191  ret_path = g_strdup (folder_with_slash);
192  g_free (folder_with_slash);
193 
194  if (*path_head_set) // prior to 3.5, assoc-head could be with or without a trailing '/'
195  {
196  if (!gnc_prefs_set_string (GNC_PREFS_GROUP_GENERAL, GNC_DOC_LINK_PATH_HEAD, ret_path))
197  PINFO ("Failed to save preference at %s, %s with %s",
198  GNC_PREFS_GROUP_GENERAL, GNC_DOC_LINK_PATH_HEAD, ret_path);
199  }
200  }
201  g_free (path_head);
202  return ret_path;
203 }
204 
205 gchar *
206 gnc_doclink_get_path_head (void)
207 {
208  gboolean path_head_set = FALSE;
209 
210  return doclink_get_path_head_and_set (&path_head_set);
211 }
212 
213 void
214 gnc_doclink_set_path_head_label (GtkWidget *path_head_label, const gchar *incoming_path_head, const gchar *prefix)
215 {
216  gboolean path_head_set = FALSE;
217  gchar *path_head = NULL;
218  gchar *scheme;
219  gchar *path_head_str;
220  gchar *path_head_text;
221 
222  if (incoming_path_head)
223  {
224  path_head = g_strdup (incoming_path_head);
225  path_head_set = TRUE;
226  }
227  else
228  path_head = doclink_get_path_head_and_set (&path_head_set);
229 
230  scheme = gnc_uri_get_scheme (path_head);
231  path_head_str = gnc_doclink_get_unescape_uri (NULL, path_head, scheme);
232 
233  if (path_head_set)
234  {
235  // test for current folder being present
236  if (g_file_test (path_head_str, G_FILE_TEST_IS_DIR))
237  path_head_text = g_strdup_printf ("%s '%s'", _("Path head for files is,"), path_head_str);
238  else
239  path_head_text = g_strdup_printf ("%s '%s'", _("Path head does not exist,"), path_head_str);
240  }
241  else
242  path_head_text = g_strdup_printf (_("Path head not set, using '%s' for relative paths"), path_head_str);
243 
244  if (prefix)
245  {
246  gchar *tmp = g_strdup (path_head_text);
247  g_free (path_head_text);
248 
249  path_head_text = g_strdup_printf ("%s %s", prefix, tmp);
250 
251  g_free (tmp);
252  }
253 
254  gtk_label_set_text (GTK_LABEL(path_head_label), path_head_text);
255 
256  // Set the style context for this label so it can be easily manipulated with css
257  gnc_widget_style_context_add_class (GTK_WIDGET(path_head_label), "gnc-class-highlight");
258 
259  g_free (scheme);
260  g_free (path_head_str);
261  g_free (path_head_text);
262  g_free (path_head);
263 }
264 
265 /* =================================================================== */
266 
267 typedef struct
268 {
269  const gchar *old_path_head_uri;
270  gboolean change_old;
271  const gchar *new_path_head_uri;
272  gboolean change_new;
273  gboolean book_ro;
275 
276 static void
277 update_invoice_uri (QofInstance* data, gpointer user_data)
278 {
279  DoclinkUpdate *doclink_update = user_data;
280  GncInvoice *invoice = GNC_INVOICE(data);
281  const gchar* uri = gncInvoiceGetDocLink (invoice);
282 
283  if (uri && *uri)
284  {
285  gboolean rel = FALSE;
286  gchar *scheme = gnc_uri_get_scheme (uri);
287 
288  if (!scheme) // path is relative
289  rel = TRUE;
290 
291  // check for relative and we want to change them
292  if (rel && doclink_update->change_old)
293  {
294  gchar *new_uri = gnc_doclink_get_use_uri (doclink_update->old_path_head_uri, uri, scheme);
295  gncInvoiceSetDocLink (invoice, new_uri);
296  g_free (new_uri);
297  }
298  g_free (scheme);
299 
300  // check for not relative and we want to change them
301  if (!rel && doclink_update->change_new && g_str_has_prefix (uri, doclink_update->new_path_head_uri))
302  {
303  // relative paths do not start with a '/'
304  const gchar *part = uri + strlen (doclink_update->new_path_head_uri);
305  gchar *new_uri = g_strdup (part);
306 
307  gncInvoiceSetDocLink (invoice, new_uri);
308  g_free (new_uri);
309  }
310  }
311 }
312 
313 static void
314 update_trans_uri (QofInstance* data, gpointer user_data)
315 {
316  DoclinkUpdate *doclink_update = user_data;
317  Transaction *trans = GNC_TRANSACTION(data);
318  gchar *uri;
319 
320  // fix an earlier error when storing relative paths before version 3.5
321  uri = gnc_doclink_convert_trans_link_uri (trans, doclink_update->book_ro);
322 
323  if (uri && *uri)
324  {
325  gboolean rel = FALSE;
326  gchar *scheme = gnc_uri_get_scheme (uri);
327 
328  if (!scheme) // path is relative
329  rel = TRUE;
330 
331  // check for relative and we want to change them
332  if (rel && doclink_update->change_old)
333  {
334  gchar *new_uri = gnc_doclink_get_use_uri (doclink_update->old_path_head_uri, uri, scheme);
335 
336  if (!xaccTransGetReadOnly (trans))
337  xaccTransSetDocLink (trans, new_uri);
338 
339  g_free (new_uri);
340  }
341  g_free (scheme);
342 
343  // check for not relative and we want to change them
344  if (!rel && doclink_update->change_new && g_str_has_prefix (uri, doclink_update->new_path_head_uri))
345  {
346  // relative paths do not start with a '/'
347  const gchar *part = uri + strlen (doclink_update->new_path_head_uri);
348  gchar *new_uri = g_strdup (part);
349 
350  if (!xaccTransGetReadOnly (trans))
351  xaccTransSetDocLink (trans, new_uri);
352 
353  g_free (new_uri);
354  }
355  }
356  g_free (uri);
357 }
358 
359 static void
360 change_relative_and_absolute_uri_paths (const gchar *old_path_head_uri, gboolean change_old,
361  const gchar *new_path_head_uri, gboolean change_new)
362 {
363  QofBook *book = gnc_get_current_book();
364  gboolean book_ro = qof_book_is_readonly (book);
365  DoclinkUpdate *doclink_update;
366 
367  /* if book is read only, nothing to do */
368  if (book_ro)
369  return;
370 
371  doclink_update = g_new0 (DoclinkUpdate, 1);
372 
373  doclink_update->old_path_head_uri = old_path_head_uri;
374  doclink_update->new_path_head_uri = new_path_head_uri;
375  doclink_update->change_old = change_old;
376  doclink_update->change_new = change_new;
377  doclink_update->book_ro = book_ro;
378 
379  /* Loop through the transactions */
380  qof_collection_foreach (qof_book_get_collection (book, GNC_ID_TRANS),
381  update_trans_uri, doclink_update);
382 
383  /* Loop through the invoices */
384  qof_collection_foreach (qof_book_get_collection (book, GNC_ID_INVOICE),
385  update_invoice_uri, doclink_update);
386 
387  g_free (doclink_update);
388 }
389 
390 void
391 gnc_doclink_pref_path_head_changed (GtkWindow *parent, const gchar *old_path_head_uri)
392 {
393  GtkWidget *dialog;
394  GtkBuilder *builder;
395  GtkWidget *use_old_path_head, *use_new_path_head;
396  GtkWidget *old_head_label, *new_head_label;
397  gint result;
398  gchar *new_path_head_uri = gnc_doclink_get_path_head ();
399 
400  if (g_strcmp0 (old_path_head_uri, new_path_head_uri) == 0)
401  {
402  g_free (new_path_head_uri);
403  return;
404  }
405 
406  /* Create the dialog box */
407  builder = gtk_builder_new();
408  gnc_builder_add_from_file (builder, "dialog-doclink.glade", "link_path_head_changed_dialog");
409  dialog = GTK_WIDGET(gtk_builder_get_object (builder, "link_path_head_changed_dialog"));
410 
411  if (parent != NULL)
412  gtk_window_set_transient_for (GTK_WINDOW(dialog), GTK_WINDOW(parent));
413 
414  // Set the name and style context for this widget so it can be easily manipulated with css
415  gtk_widget_set_name (GTK_WIDGET(dialog), "gnc-id-doclink-change");
416  gnc_widget_style_context_add_class (GTK_WIDGET(dialog), "gnc-class-doclink");
417 
418  old_head_label = GTK_WIDGET(gtk_builder_get_object (builder, "existing_path_head"));
419  new_head_label = GTK_WIDGET(gtk_builder_get_object (builder, "new_path_head"));
420 
421  use_old_path_head = GTK_WIDGET(gtk_builder_get_object (builder, "use_old_path_head"));
422  use_new_path_head = GTK_WIDGET(gtk_builder_get_object (builder, "use_new_path_head"));
423 
424  // display path head text and test if present
425  gnc_doclink_set_path_head_label (old_head_label, old_path_head_uri, _("Existing"));
426  gnc_doclink_set_path_head_label (new_head_label, new_path_head_uri, _("New"));
427 
428  gtk_widget_show (dialog);
429  g_object_unref (G_OBJECT(builder));
430 
431  // run the dialog
432  result = gtk_dialog_run (GTK_DIALOG(dialog));
433  if (result == GTK_RESPONSE_OK)
434  {
435  gboolean use_old = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(use_old_path_head));
436  gboolean use_new = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(use_new_path_head));
437 
438  if (use_old || use_new)
439  change_relative_and_absolute_uri_paths (old_path_head_uri, use_old,
440  new_path_head_uri, use_new);
441  }
442  g_free (new_path_head_uri);
443  gtk_widget_destroy (dialog);
444 }
gchar * gnc_uri_get_scheme(const gchar *uri)
Extracts the scheme from a uri.
gchar * gnc_prefs_get_string(const gchar *group, const gchar *pref_name)
Get a string value from the preferences backend.
gchar * gnc_file_path_absolute(const gchar *prefix, const gchar *relative)
Given a prefix and a relative path, return the absolute path.
utility functions for the GnuCash UI
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
const char * xaccTransGetReadOnly(Transaction *trans)
Returns a non-NULL value if this Transaction was marked as read-only with some specific "reason" text...
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
gchar * gnc_uri_get_path(const gchar *uri)
Extracts the path part from a uri.
gboolean gnc_prefs_set_string(const gchar *group, const gchar *pref_name, const gchar *value)
Store a string into the preferences backend.
Definition: gnc-prefs.c:320
const gchar * gnc_userdata_dir(void)
Ensure that the user&#39;s configuration directory exists and is minimally populated. ...
const char * xaccTransGetDocLink(const Transaction *trans)
Gets the transaction Document Link.
Account handling public routines.
Gnome specific utility functions.
Generic api to store and retrieve preferences.
Business Invoice Interface.
gboolean qof_book_is_readonly(const QofBook *book)
Return whether the book is read only.
Definition: qofbook.cpp:497
void xaccTransSetDocLink(Transaction *trans, const char *doclink)
Sets the transaction Document Link.
Utility functions for convert uri in separate components and back.
QofCollection * qof_book_get_collection(const QofBook *book, QofIdType entity_type)
Return The table of entities of the given type.
Definition: qofbook.cpp:521
File path resolution utility functions.
API for Transactions and Splits (journal entries)
gchar * gnc_uri_create_uri(const gchar *scheme, const gchar *hostname, gint32 port, const gchar *username, const gchar *password, const gchar *path)
Composes a normalized uri starting from its separate components.