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 *
convert_uri_to_abs_path(const gchar * path_head,const gchar * uri,gchar * uri_scheme,gboolean return_uri)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 *
gnc_doclink_get_unescape_uri(const gchar * path_head,const gchar * uri,gchar * uri_scheme)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 *
gnc_doclink_get_use_uri(const gchar * path_head,const gchar * uri,gchar * uri_scheme)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 *
gnc_doclink_get_unescaped_just_uri(const gchar * uri)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 *
gnc_doclink_convert_trans_link_uri(gpointer trans,gboolean book_ro)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 *
doclink_get_path_head_and_set(gboolean * path_head_set)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 *
gnc_doclink_get_path_head(void)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
gnc_doclink_set_path_head_label(GtkWidget * path_head_label,const gchar * incoming_path_head,const gchar * prefix)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;
274 }DoclinkUpdate;
275
276 static void
update_invoice_uri(QofInstance * data,gpointer user_data)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
update_trans_uri(QofInstance * data,gpointer user_data)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
change_relative_and_absolute_uri_paths(const gchar * old_path_head_uri,gboolean change_old,const gchar * new_path_head_uri,gboolean change_new)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
gnc_doclink_pref_path_head_changed(GtkWindow * parent,const gchar * old_path_head_uri)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 GtkWidget *old_hbox, *new_hbox;
398 gint result;
399 gchar *new_path_head_uri = gnc_doclink_get_path_head ();
400
401 if (g_strcmp0 (old_path_head_uri, new_path_head_uri) == 0)
402 {
403 g_free (new_path_head_uri);
404 return;
405 }
406
407 /* Create the dialog box */
408 builder = gtk_builder_new();
409 gnc_builder_add_from_file (builder, "dialog-doclink.glade", "link_path_head_changed_dialog");
410 dialog = GTK_WIDGET(gtk_builder_get_object (builder, "link_path_head_changed_dialog"));
411
412 if (parent != NULL)
413 gtk_window_set_transient_for (GTK_WINDOW(dialog), GTK_WINDOW(parent));
414
415 // Set the name and style context for this widget so it can be easily manipulated with css
416 gtk_widget_set_name (GTK_WIDGET(dialog), "gnc-id-doclink-change");
417 gnc_widget_style_context_add_class (GTK_WIDGET(dialog), "gnc-class-doclink");
418
419 old_head_label = GTK_WIDGET(gtk_builder_get_object (builder, "existing_path_head"));
420 new_head_label = GTK_WIDGET(gtk_builder_get_object (builder, "new_path_head"));
421
422 use_old_path_head = GTK_WIDGET(gtk_builder_get_object (builder, "use_old_path_head"));
423 use_new_path_head = GTK_WIDGET(gtk_builder_get_object (builder, "use_new_path_head"));
424
425 // display path head text and test if present
426 gnc_doclink_set_path_head_label (old_head_label, old_path_head_uri, _("Existing"));
427 gnc_doclink_set_path_head_label (new_head_label, new_path_head_uri, _("New"));
428
429 gtk_widget_show (dialog);
430 g_object_unref (G_OBJECT(builder));
431
432 // run the dialog
433 result = gtk_dialog_run (GTK_DIALOG(dialog));
434 if (result == GTK_RESPONSE_OK)
435 {
436 gboolean use_old = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(use_old_path_head));
437 gboolean use_new = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(use_new_path_head));
438
439 if (use_old || use_new)
440 change_relative_and_absolute_uri_paths (old_path_head_uri, use_old,
441 new_path_head_uri, use_new);
442 }
443 g_free (new_path_head_uri);
444 gtk_widget_destroy (dialog);
445 }
446