GnuCash c935c2f+
Loading...
Searching...
No Matches
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. */
43static QofLogModule log_module = GNC_MOD_GUI;
44
45/* =================================================================== */
46
47static gchar *
48convert_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
77gchar *
78gnc_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
102gchar *
103gnc_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
123gchar *
124gnc_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
135gchar *
136gnc_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
165static gchar *
166doclink_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
205gchar *
206gnc_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
213void
214gnc_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
267typedef 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
276static void
277update_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
313static void
314update_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
359static void
360change_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
390void
391gnc_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}
Account handling public routines.
API for Transactions and Splits (journal entries)
File path resolution utility functions.
Gnome specific utility functions.
Generic api to store and retrieve preferences.
utility functions for the GnuCash UI
Utility functions for convert uri in separate components and back.
Business Invoice Interface.
QofCollection * qof_book_get_collection(const QofBook *book, QofIdType entity_type)
Return The table of entities of the given type.
Definition qofbook.cpp:521
gboolean qof_book_is_readonly(const QofBook *book)
Return whether the book is read only.
Definition qofbook.cpp:497
const char * xaccTransGetDocLink(const Transaction *trans)
Gets the transaction Document Link.
void xaccTransSetDocLink(Transaction *trans, const char *doclink)
Sets the transaction Document Link.
const char * xaccTransGetReadOnly(Transaction *trans)
Returns a non-NULL value if this Transaction was marked as read-only with some specific "reason" text...
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
gchar * gnc_prefs_get_string(const gchar *group, const gchar *pref_name)
Get a string value from the preferences backend.
gboolean gnc_prefs_set_string(const gchar *group, const gchar *pref_name, const gchar *value)
Store a string into the preferences backend.
gchar * gnc_uri_get_path(const gchar *uri)
Extracts the path part from a uri.
gchar * gnc_uri_get_scheme(const gchar *uri)
Extracts the scheme from a uri.
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.
QofBook reference.
Definition qofbook-p.hpp:47