1 /**
2  * @file    editor.c
3  * @brief
4  *
5  * Copyright (C) 2009 Gummi Developers
6  * All Rights reserved.
7  *
8  * Permission is hereby granted, free of charge, to any person
9  * obtaining a copy of this software and associated documentation
10  * files (the "Software"), to deal in the Software without
11  * restriction, including without limitation the rights to use,
12  * copy, modify, merge, publish, distribute, sublicense, and/or sell
13  * copies of the Software, and to permit persons to whom the
14  * Software is furnished to do so, subject to the following
15  * conditions:
16  *
17  * The above copyright notice and this permission notice shall be
18  * included in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27  * OTHER DEALINGS IN THE SOFTWARE.
28  */
29 
30 
31 #include "editor.h"
32 
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/stat.h>
36 
37 #include <gtksourceview/gtksource.h>
38 #include <gtkspell/gtkspell.h>
39 #include <glib/gstdio.h>
40 #include <gtk/gtk.h>
41 #include <unistd.h>
42 
43 #include "configfile.h"
44 #include "constants.h"
45 #include "environment.h"
46 #include "utils.h"
47 
48 static void on_inserted_text(GtkTextBuffer *textbuffer,GtkTextIter *location,
49                              gchar *text,gint len, gpointer user_data);
50 static void on_delete_range(GtkTextBuffer *textbuffer,GtkTextIter *start,
51                              GtkTextIter *end, gpointer user_data);
52 
53 const gchar style[][3][20] = {
54     { "tool_bold", "\\textbf{", "}" },
55     { "tool_italic", "\\emph{", "}" },
56     { "tool_unline", "\\underline{", "}" },
57     { "tool_left", "\\begin{flushleft}", "\\end{flushleft}"},
58     { "tool_center", "\\begin{center}", "\\end{center}"},
59     { "tool_right", "\\begin{flushright}", "\\end{flushright}"}
60 };
61 
editor_new(GuMotion * mc)62 GuEditor* editor_new (GuMotion* mc) {
63     GuEditor* ec = g_new0 (GuEditor, 1);
64 
65     /* File related member initialization */
66     ec->workfd = -1;
67     ec->fdname = NULL;
68     ec->filename = NULL;   /* current opened file name in workspace */
69     ec->basename = NULL;   /* use this to form .dvi/.ps/.log etc. files */
70     ec->pdffile = NULL;
71     ec->workfile = NULL;
72     ec->bibfile = NULL;
73     ec->projfile = NULL;
74 
75     GtkSourceLanguageManager* manager = gtk_source_language_manager_new ();
76     GtkSourceLanguage* lang = gtk_source_language_manager_get_language (manager,
77             "latex");
78     ec->buffer = gtk_source_buffer_new_with_language (lang);
79     ec->view = GTK_SOURCE_VIEW (gtk_source_view_new_with_buffer (ec->buffer));
80     ec->stylemanager = gtk_source_style_scheme_manager_get_default ();
81     ec->errortag = gtk_text_tag_new ("error");
82     ec->searchtag = gtk_text_tag_new ("search");
83     ec->editortags = gtk_text_buffer_get_tag_table (ec_buffer);
84     ec->replace_activated = FALSE;
85     ec->term = NULL;
86 
87     ec->css = gtk_css_provider_new();
88 
89     /* Set source view style provider so we can use ec->css to set font later */
90     GtkStyleContext* context = gtk_widget_get_style_context (GTK_WIDGET(ec->view));
91     gtk_style_context_add_provider(context,
92                                    GTK_STYLE_PROVIDER(ec->css),
93                                    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
94 
95     gtk_source_view_set_tab_width
96         (ec->view, config_get_integer ("Editor", "tabwidth"));
97     gtk_source_view_set_insert_spaces_instead_of_tabs
98         (ec->view, config_get_boolean ("Editor", "spaces_instof_tabs"));
99     gtk_source_view_set_auto_indent
100         (ec->view, config_get_boolean ("Editor", "autoindentation"));
101 
102     if (config_get_boolean ("Editor", "spelling"))
103         editor_activate_spellchecking (ec, TRUE);
104 
105     editor_sourceview_config (ec);
106     gtk_text_buffer_set_modified (ec_buffer, FALSE);
107 
108     /* Register motion callback */
109     ec->sigid[0] = g_signal_connect (ec->view, "key-press-event",
110                 G_CALLBACK (on_key_press_cb), mc);
111     ec->sigid[1] = g_signal_connect (ec->view, "key-release-event",
112                 G_CALLBACK (on_key_release_cb), mc);
113     ec->sigid[2] = g_signal_connect (ec->buffer, "changed",
114                 G_CALLBACK (check_preview_timer), NULL);
115 
116     ec->sigid[3] = g_signal_connect_after(ec->buffer, "insert-text",
117                 G_CALLBACK(on_inserted_text), ec);
118     ec->sigid[4] = g_signal_connect_after(ec->buffer, "delete-range",
119                 G_CALLBACK(on_delete_range), ec);
120 
121     return ec;
122 }
123 
editor_destroy(GuEditor * ec)124 void editor_destroy (GuEditor* ec) {
125     gint i = 0;
126 
127     for (i = 0; i < 2; ++i) {
128         if (g_signal_handler_is_connected (ec->view, ec->sigid[i])) {
129             g_signal_handler_disconnect (ec->view, ec->sigid[i]);
130         }
131     }
132     for (i = 2; i < 5; ++i) {
133         if (g_signal_handler_is_connected (ec->buffer, ec->sigid[i])) {
134             g_signal_handler_disconnect (ec->buffer, ec->sigid[i]);
135         }
136     }
137 
138     editor_fileinfo_cleanup (ec);
139     g_free(ec);
140 }
141 
on_inserted_text(GtkTextBuffer * textbuffer,GtkTextIter * location,gchar * text,gint len,gpointer user_data)142 static void on_inserted_text(GtkTextBuffer *textbuffer,GtkTextIter *location,
143                              gchar *text,gint len, gpointer user_data) {
144 
145     if (location == NULL || user_data == NULL) {
146         return;
147     }
148     GuEditor* e = GU_EDITOR(user_data);
149 
150     e->last_edit = *location;
151     e->sync_to_last_edit = TRUE;
152 }
153 
on_delete_range(GtkTextBuffer * textbuffer,GtkTextIter * start,GtkTextIter * end,gpointer user_data)154 static void on_delete_range(GtkTextBuffer *textbuffer,GtkTextIter *start,
155                              GtkTextIter *end, gpointer user_data) {
156 
157     if (start == NULL || user_data == NULL) {
158         return;
159     }
160     GuEditor* e = GU_EDITOR(user_data);
161 
162     e->last_edit = *start;
163     e->sync_to_last_edit = TRUE;
164 }
165 
166 /* FileInfo:
167  * When a TeX document includes materials from other files (image, documents,
168  * bibliography ... etc), pdflatex will try to find those files under the
169  * working directory if the include path is not absolute.
170  * Before Gummi svn505, gummi copies the TeX file to a temporarily directory
171  * and compile there, because of this, the included files can't be located if
172  * the include path is not absolute. In svn505 we copy the TeX file to the
173  * same directory as the original TeX document but named it as '.FILENAME.swp'.
174  * Since pdflatex refuses to compile TeX files with '.' prefixed, we have to
175  * set the environment variable 'openout_any=a'.
176  *
177  * For a newly created document, all files including the TeX file are stored
178  * under the ~/.cache folder (freedesktop XDG standard). For files that are
179  * already saved, only the workfile is saved under DIRNAME (FILENAME).
180  * Other compilation-related files are located in the temp directory.
181  *
182  * P.S. pdflatex will automatically strip the suffix, so for a file named
183  * FILE.tex under /absolute/path/:
184  *
185  * filename = /absolute/path/FILE.tex
186  * workfile = /absolute/path/.FILE.tex.swp
187  * pdffile = ~/cache/gummi/.FILE.tex.pdf
188  */
editor_fileinfo_update(GuEditor * ec,const gchar * filename)189 void editor_fileinfo_update (GuEditor* ec, const gchar* filename) {
190 
191     // directory should exist, but if not create ~/.cache/gummi:
192     if (!g_file_test (C_TMPDIR, G_FILE_TEST_IS_DIR)) {
193             slog (L_WARNING, ".cache directory does not exist, creating..\n");
194             g_mkdir_with_parents (C_TMPDIR, DIR_PERMS);
195     }
196 
197     if (ec->workfd != -1)
198         editor_fileinfo_cleanup (ec);
199 
200     ec->fdname = g_build_filename (C_TMPDIR, "gummi_XXXXXX", NULL);
201     ec->workfd = g_mkstemp (ec->fdname);
202 
203     // This is required for Windows 7, but not for Linux. It may also
204     // be the proper way for *nix, but I don't want to change this
205     // crucial piece of code at this stage of development -alexander
206     #ifdef WIN32
207 		close(ec->workfd);
208 	#endif
209 
210     if (filename) {
211         gchar* fname = NULL;
212         if (!g_path_is_absolute (filename)) {
213             fname = g_strdup_printf ("%s%c%s", g_get_current_dir(),
214                                      G_DIR_SEPARATOR, filename);
215         } else {
216             fname = g_strdup (filename);
217         }
218 
219         gchar* base = g_path_get_basename (fname);
220         gchar* dir = g_path_get_dirname (fname);
221         ec->filename = g_strdup (fname);
222         ec->basename = g_strdup_printf ("%s%c.%s", dir, G_DIR_SEPARATOR, base);
223         ec->workfile = g_strdup_printf ("%s.swp", ec->basename);
224         ec->pdffile =  g_strdup_printf ("%s%c.%s.pdf", C_TMPDIR,
225                                        G_DIR_SEPARATOR, base);
226         // Get last modified time
227         struct stat attr;
228         stat(fname, &attr);
229         ec->last_modtime = attr.st_mtime;
230 
231         g_free (fname);
232         g_free (base);
233         g_free (dir);
234     } else {
235         ec->workfile = g_strdup (ec->fdname);
236         ec->basename = g_strdup (ec->fdname);
237         ec->pdffile =  g_strdup_printf ("%s.pdf", ec->fdname);
238     }
239 }
240 
editor_fileinfo_update_biblio(GuEditor * ec,const gchar * filename)241 gboolean editor_fileinfo_update_biblio (GuEditor* ec,  const gchar* filename) {
242     g_free (ec->bibfile);
243 
244     if (ec->filename && !g_path_is_absolute (filename)) {
245         gchar* dirname = g_path_get_dirname (ec->filename);
246         ec->bibfile = g_build_filename (dirname, filename, NULL);
247         g_free (dirname);
248     } else
249         ec->bibfile = g_strdup (filename);
250     return utils_path_exists (ec->bibfile);
251 }
252 
editor_fileinfo_cleanup(GuEditor * ec)253 void editor_fileinfo_cleanup (GuEditor* ec) {
254     gchar* auxfile = NULL;
255     gchar* logfile = NULL;
256     gchar* syncfile = NULL;
257 
258     if (ec->filename) {
259         gchar* dirname = g_path_get_dirname (ec->filename);
260         gchar* basename = g_path_get_basename (ec->filename);
261         auxfile = g_strdup_printf ("%s%c.%s.aux", C_TMPDIR,
262                 G_DIR_SEPARATOR, basename);
263         logfile = g_strdup_printf ("%s%c.%s.log", C_TMPDIR,
264                 G_DIR_SEPARATOR, basename);
265         syncfile = g_strdup_printf ("%s%c.%s.synctex.gz", C_TMPDIR,
266                 G_DIR_SEPARATOR, basename);
267         g_free (basename);
268         g_free (dirname);
269     } else {
270         gchar* dirname = g_path_get_dirname (ec->workfile);
271         gchar* basename = g_path_get_basename (ec->workfile);
272         auxfile = g_strdup_printf ("%s.aux", ec->fdname);
273         logfile = g_strdup_printf ("%s.log", ec->fdname);
274         syncfile = g_strdup_printf ("%s.synctex.gz", ec->fdname);
275         g_free (basename);
276         g_free (dirname);
277     }
278 
279     // TODO: make a loop or maybe make register of created files? proc?
280 
281     close (ec->workfd);
282     ec->workfd = -1;
283 
284     g_remove (auxfile);
285     g_remove (logfile);
286     g_remove (syncfile);
287     g_remove (ec->fdname);
288     g_remove (ec->workfile);
289     g_remove (ec->pdffile);
290     g_remove (ec->basename);
291 
292     g_free (auxfile);
293     g_free (logfile);
294     g_free (syncfile);
295     g_free (ec->fdname);
296     g_free (ec->filename);
297     g_free (ec->workfile);
298     g_free (ec->pdffile);
299     g_free (ec->basename);
300 
301     ec->fdname = NULL;
302     ec->filename = NULL;
303     ec->workfile = NULL;
304     ec->pdffile = NULL;
305     ec->basename = NULL;
306 }
307 
editor_sourceview_config(GuEditor * ec)308 void editor_sourceview_config (GuEditor* ec) {
309     GtkWrapMode wrapmode = 0;
310 
311     gtk_source_buffer_set_highlight_matching_brackets (ec->buffer, TRUE);
312 
313     const gchar* style_scheme = config_get_string ("Editor", "style_scheme");
314     editor_set_style_scheme_by_id (ec, style_scheme);
315 
316     editor_set_font (ec, config_get_string ("Editor", "font"));
317 
318     gtk_source_view_set_show_line_numbers (
319                             GTK_SOURCE_VIEW (ec->view),
320                             config_get_boolean ("Editor", "line_numbers"));
321     gtk_source_view_set_highlight_current_line (
322                             GTK_SOURCE_VIEW (ec->view),
323                             config_get_boolean ("Editor", "highlighting"));
324 
325     // The condition 'textwrapping=False && wordwrapping=True' can't happen
326     if (config_get_boolean ("Editor", "textwrapping"))
327         wrapmode += 1;
328     if (config_get_boolean ("Editor", "wordwrapping"))
329         wrapmode += 1;
330 
331     gtk_text_view_set_wrap_mode (ec_view, wrapmode);
332 }
333 
editor_activate_spellchecking(GuEditor * ec,gboolean status)334 void editor_activate_spellchecking (GuEditor* ec, gboolean status) {
335     const gchar* lang = config_get_string ("Editor", "spelling_lang");
336     GError* err = NULL;
337     GtkSpellChecker* spell = 0;
338     if (status) {
339         spell = gtk_spell_checker_new();
340 
341         if (! (gtk_spell_checker_set_language (spell, lang, &err))) {
342             slog (L_ERROR, "gtk_spell_checker_set_language (): %s\n", err->message);
343             g_error_free (err);
344         }
345         if (! (gtk_spell_checker_attach (spell, ec_view))) {
346             slog (L_ERROR, "gtk_spell_checker_attach failed\n");
347         }
348     } else {
349         GtkSpellChecker* spell = gtk_spell_checker_get_from_text_view (ec_view);
350         if (spell)
351             gtk_spell_checker_detach (spell);
352     }
353     // TODO g_object_unref (spell); ?
354 }
355 
editor_fill_buffer(GuEditor * ec,const gchar * text)356 void editor_fill_buffer (GuEditor* ec, const gchar* text) {
357     gtk_text_buffer_begin_user_action (ec_buffer);
358     gtk_source_buffer_begin_not_undoable_action (ec->buffer);
359     gtk_widget_set_sensitive (GTK_WIDGET (ec->view), FALSE);
360     gtk_text_buffer_set_text (ec_buffer, text, strlen (text));
361     gtk_widget_set_sensitive (GTK_WIDGET (ec->view), TRUE);
362     gtk_source_buffer_end_not_undoable_action (ec->buffer);
363     gtk_text_buffer_end_user_action (ec_buffer);
364 
365     // place cursor in the beginning of the document
366     GtkTextIter start;
367     gtk_text_buffer_get_start_iter (ec_buffer, &start);
368     gtk_text_buffer_place_cursor (ec_buffer, &start);
369     gtk_widget_grab_focus (GTK_WIDGET (ec->view));
370     ec->sync_to_last_edit = FALSE;
371 }
372 
editor_grab_buffer(GuEditor * ec)373 gchar* editor_grab_buffer (GuEditor* ec) {
374     GtkTextIter start, end;
375     gtk_text_buffer_get_bounds (ec_buffer, &start, &end);
376     gchar* pstr = gtk_text_iter_get_text (&start, &end);
377     return pstr;
378 }
379 
editor_buffer_changed(GuEditor * ec)380 gboolean editor_buffer_changed (GuEditor* ec) {
381     if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (ec->buffer))) {
382         return TRUE;
383     }
384     return FALSE;
385 }
386 
editor_insert_package(GuEditor * ec,const gchar * package,const gchar * options)387 void editor_insert_package (GuEditor* ec, const gchar* package, const gchar* options) {
388     GtkTextIter start, mstart, mend, sstart, send;
389 
390     gchar* pkgstr = NULL;
391     if (options == NULL) {
392         pkgstr = g_strdup_printf ("\\usepackage{%s}\n", package);
393     }
394     else {
395         pkgstr = g_strdup_printf ("\\usepackage[%s]{%s}\n", options, package);
396     }
397 
398     gtk_text_buffer_get_start_iter (ec_buffer, &start);
399     gtk_text_iter_forward_search (&start, (gchar*)"\\begin{document}", 0,
400             &mstart, &mend, NULL);
401     if (!gtk_text_iter_backward_search (&mstart, pkgstr, 0, &sstart, &send,
402                 NULL)) {
403         gtk_source_buffer_begin_not_undoable_action (ec->buffer);
404         gtk_text_buffer_begin_user_action (ec_buffer);
405         gtk_text_buffer_insert (ec_buffer, &mstart, pkgstr, -1);
406         gtk_text_buffer_end_user_action (ec_buffer);
407         gtk_source_buffer_end_not_undoable_action (ec->buffer);
408         gtk_text_buffer_set_modified (ec_buffer, TRUE);
409     }
410     g_free (pkgstr);
411 }
412 
editor_insert_bib(GuEditor * ec,const gchar * package)413 void editor_insert_bib (GuEditor* ec, const gchar* package) {
414     GtkTextIter start, end, mstart, mend, sstart, send;
415     gchar* pkgstr = g_strdup_printf (
416             "\\bibliography{%s}{}\n\\bibliographystyle{plain}\n", package);
417     gtk_text_buffer_get_start_iter (ec_buffer, &start);
418     gtk_text_buffer_get_end_iter (ec_buffer, &end);
419     gtk_text_iter_backward_search (&end, (gchar*)"\\end{document}", 0,
420             &mstart, &mend, NULL);
421     if (!gtk_text_iter_forward_search (&start, "\\bibliography{", 0,
422                 &sstart, &send, NULL)) {
423         gtk_source_buffer_begin_not_undoable_action (ec->buffer);
424         gtk_text_buffer_begin_user_action (ec_buffer);
425         gtk_text_buffer_insert (ec_buffer, &mstart, pkgstr, -1);
426         gtk_text_buffer_end_user_action (ec_buffer);
427         gtk_source_buffer_end_not_undoable_action (ec->buffer);
428         gtk_text_buffer_set_modified (ec_buffer, TRUE);
429     }
430     g_free (pkgstr);
431 }
432 
editor_set_selection_textstyle(GuEditor * ec,const gchar * type)433 void editor_set_selection_textstyle (GuEditor* ec, const gchar* type) {
434     GtkTextIter start, end;
435     gint i = 0, selected = 0;
436     const gchar* selected_text = 0;
437     gint style_size = sizeof (style) / sizeof (style[0]);
438     gchar** result = NULL;
439     GError* err = NULL;
440     GRegex* match_str = NULL;
441     GMatchInfo* match_info = NULL;
442     gchar* outtext = NULL;
443     gchar* regexbuf = NULL;
444 
445     /* grab selected text */
446     gtk_text_buffer_get_selection_bounds (ec_buffer, &start, &end);
447     selected_text = gtk_text_iter_get_text (&start, &end);
448 
449     /* select style */
450     for (i = 0; i < style_size; ++i)
451         if (STR_EQU (style[i][0], type)) {
452             selected = i;
453             break;
454         }
455 
456     /* generate regex expression */
457     regexbuf = g_strdup_printf ("(.*)%s%s(.*)%s%s(.*)",
458             (style[selected][1][0] == '\\')? "\\": "",
459             style[selected][1],
460             (style[selected][2][0] == '\\')? "\\": "",
461             style[selected][2]);
462 
463     if (! (match_str = g_regex_new (regexbuf, G_REGEX_DOTALL, 0, &err))) {
464         slog (L_ERROR, "g_regex_new (): %s\n", err->message);
465         g_error_free (err);
466         goto cleanup;
467     }
468 
469     if (g_regex_match (match_str, selected_text, 0, &match_info)) {
470         result = g_match_info_fetch_all (match_info);
471         if (strlen (result[1]) == 0 && strlen (result[3]) == 0) {
472             /* already applied, so we remove it */
473             outtext = g_strdup (result[2]);
474         } else if (strlen (result[1]) != 0 || strlen (result[3]) != 0) {
475             /* the text contains a partially styled text, remove it and apply
476              * style to the whole text */
477             outtext = g_strdup_printf ("%s%s%s%s%s",
478                     style[selected][1], result[1], result[2], result[3],
479                     style[selected][2]);
480         }
481     } else { /* no previous style applied */
482         outtext = g_strdup_printf ("%s%s%s",
483                 style[selected][1], selected_text, style[selected][2]);
484     }
485 
486     gtk_text_buffer_begin_user_action (ec_buffer);
487     gtk_text_buffer_delete (ec_buffer, &start, &end);
488     gtk_text_buffer_insert (ec_buffer, &start, outtext, -1);
489     end = start;
490     gtk_text_iter_backward_chars (&start, strlen (outtext));
491     gtk_text_buffer_select_range (ec_buffer, &start, &end);
492     gtk_text_buffer_end_user_action (ec_buffer);
493     gtk_text_buffer_set_modified (ec_buffer, TRUE);
494 
495 cleanup:
496     g_free (outtext);
497     g_free (regexbuf);
498     g_strfreev (result);
499     g_match_info_free (match_info);
500     g_regex_unref (match_str);
501 }
502 
editor_apply_errortags(GuEditor * ec,gint * lines)503 void editor_apply_errortags (GuEditor* ec, gint* lines) {
504     GtkTextIter start, end;
505     gint count = 0;
506     /* remove the tag from the table if it is in threre */
507     if (gtk_text_tag_table_lookup (ec->editortags, "error"))
508         gtk_text_tag_table_remove (ec->editortags, ec->errortag);
509 
510     gtk_text_tag_table_add (ec->editortags, ec->errortag);
511     while (lines[count]) {
512         gtk_text_buffer_get_iter_at_line (ec_buffer,&start,lines[count]-1);
513         gtk_text_buffer_get_iter_at_line (ec_buffer, &end, lines[count]);
514         gtk_text_buffer_apply_tag (ec_buffer, ec->errortag, &start, &end);
515         ++count;
516     }
517 }
518 
editor_jumpto_search_result(GuEditor * ec,gint direction)519 void editor_jumpto_search_result (GuEditor* ec, gint direction) {
520     if (!ec->term) return;
521     if (direction == 1)
522         editor_search_next (ec, FALSE);
523     else
524         editor_search_next (ec, TRUE);
525 }
526 
editor_start_search(GuEditor * ec,const gchar * term,gboolean backwards,gboolean wholeword,gboolean matchcase)527 void editor_start_search (GuEditor* ec, const gchar* term,
528         gboolean backwards, gboolean wholeword, gboolean matchcase) {
529     /* save options */
530     if (ec->term != term) {
531         g_free (ec->term);
532         ec->term = g_strdup (term);
533     }
534 
535     ec->backwards = backwards;
536     ec->wholeword = wholeword;
537     ec->matchcase = matchcase;
538 
539     editor_apply_searchtag (ec);
540     editor_search_next (ec, FALSE);
541 }
542 
editor_apply_searchtag(GuEditor * ec)543 void editor_apply_searchtag (GuEditor* ec) {
544     GtkTextIter start, mstart, mend;
545     gboolean ret = FALSE;
546 
547     gtk_text_buffer_get_start_iter (ec_buffer, &start);
548 
549     if (gtk_text_tag_table_lookup (ec->editortags, "search"))
550         gtk_text_tag_table_remove (ec->editortags, ec->searchtag);
551     gtk_text_tag_table_add (ec->editortags, ec->searchtag);
552 
553     while (TRUE) {
554 		do {
555 			ret = gtk_text_iter_forward_search (&start, ec->term,
556 	                (ec->matchcase? 0: GTK_TEXT_SEARCH_CASE_INSENSITIVE),
557 	                &mstart, &mend, NULL);
558 			start = mend;
559 		} while (ec->wholeword && ret && (!gtk_text_iter_starts_word(&mstart) ||
560 				!gtk_text_iter_ends_word(&mend)));
561 
562         if (ret) {
563             gtk_text_buffer_apply_tag (ec_buffer, ec->searchtag,
564                     &mstart, &mend);
565 //            start =  mend;
566         } else break;
567     }
568 }
569 
editor_search_next(GuEditor * ec,gboolean inverse)570 void editor_search_next (GuEditor* ec, gboolean inverse) {
571     GtkTextIter current, start, end, mstart, mend;
572     gboolean ret = FALSE, response = FALSE;
573 
574     editor_get_current_iter (ec, &current);
575 
576 	do {
577 	    if (ec->backwards ^ inverse) {
578 	        ret = gtk_text_iter_backward_search (&current, ec->term,
579 	                (ec->matchcase? 0: GTK_TEXT_SEARCH_CASE_INSENSITIVE),
580 	                &mstart, &mend, NULL);
581 	    } else {
582 	        gtk_text_iter_forward_chars (&current, strlen (ec->term));
583 	        ret = gtk_text_iter_forward_search (&current, ec->term,
584 	                (ec->matchcase? 0: GTK_TEXT_SEARCH_CASE_INSENSITIVE),
585 	                &mstart, &mend, NULL);
586 		}
587 		current = mend;
588 	} while (ec->wholeword && ret && (!gtk_text_iter_starts_word(&mstart) ||
589 			!gtk_text_iter_ends_word(&mend)));
590 
591     if (ret) {
592         gtk_text_buffer_select_range (ec_buffer, &mstart, &mend);
593         editor_scroll_to_cursor (ec);
594     }
595     /* check if the top/bottom is reached */
596     gtk_text_buffer_get_start_iter (ec_buffer, &start);
597     gtk_text_buffer_get_end_iter (ec_buffer, &end);
598 
599     if (!ret) {
600         if (ec->backwards ^ inverse) {
601             response = utils_yes_no_dialog (
602                     _("Top reached, search from bottom?"));
603             if (GTK_RESPONSE_YES == response) {
604                 gtk_text_buffer_place_cursor (ec_buffer, &end);
605                 editor_search_next (ec, inverse);
606             }
607         } else {
608             response = utils_yes_no_dialog (
609                     _("Bottom reached, search from top?"));
610             if (GTK_RESPONSE_YES == response) {
611                 gtk_text_buffer_place_cursor (ec_buffer, &start);
612                 editor_search_next (ec, inverse);
613             }
614         }
615     }
616 }
617 
editor_start_replace_next(GuEditor * ec,const gchar * term,const gchar * rterm,gboolean backwards,gboolean wholeword,gboolean matchcase)618 void editor_start_replace_next (GuEditor* ec, const gchar* term,
619         const gchar* rterm, gboolean backwards, gboolean wholeword,
620         gboolean matchcase) {
621     GtkTextIter current, mstart, mend;
622     gboolean ret = FALSE;
623 
624     if (!ec->replace_activated) {
625         ec->replace_activated = TRUE;
626         editor_start_search (ec, term, backwards, wholeword, matchcase);
627         return;
628     }
629 
630     /* place cursor to the next result */
631     editor_get_current_iter (ec, &current);
632 
633     if (backwards)
634        ret = gtk_text_iter_backward_search (&current, term,
635                 (matchcase? 0: GTK_TEXT_SEARCH_CASE_INSENSITIVE),
636                 &mstart, &mend, NULL);
637     else
638        ret = gtk_text_iter_forward_search (&current, term,
639                 (matchcase? 0: GTK_TEXT_SEARCH_CASE_INSENSITIVE),
640                 &mstart, &mend, NULL);
641 
642     if (ret && (!wholeword || (wholeword
643             && gtk_text_iter_starts_word (&mstart)
644             && gtk_text_iter_ends_word (&mend)))) {
645         gtk_text_buffer_begin_user_action (ec_buffer);
646         gtk_text_buffer_delete (ec_buffer, &mstart, &mend);
647         gtk_text_buffer_insert (ec_buffer, &mstart, rterm, -1);
648         gtk_text_buffer_end_user_action (ec_buffer);
649         editor_search_next (ec, FALSE);
650     }
651     return;
652 }
653 
editor_start_replace_all(GuEditor * ec,const gchar * term,const gchar * rterm,gboolean backwards,gboolean wholeword,gboolean matchcase)654 void editor_start_replace_all (GuEditor* ec, const gchar* term,
655         const gchar* rterm, gboolean backwards, gboolean wholeword,
656         gboolean matchcase) {
657     GtkTextIter start, mstart, mend;
658     gboolean ret = FALSE;
659     gboolean action_started = FALSE;
660 
661     gtk_text_buffer_get_start_iter (ec_buffer, &start);
662 
663     while (TRUE) {
664         ret = gtk_text_iter_forward_search (&start, term,
665                 (matchcase? 0: GTK_TEXT_SEARCH_CASE_INSENSITIVE),
666                 &mstart, &mend, NULL);
667 
668         if (ret && (!wholeword || (wholeword
669                 && gtk_text_iter_starts_word (&mstart)
670                 && gtk_text_iter_ends_word (&mend)))) {
671             if (!action_started) {
672                 gtk_text_buffer_begin_user_action (ec_buffer);
673                 action_started = TRUE;
674             }
675             gtk_text_buffer_delete (ec_buffer, &mstart, &mend);
676             gtk_text_buffer_insert (ec_buffer, &mstart, rterm, -1);
677             start =  mstart;
678         } else break;
679     }
680     if (action_started) {
681         gtk_text_buffer_end_user_action (ec_buffer);
682     }
683 }
684 
editor_get_current_iter(GuEditor * ec,GtkTextIter * current)685 void editor_get_current_iter (GuEditor* ec, GtkTextIter* current) {
686     GtkTextMark* mark = gtk_text_buffer_get_insert (ec_buffer);
687     gtk_text_buffer_get_iter_at_mark (ec_buffer, current, mark);
688 }
689 
editor_scroll_to_cursor(GuEditor * ec)690 void editor_scroll_to_cursor (GuEditor* ec) {
691     gtk_text_view_scroll_to_mark (ec_view,
692                                  gtk_text_buffer_get_insert (ec_buffer),
693                                  0.25, FALSE, 0.0, 0.0);
694 }
695 
editor_scroll_to_line(GuEditor * ec,gint line)696 void editor_scroll_to_line (GuEditor* ec, gint line) {
697     if (ec == NULL) {
698         return;
699     }
700     GtkTextIter iter;
701     gtk_text_buffer_get_iter_at_line(ec_buffer, &iter, line);
702     gtk_text_buffer_place_cursor(ec_buffer, &iter);
703     editor_scroll_to_cursor(ec);
704     ec->sync_to_last_edit = FALSE;
705 }
706 
editor_undo_change(GuEditor * ec)707 void editor_undo_change (GuEditor* ec) {
708     GtkTextIter current;
709     if (gtk_source_buffer_can_undo (ec->buffer)) {
710         gtk_source_buffer_undo (ec->buffer);
711         editor_get_current_iter (ec, &current);
712         editor_scroll_to_cursor (ec);
713         gtk_text_buffer_set_modified (ec_buffer, TRUE);
714     }
715 }
716 
editor_redo_change(GuEditor * ec)717 void editor_redo_change (GuEditor* ec) {
718     GtkTextIter current;
719     if (gtk_source_buffer_can_redo (ec->buffer)) {
720         gtk_source_buffer_redo (ec->buffer);
721         editor_get_current_iter (ec, &current);
722         editor_scroll_to_cursor (ec);
723         gtk_text_buffer_set_modified (ec_buffer, TRUE);
724     }
725 }
726 
editor_set_font(GuEditor * ec,const gchar * font)727 void editor_set_font (GuEditor* ec, const gchar* font) {
728     // surely there has to be a better solution to transform
729     // a string like 'Monospace 12' into css syntax right..?
730     gchar** font_elems = g_strsplit (font, " ", BUFSIZ);
731     gchar* style = g_strdup_printf ("* { font: %spx '%s'; }",
732                                     font_elems[1], font_elems[0]);
733 
734     gtk_css_provider_load_from_data (ec->css, style, -1, NULL);
735     g_free (style);
736 }
737 
738 
editor_set_style_scheme_by_id(GuEditor * ec,const gchar * id)739 void editor_set_style_scheme_by_id (GuEditor* ec, const gchar* id) {
740 
741     GtkSourceStyleScheme* scheme =
742         gtk_source_style_scheme_manager_get_scheme (ec->stylemanager, id);
743     slog (L_INFO, "Setting styles scheme to %s\n", id);
744 
745     if (!scheme) {
746         slog (L_ERROR, "No style scheme %s found, setting to classic\n", id);
747         scheme = gtk_source_style_scheme_manager_get_scheme (ec->stylemanager,
748                 "classic");
749     }
750     gtk_source_buffer_set_style_scheme (ec->buffer, scheme);
751 
752     set_style_fg_bg(G_OBJECT (ec->searchtag), scheme, "search-match", "yellow");
753     set_style_fg_bg(G_OBJECT (ec->errortag), scheme,"def:error",  "red");
754 }
755 
gdk_rgba_luminance(GdkRGBA c)756 static inline gdouble gdk_rgba_luminance (GdkRGBA c) {
757     return 0.2126 * c.red + 0.7152 * c.green + 0.0722 * c.blue;
758 }
759 
760 /**
761  *  Sets a object's fore- and background color to that of scheme's style
762  *  "styleName". If no background color is defined in the style, defaultBG is
763  *  used. defaultBG can be any valid parameter to gdk_rgba_parse().
764  *  If only a foreground color was defined and it has not enough contrast to the
765  *  default background, it will be overwritten. The foreground color will either
766  *  be white or black, which has more contrast.
767  */
set_style_fg_bg(GObject * obj,GtkSourceStyleScheme * scheme,gchar * styleName,gchar * defaultBG)768 void set_style_fg_bg (GObject* obj, GtkSourceStyleScheme* scheme,
769                       gchar* styleName, gchar* defaultBG) {
770     GtkSourceStyle *style = NULL;
771 
772     gchar *bg = NULL;
773     gchar *fg = NULL;
774     gboolean foreground_set;
775     gboolean background_set;
776     GdkRGBA foreground;
777     GdkRGBA background;
778 
779 
780     if (scheme == NULL) {
781         goto set_style_fg_bg_return_defaults;
782     }
783 
784     style = gtk_source_style_scheme_get_style (scheme, styleName);
785 
786     if (style == NULL) {
787         goto set_style_fg_bg_return_defaults;
788     }
789 
790     // Get properties of style
791     g_object_get (style,
792                   "foreground-set", &foreground_set,
793                   "foreground", &fg,
794                   "background-set", &background_set,
795                   "background", &bg,
796                   NULL);
797 
798     if (foreground_set) {
799         if (fg == NULL || !gdk_rgba_parse(&foreground, fg))
800             foreground_set = FALSE;
801     }
802 
803     if (background_set) {
804         if (bg == NULL || !gdk_rgba_parse(&background, bg))
805             background_set = FALSE;
806     }
807 
808     g_free(fg);
809     g_free(bg);
810 
811     if (background_set && foreground_set) {
812         // We trust the style to set both to good values
813         // Do nothing
814     } else if (!background_set && foreground_set) {
815         // Set bg to default and check if fg has enough contrast
816         gdk_rgba_parse(&background, defaultBG);
817 
818         gdouble diff = ABS(gdk_rgba_luminance(foreground) -
819                            gdk_rgba_luminance(background));
820 
821         if (diff < 0.5) {
822             slog(L_INFO, "Style \"%s\" defines a foreground, but no background "
823                          "color. As the fourground color has not enough "
824                          "contrast to Gummis default background color, the "
825                          "foreground color has been adjusted.\n", styleName);
826             if (gdk_rgba_luminance(background) > 0.5) {
827                 gdk_rgba_parse(&foreground, "black");
828             } else {
829                 gdk_rgba_parse(&foreground, "white");
830             }
831         }
832     } else if (background_set && !foreground_set) {
833         // Choose a fg = white or black, which has more contrast
834         if (gdk_rgba_luminance(background) > 0.5) {
835             gdk_rgba_parse(&foreground, "black");
836         } else {
837             gdk_rgba_parse(&foreground, "white");
838         }
839     } else {
840         // none set, set defaults
841         goto set_style_fg_bg_return_defaults;
842     }
843 
844     g_object_set(obj, "foreground", gdk_rgba_to_string(&foreground),
845                       "background", gdk_rgba_to_string(&background),
846                        NULL);
847     return;
848 
849 set_style_fg_bg_return_defaults:
850 
851     // No valid style, set defaults
852     gdk_rgba_parse(&background, defaultBG);
853 
854     if (gdk_rgba_luminance(background) > 0.5) {
855         gdk_rgba_parse(&foreground, "black");
856     } else {
857         gdk_rgba_parse(&foreground, "white");
858     }
859 
860     g_object_set (obj, "foreground-gdk", &foreground,
861                         "background-gdk", &background, NULL);
862 
863 }
864 
865 /* The following functions are taken from gedit and partially modified */
866 
schemes_compare(gconstpointer a,gconstpointer b)867 gint schemes_compare (gconstpointer a, gconstpointer b) {
868     GtkSourceStyleScheme *scheme_a = (GtkSourceStyleScheme *)a;
869     GtkSourceStyleScheme *scheme_b = (GtkSourceStyleScheme *)b;
870 
871     const gchar *name_a = gtk_source_style_scheme_get_name (scheme_a);
872     const gchar *name_b = gtk_source_style_scheme_get_name (scheme_b);
873 
874     return g_utf8_collate (name_a, name_b);
875 }
876 
editor_list_style_scheme_sorted(void)877 GList* editor_list_style_scheme_sorted (void) {
878     const gchar * const * scheme_ids;
879     GList *schemes = NULL;
880     GtkSourceStyleSchemeManager* manager =
881         gtk_source_style_scheme_manager_get_default();
882 
883     // add custom style path ~/.config/gummi/styles:
884     gchar* custom = g_build_path (G_DIR_SEPARATOR_S, C_GUMMI_CONFDIR, "styles", NULL);
885     if (g_file_test (custom, G_FILE_TEST_IS_DIR)) {
886         gtk_source_style_scheme_manager_append_search_path (manager, custom);
887     }
888     g_free (custom);
889 
890     scheme_ids = gtk_source_style_scheme_manager_get_scheme_ids (manager);
891 
892     while (*scheme_ids != NULL) {
893         GtkSourceStyleScheme *scheme;
894         scheme = gtk_source_style_scheme_manager_get_scheme (manager,
895                 *scheme_ids);
896         schemes = g_list_prepend (schemes, scheme);
897         ++scheme_ids;
898     }
899 
900     if (schemes != NULL)
901         schemes = g_list_sort (schemes, (GCompareFunc)schemes_compare);
902 
903     return schemes;
904 }
905