1 /*
2  * This file is part of GNOME LaTeX.
3  *
4  * From gedit-utils.c:
5  * Copyright (C) 1998, 1999 - Alex Roberts, Evan Lawrence
6  * Copyright (C) 2000, 2002 - Chema Celorio, Paolo Maggi
7  * Copyright (C) 2003-2005 - Paolo Maggi
8  *
9  * Copyright (C) 2014-2020 - Sébastien Wilmet <swilmet@gnome.org>
10  *
11  * GNOME LaTeX is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * GNOME LaTeX is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with GNOME LaTeX.  If not, see <http://www.gnu.org/licenses/>.
23  */
24 
25 /**
26  * SECTION:utils
27  * @title: LatexilaUtils
28  * @short_description: Utility functions
29  *
30  * Various utility functions.
31  */
32 
33 #include "config.h"
34 #include "latexila-utils.h"
35 #include <string.h>
36 #include <tepl/tepl.h>
37 #include "latexila-synctex.h"
38 
39 #if HAVE_DCONF_MIGRATION
40 #include "dh-dconf-migration.h"
41 #endif
42 
43 /**
44  * latexila_utils_register_icons:
45  *
46  * Register the GNOME LaTeX icons to the #GtkIconTheme as built-in icons.
47  *
48  * TODO: prefix icon names with "glatex-", so for example badbox.png would be
49  * "glatex-badbox".
50  */
51 void
latexila_utils_register_icons(void)52 latexila_utils_register_icons (void)
53 {
54 	gtk_icon_theme_add_resource_path (gtk_icon_theme_get_default (),
55 					  "/org/gnome/gnome-latex/stock-icons/");
56 }
57 
58 /**
59  * latexila_utils_get_pixbuf_from_icon_name:
60  * @icon_name: an icon name.
61  * @icon_size: an icon size.
62  *
63  * Returns: (nullable) (transfer full): the corresponding #GdkPixbuf.
64  */
65 GdkPixbuf *
latexila_utils_get_pixbuf_from_icon_name(const gchar * icon_name,GtkIconSize icon_size)66 latexila_utils_get_pixbuf_from_icon_name (const gchar *icon_name,
67 					  GtkIconSize  icon_size)
68 {
69 	gint size;
70 	GdkPixbuf *pixbuf;
71 	GError *error = NULL;
72 
73 	gtk_icon_size_lookup (icon_size, &size, NULL);
74 
75 	pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
76 					   icon_name,
77 					   size,
78 					   0,
79 					   &error);
80 
81 	if (error != NULL)
82 	{
83 		g_warning ("Error when loading icon \"%s\": %s", icon_name, error->message);
84 		g_error_free (error);
85 	}
86 
87 	return pixbuf;
88 }
89 
90 static gboolean
default_document_viewer_is_evince(const gchar * uri)91 default_document_viewer_is_evince (const gchar *uri)
92 {
93 	GFile *file;
94 	GAppInfo *app_info;
95 	const gchar *executable;
96 	gboolean ret;
97 	GError *error = NULL;
98 
99 	file = g_file_new_for_uri (uri);
100 	app_info = g_file_query_default_handler (file, NULL, &error);
101 	g_object_unref (file);
102 
103 	if (error != NULL)
104 	{
105 		g_warning ("Impossible to know if evince is the default document viewer: %s",
106 			   error->message);
107 
108 		g_error_free (error);
109 		return FALSE;
110 	}
111 
112 	executable = g_app_info_get_executable (app_info);
113 	ret = strstr (executable, "evince") != NULL;
114 
115 	g_object_unref (app_info);
116 	return ret;
117 }
118 
119 /**
120  * latexila_utils_show_uri:
121  * @widget: (nullable): the associated #GtkWidget, or %NULL.
122  * @uri: the URI to show.
123  * @timestamp: a timestamp.
124  * @error: (out) (optional): a %NULL #GError, or %NULL.
125  *
126  * Shows the @uri. If the URI is a PDF file and if Evince is the default
127  * document viewer, this function also connects the Evince window so the
128  * backward search works (switch from the PDF to the source file).
129  */
130 void
latexila_utils_show_uri(GtkWidget * widget,const gchar * uri,guint32 timestamp,GError ** error)131 latexila_utils_show_uri (GtkWidget    *widget,
132 			 const gchar  *uri,
133 			 guint32       timestamp,
134 			 GError      **error)
135 {
136 	GtkWindow *parent = NULL;
137 
138 	g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget));
139 	g_return_if_fail (uri != NULL);
140 	g_return_if_fail (error == NULL || *error == NULL);
141 
142 	if (widget != NULL)
143 	{
144 		GtkWidget *toplevel;
145 
146 		toplevel = gtk_widget_get_toplevel (widget);
147 		if (gtk_widget_is_toplevel (toplevel) &&
148 		    GTK_IS_WINDOW (toplevel))
149 		{
150 			parent = GTK_WINDOW (toplevel);
151 		}
152 	}
153 
154 	if (gtk_show_uri_on_window (parent, uri, timestamp, error))
155 	{
156 		gchar *extension = tepl_utils_get_file_extension (uri);
157 
158 		if (g_strcmp0 (extension, ".pdf") == 0 &&
159 		    default_document_viewer_is_evince (uri))
160 		{
161 			LatexilaSynctex *synctex = latexila_synctex_get_instance ();
162 			latexila_synctex_connect_evince_window (synctex, uri);
163 		}
164 
165 		g_free (extension);
166 	}
167 }
168 
169 /**
170  * latexila_utils_get_dialog_component:
171  * @title: the title of the dialog component.
172  * @widget: the widget displayed below the title.
173  *
174  * Gets a #GtkDialog component. When a dialog contains several components, or
175  * logical groups, this function is useful to attach the @widget with a @title.
176  * The title will be in bold, left-aligned, and the widget will have a left
177  * margin.
178  *
179  * Returns: (transfer floating): the dialog component containing the @title and
180  * the @widget.
181  */
182 GtkWidget *
latexila_utils_get_dialog_component(const gchar * title,GtkWidget * widget)183 latexila_utils_get_dialog_component (const gchar *title,
184 				     GtkWidget   *widget)
185 {
186 	GtkContainer *grid;
187 	GtkWidget *label;
188 	gchar *markup;
189 
190 	grid = GTK_CONTAINER (gtk_grid_new ());
191 	gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL);
192 	gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
193 	gtk_container_set_border_width (grid, 6);
194 
195 	/* Title in bold, left-aligned. */
196 	label = gtk_label_new (NULL);
197 	markup = g_strdup_printf ("<b>%s</b>", title);
198 	gtk_label_set_markup (GTK_LABEL (label), markup);
199 	gtk_widget_set_halign (label, GTK_ALIGN_START);
200 	gtk_container_add (grid, label);
201 
202 	/* Left margin for the widget. */
203 	gtk_widget_set_margin_start (widget, 12);
204 	gtk_container_add (grid, widget);
205 
206 	g_free (markup);
207 	return GTK_WIDGET (grid);
208 }
209 
210 /**
211  * latexila_utils_join_widgets:
212  * @widget_top: the #GtkWidget at the top.
213  * @widget_bottom: the #GtkWidget at the bottom.
214  *
215  * Joins two widgets vertically, with junction sides.
216  *
217  * Returns: (transfer floating): a #GtkContainer containing @widget_top and
218  * @widget_bottom.
219  */
220 /* TODO try to simply add the GTK_STYLE_CLASS_LINKED class to the
221  * GtkStyleContext of a GtkBox. Test also in RTL (right-to-left) text, although
222  * here it's a vertical container so it should not matter.
223  */
224 GtkWidget *
latexila_utils_join_widgets(GtkWidget * widget_top,GtkWidget * widget_bottom)225 latexila_utils_join_widgets (GtkWidget *widget_top,
226 			     GtkWidget *widget_bottom)
227 {
228 	GtkStyleContext *context;
229 	GtkBox *vbox;
230 
231 	g_return_val_if_fail (GTK_IS_WIDGET (widget_top), NULL);
232 	g_return_val_if_fail (GTK_IS_WIDGET (widget_bottom), NULL);
233 
234 	context = gtk_widget_get_style_context (widget_top);
235 	gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
236 
237 	context = gtk_widget_get_style_context (widget_bottom);
238 	gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);
239 
240 	vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
241 	gtk_box_pack_start (vbox, widget_top, TRUE, TRUE, 0);
242 	gtk_box_pack_start (vbox, widget_bottom, FALSE, FALSE, 0);
243 
244 	return GTK_WIDGET (vbox);
245 }
246 
247 static void
migrate_latexila_to_gnome_latex_gsettings(void)248 migrate_latexila_to_gnome_latex_gsettings (void)
249 {
250 #if HAVE_DCONF_MIGRATION
251 	DhDconfMigration *migration;
252 	gint i;
253 
254 	const gchar *keys[] =
255 	{
256 		"preferences/editor/auto-save",
257 		"preferences/editor/auto-save-interval",
258 		"preferences/editor/bracket-matching",
259 		"preferences/editor/create-backup-copy",
260 		"preferences/editor/display-line-numbers",
261 		"preferences/editor/editor-font",
262 		"preferences/editor/forget-no-tabs",
263 		"preferences/editor/highlight-current-line",
264 		"preferences/editor/highlight-misspelled-words",
265 		"preferences/editor/insert-spaces",
266 		"preferences/editor/reopen-files",
267 		"preferences/editor/scheme",
268 		"preferences/editor/spell-checking-language",
269 		"preferences/editor/tabs-size",
270 		"preferences/editor/use-default-font",
271 		"preferences/file-browser/current-directory",
272 		"preferences/file-browser/show-build-files",
273 		"preferences/file-browser/show-hidden-files",
274 		"preferences/latex/automatic-clean",
275 		"preferences/latex/clean-extensions",
276 		"preferences/latex/disabled-default-build-tools",
277 		"preferences/latex/enabled-default-build-tools",
278 		"preferences/latex/interactive-completion",
279 		"preferences/latex/interactive-completion-num",
280 		"preferences/latex/no-confirm-clean",
281 		"preferences/ui/bottom-panel-visible",
282 		"preferences/ui/edit-toolbar-visible",
283 		"preferences/ui/main-toolbar-visible",
284 		"preferences/ui/show-build-badboxes",
285 		"preferences/ui/show-build-warnings",
286 		"preferences/ui/side-panel-component",
287 		"preferences/ui/side-panel-visible",
288 		"state/window/documents",
289 		"state/window/side-panel-size",
290 		"state/window/size",
291 		"state/window/state",
292 		"state/window/structure-paned-position",
293 		"state/window/vertical-paned-position",
294 		NULL
295 	};
296 
297 	migration = _dh_dconf_migration_new ();
298 
299 	for (i = 0; keys[i] != NULL; i++)
300 	{
301 		const gchar *cur_key = keys[i];
302 		gchar *cur_glatex_key;
303 		gchar *cur_latexila_key;
304 
305 		cur_glatex_key = g_strconcat ("/org/gnome/gnome-latex/", cur_key, NULL);
306 		cur_latexila_key = g_strconcat ("/org/gnome/latexila/", cur_key, NULL);
307 
308 		_dh_dconf_migration_migrate_key (migration,
309 						 cur_glatex_key,
310 						 cur_latexila_key,
311 						 NULL);
312 
313 		g_free (cur_glatex_key);
314 		g_free (cur_latexila_key);
315 	}
316 
317 	_dh_dconf_migration_sync_and_free (migration);
318 #else
319 	g_warning ("LaTeXila -> GNOME LaTeX dconf migration not supported.");
320 #endif
321 }
322 
323 static void
migrate_latexila_to_gnome_latex_copy_file(GFile * latexila_file,GFile * glatex_file)324 migrate_latexila_to_gnome_latex_copy_file (GFile *latexila_file,
325 					   GFile *glatex_file)
326 {
327 	GError *error = NULL;
328 
329 	tepl_utils_create_parent_directories (glatex_file, NULL, &error);
330 	if (error != NULL)
331 	{
332 		goto out;
333 	}
334 
335 	g_file_copy (latexila_file,
336 		     glatex_file,
337 		     G_FILE_COPY_TARGET_DEFAULT_PERMS,
338 		     NULL, NULL, NULL,
339 		     &error);
340 
341 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) ||
342 	    g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
343 	{
344 		g_clear_error (&error);
345 	}
346 
347 out:
348 	if (error != NULL)
349 	{
350 		g_warning ("Error when migrating LaTeXila to GNOME LaTeX user data file: %s",
351 			   error->message);
352 		g_clear_error (&error);
353 	}
354 }
355 
356 static void
migrate_latexila_to_gnome_latex_most_used_symbols(void)357 migrate_latexila_to_gnome_latex_most_used_symbols (void)
358 {
359 	GFile *latexila_file;
360 	GFile *glatex_file;
361 
362 	latexila_file = g_file_new_build_filename (g_get_user_data_dir (),
363 						   "latexila",
364 						   "most_used_symbols.xml",
365 						   NULL);
366 
367 	glatex_file = g_file_new_build_filename (g_get_user_data_dir (),
368 						 "gnome-latex",
369 						 "most_used_symbols.xml",
370 						 NULL);
371 
372 	migrate_latexila_to_gnome_latex_copy_file (latexila_file, glatex_file);
373 
374 	g_object_unref (latexila_file);
375 	g_object_unref (glatex_file);
376 }
377 
378 static void
migrate_latexila_to_gnome_latex_projects(void)379 migrate_latexila_to_gnome_latex_projects (void)
380 {
381 	GFile *latexila_file;
382 	GFile *glatex_file;
383 
384 	latexila_file = g_file_new_build_filename (g_get_user_data_dir (),
385 						   "latexila",
386 						   "projects.xml",
387 						   NULL);
388 
389 	glatex_file = g_file_new_build_filename (g_get_user_data_dir (),
390 						 "gnome-latex",
391 						 "projects.xml",
392 						 NULL);
393 
394 	migrate_latexila_to_gnome_latex_copy_file (latexila_file, glatex_file);
395 
396 	g_object_unref (latexila_file);
397 	g_object_unref (glatex_file);
398 }
399 
400 static void
migrate_latexila_to_gnome_latex_personal_build_tools(void)401 migrate_latexila_to_gnome_latex_personal_build_tools (void)
402 {
403 	GFile *latexila_file;
404 	GFile *glatex_file;
405 
406 	latexila_file = g_file_new_build_filename (g_get_user_config_dir (),
407 						   "latexila",
408 						   "build_tools.xml",
409 						   NULL);
410 
411 	glatex_file = g_file_new_build_filename (g_get_user_config_dir (),
412 						 "gnome-latex",
413 						 "build_tools.xml",
414 						 NULL);
415 
416 	migrate_latexila_to_gnome_latex_copy_file (latexila_file, glatex_file);
417 
418 	g_object_unref (latexila_file);
419 	g_object_unref (glatex_file);
420 }
421 
422 static void
migrate_latexila_to_gnome_latex_personal_templates_tex_files(void)423 migrate_latexila_to_gnome_latex_personal_templates_tex_files (void)
424 {
425 	GFile *latexila_dir;
426 	GFile *glatex_dir;
427 	GFileEnumerator *enumerator;
428 	GError *error = NULL;
429 
430 	latexila_dir = g_file_new_build_filename (g_get_user_data_dir (),
431 						  "latexila",
432 						  NULL);
433 	glatex_dir = g_file_new_build_filename (g_get_user_data_dir (),
434 						"gnome-latex",
435 						NULL);
436 
437 	enumerator = g_file_enumerate_children (latexila_dir,
438 						G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
439 						G_FILE_QUERY_INFO_NONE,
440 						NULL,
441 						&error);
442 
443 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
444 	{
445 		g_clear_error (&error);
446 		goto out;
447 	}
448 
449 	if (enumerator == NULL || error != NULL)
450 	{
451 		goto out;
452 	}
453 
454 	while (TRUE)
455 	{
456 		GFileInfo *child_file_info;
457 		GFile *child_file;
458 		const gchar *child_name;
459 		GFile *glatex_child_file;
460 
461 		g_file_enumerator_iterate (enumerator, &child_file_info, &child_file, NULL, &error);
462 		if (child_file == NULL || error != NULL)
463 		{
464 			break;
465 		}
466 
467 		child_name = g_file_info_get_display_name (child_file_info);
468 		if (child_name == NULL || !g_str_has_suffix (child_name, ".tex"))
469 		{
470 			continue;
471 		}
472 
473 		glatex_child_file = g_file_get_child (glatex_dir, child_name);
474 		migrate_latexila_to_gnome_latex_copy_file (child_file, glatex_child_file);
475 		g_object_unref (glatex_child_file);
476 	}
477 
478 out:
479 	if (error != NULL)
480 	{
481 		g_warning ("Error when migrating LaTeXila to GNOME LaTeX personal templates: %s",
482 			   error->message);
483 		g_clear_error (&error);
484 	}
485 
486 	g_object_unref (latexila_dir);
487 	g_object_unref (glatex_dir);
488 	g_clear_object (&enumerator);
489 }
490 
491 #define TEMPLATES_RC_FILE_OLD_GROUP_NAME "[LaTeXila]\n"
492 #define TEMPLATES_RC_FILE_NEW_GROUP_NAME "[Personal templates]\n"
493 
494 static void
migrate_latexila_to_gnome_latex_personal_templates_rc_file(void)495 migrate_latexila_to_gnome_latex_personal_templates_rc_file (void)
496 {
497 	GFile *latexila_file;
498 	gchar *content = NULL;
499 	GFile *glatex_file = NULL;
500 	GFileOutputStream *output_stream = NULL;
501 	GError *error = NULL;
502 
503 	/* Load old RC file. */
504 	latexila_file = g_file_new_build_filename (g_get_user_data_dir (),
505 						   "latexila",
506 						   "templatesrc",
507 						   NULL);
508 
509 	g_file_load_contents (latexila_file, NULL, &content, NULL, NULL, &error);
510 
511 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
512 	{
513 		g_clear_error (&error);
514 		goto out;
515 	}
516 
517 	if (error != NULL || content == NULL)
518 	{
519 		goto out;
520 	}
521 
522 	/* Modify group name. */
523 	if (g_str_has_prefix (content, TEMPLATES_RC_FILE_OLD_GROUP_NAME))
524 	{
525 		gchar *modified_content;
526 
527 		modified_content = g_strconcat (TEMPLATES_RC_FILE_NEW_GROUP_NAME,
528 						content + strlen (TEMPLATES_RC_FILE_OLD_GROUP_NAME),
529 						NULL);
530 		g_free (content);
531 		content = modified_content;
532 	}
533 
534 	/* Save to new location. */
535 	glatex_file = g_file_new_build_filename (g_get_user_data_dir (),
536 						 "gnome-latex",
537 						 "templatesrc",
538 						 NULL);
539 
540 	output_stream = g_file_create (glatex_file, G_FILE_CREATE_NONE, NULL, &error);
541 
542 	if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
543 	{
544 		g_clear_error (&error);
545 		goto out;
546 	}
547 
548 	if (error != NULL || output_stream == NULL)
549 	{
550 		goto out;
551 	}
552 
553 	g_output_stream_write_all (G_OUTPUT_STREAM (output_stream),
554 				   content,
555 				   strlen (content),
556 				   NULL,
557 				   NULL,
558 				   &error);
559 
560 out:
561 	if (error != NULL)
562 	{
563 		g_warning ("Error when migrating LaTeXila to GNOME LaTeX personal templates: %s",
564 			   error->message);
565 		g_clear_error (&error);
566 	}
567 
568 	g_object_unref (latexila_file);
569 	g_free (content);
570 	g_clear_object (&glatex_file);
571 	g_clear_object (&output_stream);
572 }
573 
574 static void
migrate_latexila_to_gnome_latex_personal_templates(void)575 migrate_latexila_to_gnome_latex_personal_templates (void)
576 {
577 	migrate_latexila_to_gnome_latex_personal_templates_tex_files ();
578 	migrate_latexila_to_gnome_latex_personal_templates_rc_file ();
579 }
580 
581 /**
582  * latexila_utils_migrate_latexila_to_gnome_latex:
583  *
584  * Migrates the #GSettings values and user data/config files from LaTeXila to
585  * GNOME LaTeX, so that users don't lose all their settings.
586  *
587  * TODO in >= 2025: delete this code.
588  */
589 void
latexila_utils_migrate_latexila_to_gnome_latex(void)590 latexila_utils_migrate_latexila_to_gnome_latex (void)
591 {
592 	GSettings *settings;
593 
594 	settings = g_settings_new ("org.gnome.gnome-latex");
595 
596 	if (!g_settings_get_boolean (settings, "latexila-to-gnome-latex-migration-done"))
597 	{
598 		migrate_latexila_to_gnome_latex_gsettings ();
599 		migrate_latexila_to_gnome_latex_most_used_symbols ();
600 		migrate_latexila_to_gnome_latex_projects ();
601 		migrate_latexila_to_gnome_latex_personal_build_tools ();
602 		migrate_latexila_to_gnome_latex_personal_templates ();
603 
604 		g_settings_set_boolean (settings, "latexila-to-gnome-latex-migration-done", TRUE);
605 	}
606 
607 	g_object_unref (settings);
608 }
609