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, ¤t);
575
576 do {
577 if (ec->backwards ^ inverse) {
578 ret = gtk_text_iter_backward_search (¤t, ec->term,
579 (ec->matchcase? 0: GTK_TEXT_SEARCH_CASE_INSENSITIVE),
580 &mstart, &mend, NULL);
581 } else {
582 gtk_text_iter_forward_chars (¤t, strlen (ec->term));
583 ret = gtk_text_iter_forward_search (¤t, 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, ¤t);
632
633 if (backwards)
634 ret = gtk_text_iter_backward_search (¤t, term,
635 (matchcase? 0: GTK_TEXT_SEARCH_CASE_INSENSITIVE),
636 &mstart, &mend, NULL);
637 else
638 ret = gtk_text_iter_forward_search (¤t, 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, ¤t);
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, ¤t);
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