1 /*
2 * pluma-bookmarks-plugin.c - Bookmarking for pluma
3 *
4 * Copyright (C) 2008 Jesse van den Kieboom
5 * Copyright (C) 2020-2021 MATE Developers
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include "pluma-bookmarks-plugin.h"
27
28 #include <stdlib.h>
29 #include <glib/gi18n-lib.h>
30 #include <gtk/gtk.h>
31 #include <gmodule.h>
32 #include <gtksourceview/gtksource.h>
33
34 #include <pluma/pluma-window-activatable.h>
35 #include <pluma/pluma-debug.h>
36 #include <pluma/pluma-window.h>
37 #include <pluma/pluma-document.h>
38
39 #define BOOKMARK_CATEGORY "PlumaBookmarksPluginBookmark"
40 #define BOOKMARK_PRIORITY 1
41
42 #define INSERT_DATA_KEY "PlumaBookmarksInsertData"
43 #define METADATA_ATTR "metadata::pluma-bookmarks"
44
45 #define MESSAGE_OBJECT_PATH "/plugins/bookmarks"
46 #define BUS_CONNECT(bus, name, data) pluma_message_bus_connect(bus, MESSAGE_OBJECT_PATH, #name, (PlumaMessageCallback) message_##name##_cb, data, NULL)
47
48 typedef struct
49 {
50 GtkSourceMark *bookmark;
51 GtkTextMark *mark;
52 } InsertTracker;
53
54 typedef struct
55 {
56 GSList *trackers;
57 guint user_action;
58 } InsertData;
59
60 static void on_style_scheme_notify (GObject *object,
61 GParamSpec *pspec,
62 PlumaView *view);
63
64 static void on_delete_range (GtkTextBuffer *buffer,
65 GtkTextIter *start,
66 GtkTextIter *end,
67 gpointer user_data);
68
69 static void on_insert_text_before (GtkTextBuffer *buffer,
70 GtkTextIter *location,
71 gchar *text,
72 gint len,
73 InsertData *data);
74
75 static void on_begin_user_action (GtkTextBuffer *buffer,
76 InsertData *data);
77
78 static void on_end_user_action (GtkTextBuffer *buffer,
79 InsertData *data);
80
81 static void on_toggle_bookmark_activate (GtkAction *action,
82 PlumaBookmarksPlugin *plugin);
83 static void on_next_bookmark_activate (GtkAction *action,
84 PlumaBookmarksPlugin *plugin);
85 static void on_previous_bookmark_activate (GtkAction *action,
86 PlumaBookmarksPlugin *plugin);
87 static void on_tab_added (PlumaWindow *window,
88 PlumaTab *tab,
89 PlumaBookmarksPlugin *plugin);
90 static void on_tab_removed (PlumaWindow *window,
91 PlumaTab *tab,
92 PlumaBookmarksPlugin *plugin);
93
94 static void add_bookmark (GtkSourceBuffer *buffer, GtkTextIter *iter);
95 static void remove_bookmark (GtkSourceBuffer *buffer, GtkTextIter *iter);
96 static void toggle_bookmark (GtkSourceBuffer *buffer, GtkTextIter *iter);
97
98 static void pluma_window_activatable_iface_init (PlumaWindowActivatableInterface *iface);
99
100 struct _PlumaBookmarksPluginPrivate
101 {
102 PlumaWindow *window;
103
104 GtkActionGroup *action_group;
105 guint ui_id;
106 };
107
108 G_DEFINE_DYNAMIC_TYPE_EXTENDED (PlumaBookmarksPlugin,
109 pluma_bookmarks_plugin,
110 PEAS_TYPE_EXTENSION_BASE,
111 0,
112 G_ADD_PRIVATE_DYNAMIC (PlumaBookmarksPlugin)
113 G_IMPLEMENT_INTERFACE_DYNAMIC (PLUMA_TYPE_WINDOW_ACTIVATABLE,
114 pluma_window_activatable_iface_init))
115
116 enum
117 {
118 PROP_0,
119 PROP_WINDOW
120 };
121
122 static void
pluma_bookmarks_plugin_init(PlumaBookmarksPlugin * plugin)123 pluma_bookmarks_plugin_init (PlumaBookmarksPlugin *plugin)
124 {
125 pluma_debug_message (DEBUG_PLUGINS, "PlumaBookmarksPlugin initializing");
126
127 plugin->priv = pluma_bookmarks_plugin_get_instance_private (plugin);
128 }
129
130 static void
pluma_bookmarks_plugin_dispose(GObject * object)131 pluma_bookmarks_plugin_dispose (GObject *object)
132 {
133 PlumaBookmarksPlugin *plugin = PLUMA_BOOKMARKS_PLUGIN (object);
134
135 pluma_debug_message (DEBUG_PLUGINS, "PlumaBookmarksPlugin disposing");
136
137 if (plugin->priv->action_group != NULL)
138 {
139 g_object_unref (plugin->priv->action_group);
140 plugin->priv->action_group = NULL;
141 }
142
143 if (plugin->priv->window != NULL)
144 {
145 g_object_unref (plugin->priv->window);
146 plugin->priv->window = NULL;
147 }
148
149 G_OBJECT_CLASS (pluma_bookmarks_plugin_parent_class)->dispose (object);
150 }
151
152 static void
pluma_bookmarks_plugin_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)153 pluma_bookmarks_plugin_set_property (GObject *object,
154 guint prop_id,
155 const GValue *value,
156 GParamSpec *pspec)
157 {
158 PlumaBookmarksPlugin *plugin = PLUMA_BOOKMARKS_PLUGIN (object);
159
160 switch (prop_id)
161 {
162 case PROP_WINDOW:
163 plugin->priv->window = PLUMA_WINDOW (g_value_dup_object (value));
164 break;
165
166 default:
167 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
168 break;
169 }
170 }
171
172 static void
pluma_bookmarks_plugin_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)173 pluma_bookmarks_plugin_get_property (GObject *object,
174 guint prop_id,
175 GValue *value,
176 GParamSpec *pspec)
177 {
178 PlumaBookmarksPlugin *plugin = PLUMA_BOOKMARKS_PLUGIN (object);
179
180 switch (prop_id)
181 {
182 case PROP_WINDOW:
183 g_value_set_object (value, plugin->priv->window);
184 break;
185
186 default:
187 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
188 break;
189 }
190 }
191
192 static void
free_insert_data(InsertData * data)193 free_insert_data (InsertData *data)
194 {
195 g_slice_free (InsertData, data);
196 }
197
198 static GtkActionEntry const action_entries[] = {
199 {"ToggleBookmark", NULL, N_("Toggle Bookmark"), "<Control><Alt>B",
200 N_("Toggle bookmark status of the current line"),
201 G_CALLBACK (on_toggle_bookmark_activate)},
202 {"NextBookmark", NULL, N_("Go to Next Bookmark"), "<Control>B",
203 N_("Go to the next bookmark"),
204 G_CALLBACK (on_next_bookmark_activate)},
205 {"PreviousBookmark", NULL, N_("Go to Previous Bookmark"), "<Control><Shift>B",
206 N_("Go to the previous bookmark"),
207 G_CALLBACK (on_previous_bookmark_activate)}
208 };
209
210 static gchar const uidefinition[] = ""
211 "<ui>"
212 " <menubar name='MenuBar'>"
213 " <menu name='EditMenu' action='Edit'>"
214 " <placeholder name='EditOps_6'>"
215 " <menuitem action='ToggleBookmark'/>"
216 " <menuitem action='PreviousBookmark'/>"
217 " <menuitem action='NextBookmark'/>"
218 " </placeholder>"
219 " </menu>"
220 " </menubar>"
221 "</ui>";
222
223 static void
install_menu(PlumaBookmarksPlugin * plugin)224 install_menu (PlumaBookmarksPlugin *plugin)
225 {
226 PlumaBookmarksPluginPrivate *priv;
227 GtkUIManager *manager;
228 GError *error = NULL;
229
230 priv = plugin->priv;
231 manager = pluma_window_get_ui_manager (priv->window);
232 priv->action_group = gtk_action_group_new ("PlumaBookmarksPluginActions");
233
234 gtk_action_group_set_translation_domain (priv->action_group,
235 GETTEXT_PACKAGE);
236
237 gtk_action_group_add_actions (priv->action_group,
238 action_entries,
239 G_N_ELEMENTS (action_entries),
240 plugin);
241
242 gtk_ui_manager_insert_action_group (manager, priv->action_group, -1);
243 priv->ui_id = gtk_ui_manager_add_ui_from_string (manager, uidefinition, -1, &error);
244
245 if (!priv->ui_id)
246 {
247 g_warning ("Could not load UI: %s", error->message);
248 g_error_free (error);
249 }
250 }
251
252 static void
uninstall_menu(PlumaBookmarksPlugin * plugin)253 uninstall_menu (PlumaBookmarksPlugin *plugin)
254 {
255 PlumaBookmarksPluginPrivate *priv;
256 GtkUIManager *manager;
257
258 priv = plugin->priv;
259 manager = pluma_window_get_ui_manager (priv->window);
260
261 gtk_ui_manager_remove_ui (manager, priv->ui_id);
262 gtk_ui_manager_remove_action_group (manager, priv->action_group);
263
264 g_object_unref (priv->action_group);
265 priv->action_group = NULL;
266 }
267
268 static void
remove_all_bookmarks(GtkSourceBuffer * buffer)269 remove_all_bookmarks (GtkSourceBuffer *buffer)
270 {
271 GtkTextIter start;
272 GtkTextIter end;
273
274 pluma_debug (DEBUG_PLUGINS);
275
276 gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end);
277 gtk_source_buffer_remove_source_marks (buffer,
278 &start,
279 &end,
280 BOOKMARK_CATEGORY);
281 }
282
283 static void
disable_bookmarks(PlumaView * view)284 disable_bookmarks (PlumaView *view)
285 {
286 GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
287 gpointer data;
288
289 gtk_source_view_set_show_line_marks (GTK_SOURCE_VIEW (view), FALSE);
290 remove_all_bookmarks (GTK_SOURCE_BUFFER (buffer));
291
292 g_signal_handlers_disconnect_by_func (buffer, on_style_scheme_notify, view);
293 g_signal_handlers_disconnect_by_func (buffer, on_delete_range, NULL);
294
295 data = g_object_get_data (G_OBJECT (buffer), INSERT_DATA_KEY);
296
297 g_signal_handlers_disconnect_by_func (buffer, on_insert_text_before, data);
298 g_signal_handlers_disconnect_by_func (buffer, on_begin_user_action, data);
299 g_signal_handlers_disconnect_by_func (buffer, on_end_user_action, data);
300
301 g_object_set_data (G_OBJECT (buffer), INSERT_DATA_KEY, NULL);
302 }
303
304 static GdkPixbuf *
get_bookmark_pixbuf(PlumaBookmarksPlugin * plugin)305 get_bookmark_pixbuf (PlumaBookmarksPlugin *plugin)
306 {
307 GdkPixbuf *pixbuf;
308 gint width;
309 GError *error = NULL;
310
311 gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL);
312 pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
313 "user-bookmarks-symbolic",
314 (width * 2) / 3,
315 0,
316 &error);
317
318 if (error != NULL)
319 {
320 g_warning ("Could not load theme icon user-bookmarks-symbolic: %s",
321 error->message);
322 g_error_free (error);
323 }
324
325 return pixbuf;
326 }
327
328 static void
update_background_color(GtkSourceMarkAttributes * attrs,GtkSourceBuffer * buffer)329 update_background_color (GtkSourceMarkAttributes *attrs,
330 GtkSourceBuffer *buffer)
331 {
332 GtkSourceStyleScheme *scheme;
333 GtkSourceStyle *style;
334
335 scheme = gtk_source_buffer_get_style_scheme (buffer);
336 style = gtk_source_style_scheme_get_style (scheme, "search-match");
337
338 if (style)
339 {
340 gboolean bgset;
341 gchar *bg;
342
343 g_object_get (style, "background-set", &bgset, "background", &bg, NULL);
344
345 if (bgset)
346 {
347 GdkRGBA color;
348
349 gdk_rgba_parse (&color, bg);
350 gtk_source_mark_attributes_set_background (attrs, &color);
351 g_free (bg);
352
353 return;
354 }
355 }
356
357 gtk_source_mark_attributes_set_background (attrs, NULL);
358 }
359
360 static void
enable_bookmarks(PlumaView * view,PlumaBookmarksPlugin * plugin)361 enable_bookmarks (PlumaView *view,
362 PlumaBookmarksPlugin *plugin)
363 {
364 GdkPixbuf *pixbuf;
365
366 pixbuf = get_bookmark_pixbuf (plugin);
367
368 /* Make sure the category pixbuf is set */
369 if (pixbuf)
370 {
371 GtkTextBuffer *buffer;
372 GtkSourceMarkAttributes *attrs;
373 InsertData *data;
374
375 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
376
377 attrs = gtk_source_mark_attributes_new ();
378
379 update_background_color (attrs, GTK_SOURCE_BUFFER (buffer));
380 gtk_source_mark_attributes_set_pixbuf (attrs, pixbuf);
381 g_object_unref (pixbuf);
382
383 gtk_source_view_set_mark_attributes (GTK_SOURCE_VIEW (view),
384 BOOKMARK_CATEGORY,
385 attrs,
386 BOOKMARK_PRIORITY);
387
388 gtk_source_view_set_show_line_marks (GTK_SOURCE_VIEW (view), TRUE);
389
390 g_signal_connect (buffer,
391 "notify::style-scheme",
392 G_CALLBACK (on_style_scheme_notify),
393 view);
394
395 g_signal_connect (buffer,
396 "delete-range",
397 G_CALLBACK (on_delete_range),
398 NULL);
399
400 data = g_slice_new0 (InsertData);
401
402 g_object_set_data_full (G_OBJECT (buffer),
403 INSERT_DATA_KEY,
404 data,
405 (GDestroyNotify) free_insert_data);
406
407 g_signal_connect (buffer,
408 "insert-text",
409 G_CALLBACK (on_insert_text_before),
410 data);
411
412 g_signal_connect (buffer,
413 "begin-user-action",
414 G_CALLBACK (on_begin_user_action),
415 data);
416
417 g_signal_connect (buffer,
418 "end-user-action",
419 G_CALLBACK (on_end_user_action),
420 data);
421
422 }
423 else
424 {
425 g_warning ("Could not set bookmark icon!");
426 }
427 }
428
429 static void
load_bookmarks(PlumaView * view,gchar ** bookmarks)430 load_bookmarks (PlumaView *view,
431 gchar **bookmarks)
432 {
433 GtkSourceBuffer *buf;
434 GtkTextIter iter;
435 gint tot_lines;
436 gint i;
437
438 pluma_debug (DEBUG_PLUGINS);
439
440 buf = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
441
442 gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (buf), &iter);
443 tot_lines = gtk_text_iter_get_line (&iter);
444
445 for (i = 0; bookmarks != NULL && bookmarks[i] != NULL; i++)
446 {
447 gint line;
448
449 line = atoi (bookmarks[i]);
450
451 if (line >= 0 && line < tot_lines)
452 {
453 GSList *marks;
454
455 gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (buf),
456 &iter, line);
457
458 marks = gtk_source_buffer_get_source_marks_at_iter (buf, &iter,
459 BOOKMARK_CATEGORY);
460 if (marks == NULL)
461 /* Add new bookmark */
462 gtk_source_buffer_create_source_mark (buf,
463 NULL,
464 BOOKMARK_CATEGORY,
465 &iter);
466 else
467 g_slist_free (marks);
468 }
469 }
470 }
471
472 static void
load_bookmark_metadata(PlumaView * view)473 load_bookmark_metadata (PlumaView *view)
474 {
475 PlumaDocument *doc;
476 gchar *bookmarks_attr;
477
478 doc = PLUMA_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
479 bookmarks_attr = pluma_document_get_metadata (doc, METADATA_ATTR);
480
481 if (bookmarks_attr != NULL)
482 {
483 gchar **bookmarks;
484
485 bookmarks = g_strsplit (bookmarks_attr, ",", -1);
486 g_free (bookmarks_attr);
487
488 load_bookmarks (view, bookmarks);
489
490 g_strfreev (bookmarks);
491 }
492 }
493
494 typedef gboolean (*IterSearchFunc)(GtkSourceBuffer *buffer, GtkTextIter *iter, const gchar *category);
495 typedef void (*CycleFunc)(GtkTextBuffer *buffer, GtkTextIter *iter);
496
497 static void
goto_bookmark(PlumaWindow * window,GtkSourceView * view,GtkTextIter * iter,IterSearchFunc func,CycleFunc cycle_func)498 goto_bookmark (PlumaWindow *window,
499 GtkSourceView *view,
500 GtkTextIter *iter,
501 IterSearchFunc func,
502 CycleFunc cycle_func)
503 {
504 GtkTextBuffer *buffer;
505 GtkTextIter at;
506 GtkTextIter end;
507
508 if (view == NULL)
509 view = GTK_SOURCE_VIEW (pluma_window_get_active_view (window));
510
511 g_return_if_fail (view != NULL);
512
513 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
514
515 if (iter == NULL)
516 gtk_text_buffer_get_iter_at_mark (buffer,
517 &at,
518 gtk_text_buffer_get_insert (buffer));
519 else
520 at = *iter;
521
522 /* Move the iter to the beginning of the line, where the bookmarks are */
523 gtk_text_iter_set_line_offset (&at, 0);
524
525 /* Try to find the next bookmark */
526 if (!func (GTK_SOURCE_BUFFER (buffer), &at, BOOKMARK_CATEGORY))
527 {
528 GSList *marks;
529
530 /* cycle through */
531 cycle_func (buffer, &at);
532 gtk_text_iter_set_line_offset (&at, 0);
533
534 marks = gtk_source_buffer_get_source_marks_at_iter (GTK_SOURCE_BUFFER (buffer),
535 &at,
536 BOOKMARK_CATEGORY);
537
538 if (!marks && !func (GTK_SOURCE_BUFFER (buffer), &at, BOOKMARK_CATEGORY))
539 return;
540
541 g_slist_free (marks);
542 }
543
544 end = at;
545 if (!gtk_text_iter_forward_visible_line (&end))
546 gtk_text_buffer_get_end_iter (buffer, &end);
547 else
548 gtk_text_iter_backward_char (&end);
549
550 gtk_text_buffer_select_range (buffer, &at, &end);
551 gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (view), &at, 0.3, FALSE, 0, 0);
552 }
553
554 static void
message_get_view_iter(PlumaWindow * window,PlumaMessage * message,GtkSourceView ** view,GtkTextIter * iter)555 message_get_view_iter (PlumaWindow *window,
556 PlumaMessage *message,
557 GtkSourceView **view,
558 GtkTextIter *iter)
559 {
560 if (pluma_message_has_key (message, "view"))
561 pluma_message_get (message, "view", view, NULL);
562 else
563 *view = GTK_SOURCE_VIEW (pluma_window_get_active_view (window));
564
565 g_return_if_fail (*view);
566
567 if (pluma_message_has_key (message, "iter"))
568 {
569 pluma_message_get (message, "iter", iter, NULL);
570 }
571 else
572 {
573 GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (*view));
574 gtk_text_buffer_get_iter_at_mark (buffer,
575 iter,
576 gtk_text_buffer_get_insert (buffer));
577 }
578 }
579
580 static void
message_toggle_cb(PlumaMessageBus * bus,PlumaMessage * message,PlumaWindow * window)581 message_toggle_cb (PlumaMessageBus *bus,
582 PlumaMessage *message,
583 PlumaWindow *window)
584 {
585 GtkSourceView *view = NULL;
586 GtkTextIter iter;
587
588 message_get_view_iter (window, message, &view, &iter);
589
590 g_return_if_fail (view);
591
592 toggle_bookmark (GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))),
593 &iter);
594 }
595
596 static void
message_add_cb(PlumaMessageBus * bus,PlumaMessage * message,PlumaWindow * window)597 message_add_cb (PlumaMessageBus *bus,
598 PlumaMessage *message,
599 PlumaWindow *window)
600 {
601 GtkSourceView *view = NULL;
602 GtkTextIter iter;
603
604 message_get_view_iter (window, message, &view, &iter);
605
606 g_return_if_fail (view);
607
608 add_bookmark (GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))),
609 &iter);
610 }
611
612 static void
message_remove_cb(PlumaMessageBus * bus,PlumaMessage * message,PlumaWindow * window)613 message_remove_cb (PlumaMessageBus *bus,
614 PlumaMessage *message,
615 PlumaWindow *window)
616 {
617 GtkSourceView *view = NULL;
618 GtkTextIter iter;
619
620 message_get_view_iter (window, message, &view, &iter);
621
622 g_return_if_fail (view);
623
624 remove_bookmark (GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))),
625 &iter);
626 }
627
628 static void
message_goto_next_cb(PlumaMessageBus * bus,PlumaMessage * message,PlumaWindow * window)629 message_goto_next_cb (PlumaMessageBus *bus,
630 PlumaMessage *message,
631 PlumaWindow *window)
632 {
633 GtkSourceView *view = NULL;
634 GtkTextIter iter;
635
636 message_get_view_iter (window, message, &view, &iter);
637
638 g_return_if_fail (view);
639
640 goto_bookmark (window,
641 view,
642 &iter,
643 gtk_source_buffer_forward_iter_to_source_mark,
644 gtk_text_buffer_get_start_iter);
645 }
646
647 static void
message_goto_previous_cb(PlumaMessageBus * bus,PlumaMessage * message,PlumaWindow * window)648 message_goto_previous_cb (PlumaMessageBus *bus,
649 PlumaMessage *message,
650 PlumaWindow *window)
651 {
652 GtkSourceView *view = NULL;
653 GtkTextIter iter;
654
655 message_get_view_iter (window, message, &view, &iter);
656
657 g_return_if_fail (view);
658
659 goto_bookmark (window,
660 view,
661 &iter,
662 gtk_source_buffer_backward_iter_to_source_mark,
663 gtk_text_buffer_get_end_iter);
664 }
665
666 static void
install_messages(PlumaWindow * window)667 install_messages (PlumaWindow *window)
668 {
669 PlumaMessageBus *bus = pluma_window_get_message_bus (window);
670
671 pluma_message_bus_register (bus,
672 MESSAGE_OBJECT_PATH,
673 "toggle",
674 2,
675 "view", GTK_SOURCE_TYPE_VIEW,
676 "iter", GTK_TYPE_TEXT_ITER,
677 NULL);
678
679 pluma_message_bus_register (bus,
680 MESSAGE_OBJECT_PATH,
681 "add",
682 2,
683 "view", GTK_SOURCE_TYPE_VIEW,
684 "iter", GTK_TYPE_TEXT_ITER,
685 NULL);
686
687 pluma_message_bus_register (bus,
688 MESSAGE_OBJECT_PATH,
689 "remove",
690 2,
691 "view", GTK_SOURCE_TYPE_VIEW,
692 "iter", GTK_TYPE_TEXT_ITER,
693 NULL);
694
695 pluma_message_bus_register (bus,
696 MESSAGE_OBJECT_PATH,
697 "goto_next",
698 2,
699 "view", GTK_SOURCE_TYPE_VIEW,
700 "iter", GTK_TYPE_TEXT_ITER,
701 NULL);
702
703 pluma_message_bus_register (bus,
704 MESSAGE_OBJECT_PATH,
705 "goto_previous",
706 2,
707 "view", GTK_SOURCE_TYPE_VIEW,
708 "iter", GTK_TYPE_TEXT_ITER,
709 NULL);
710
711 BUS_CONNECT (bus, toggle, window);
712 BUS_CONNECT (bus, add, window);
713 BUS_CONNECT (bus, remove, window);
714 BUS_CONNECT (bus, goto_next, window);
715 BUS_CONNECT (bus, goto_previous, window);
716 }
717
718 static void
uninstall_messages(PlumaWindow * window)719 uninstall_messages (PlumaWindow *window)
720 {
721 PlumaMessageBus *bus = pluma_window_get_message_bus (window);
722 pluma_message_bus_unregister_all (bus, MESSAGE_OBJECT_PATH);
723 }
724
725 static void
pluma_bookmarks_plugin_activate(PlumaWindowActivatable * activatable)726 pluma_bookmarks_plugin_activate (PlumaWindowActivatable *activatable)
727 {
728 PlumaBookmarksPluginPrivate *priv;
729 GList *views;
730 GList *item;
731
732 pluma_debug (DEBUG_PLUGINS);
733
734 priv = PLUMA_BOOKMARKS_PLUGIN (activatable)->priv;
735
736 views = pluma_window_get_views (priv->window);
737 for (item = views; item != NULL; item = item->next)
738 {
739 enable_bookmarks (PLUMA_VIEW (item->data),
740 PLUMA_BOOKMARKS_PLUGIN (activatable));
741 load_bookmark_metadata (PLUMA_VIEW (item->data));
742 }
743
744 g_list_free (views);
745
746 g_signal_connect (priv->window, "tab-added",
747 G_CALLBACK (on_tab_added), activatable);
748
749 g_signal_connect (priv->window, "tab-removed",
750 G_CALLBACK (on_tab_removed), activatable);
751
752 install_menu (PLUMA_BOOKMARKS_PLUGIN (activatable));
753 install_messages (priv->window);
754 }
755
756 static void
pluma_bookmarks_plugin_update_state(PlumaWindowActivatable * activatable)757 pluma_bookmarks_plugin_update_state (PlumaWindowActivatable *activatable)
758 {
759 PlumaBookmarksPluginPrivate *priv;
760
761 priv = PLUMA_BOOKMARKS_PLUGIN (activatable)->priv;
762 gtk_action_group_set_sensitive (priv->action_group,
763 pluma_window_get_active_view (priv->window) != NULL);
764 }
765
766 static void
save_bookmark_metadata(PlumaView * view)767 save_bookmark_metadata (PlumaView *view)
768 {
769 GtkTextIter iter;
770 GtkTextBuffer *buf;
771 GString *string;
772 gchar *val = NULL;
773 gboolean first = TRUE;
774
775 buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
776 gtk_text_buffer_get_start_iter (buf, &iter);
777 string = g_string_new (NULL);
778
779 while (gtk_source_buffer_forward_iter_to_source_mark (GTK_SOURCE_BUFFER (buf),
780 &iter,
781 BOOKMARK_CATEGORY))
782 {
783 gint line;
784
785 line = gtk_text_iter_get_line (&iter);
786
787 if (!first)
788 {
789 g_string_append_printf (string, ",%d", line);
790 }
791 else
792 {
793 g_string_append_printf (string, "%d", line);
794 first = FALSE;
795 }
796 }
797
798 if (string->len == 0)
799 {
800 val = g_string_free (string, TRUE);
801 val = NULL;
802 }
803 else
804 {
805 val = g_string_free (string, FALSE);
806 }
807
808 pluma_document_set_metadata (PLUMA_DOCUMENT (buf), METADATA_ATTR,
809 val, NULL);
810
811 g_free (val);
812 }
813
814 static void
pluma_bookmarks_plugin_deactivate(PlumaWindowActivatable * activatable)815 pluma_bookmarks_plugin_deactivate (PlumaWindowActivatable *activatable)
816 {
817 PlumaBookmarksPluginPrivate *priv;
818 GList *views;
819 GList *item;
820
821 pluma_debug (DEBUG_PLUGINS);
822
823 priv = PLUMA_BOOKMARKS_PLUGIN (activatable)->priv;
824
825 uninstall_menu (PLUMA_BOOKMARKS_PLUGIN (activatable));
826 uninstall_messages (priv->window);
827
828 views = pluma_window_get_views (priv->window);
829
830 for (item = views; item != NULL; item = item->next)
831 disable_bookmarks (PLUMA_VIEW (item->data));
832
833 g_list_free (views);
834
835 g_signal_handlers_disconnect_by_func (priv->window, on_tab_added, activatable);
836 g_signal_handlers_disconnect_by_func (priv->window, on_tab_removed, activatable);
837 }
838
839 static void
pluma_bookmarks_plugin_class_init(PlumaBookmarksPluginClass * klass)840 pluma_bookmarks_plugin_class_init (PlumaBookmarksPluginClass *klass)
841 {
842 GObjectClass *object_class = G_OBJECT_CLASS (klass);
843
844 object_class->dispose = pluma_bookmarks_plugin_dispose;
845 object_class->set_property = pluma_bookmarks_plugin_set_property;
846 object_class->get_property = pluma_bookmarks_plugin_get_property;
847
848 g_object_class_override_property (object_class, PROP_WINDOW, "window");
849 }
850
851 static void
pluma_bookmarks_plugin_class_finalize(PlumaBookmarksPluginClass * klass)852 pluma_bookmarks_plugin_class_finalize (PlumaBookmarksPluginClass *klass)
853 {
854 }
855
856 static void
on_style_scheme_notify(GObject * object,GParamSpec * pspec,PlumaView * view)857 on_style_scheme_notify (GObject *object,
858 GParamSpec *pspec,
859 PlumaView *view)
860 {
861 GtkSourceMarkAttributes *attrs;
862
863 attrs = gtk_source_view_get_mark_attributes (GTK_SOURCE_VIEW (view),
864 BOOKMARK_CATEGORY,
865 NULL);
866
867 update_background_color (attrs, GTK_SOURCE_BUFFER (object));
868 }
869
870 static void
on_delete_range(GtkTextBuffer * buffer,GtkTextIter * start,GtkTextIter * end,gpointer user_data)871 on_delete_range (GtkTextBuffer *buffer,
872 GtkTextIter *start,
873 GtkTextIter *end,
874 gpointer user_data)
875 {
876 GtkTextIter start_iter;
877 GtkTextIter end_iter;
878 gboolean keep_bookmark;
879
880 /* Nothing to do for us here. The bookmark, if any, will stay at the
881 beginning of the line due to its left gravity. */
882 if (gtk_text_iter_get_line (start) == gtk_text_iter_get_line (end))
883 return;
884
885 start_iter = *start;
886 gtk_text_iter_set_line_offset (&start_iter, 0);
887
888 end_iter = *end;
889 gtk_text_iter_set_line_offset (&end_iter, 0);
890
891 keep_bookmark = ((gtk_source_buffer_get_source_marks_at_iter (GTK_SOURCE_BUFFER (buffer),
892 &start_iter,
893 BOOKMARK_CATEGORY) != NULL) ||
894 (gtk_source_buffer_get_source_marks_at_iter (GTK_SOURCE_BUFFER (buffer),
895 &end_iter,
896 BOOKMARK_CATEGORY) != NULL));
897
898 /* Remove all bookmarks in the range. */
899 gtk_source_buffer_remove_source_marks (GTK_SOURCE_BUFFER (buffer),
900 &start_iter,
901 &end_iter,
902 BOOKMARK_CATEGORY);
903
904 if (keep_bookmark)
905 gtk_source_buffer_create_source_mark (GTK_SOURCE_BUFFER (buffer),
906 NULL,
907 BOOKMARK_CATEGORY,
908 &start_iter);
909 }
910
911 static void
on_begin_user_action(GtkTextBuffer * buffer,InsertData * data)912 on_begin_user_action (GtkTextBuffer *buffer,
913 InsertData *data)
914 {
915 ++data->user_action;
916 }
917
918 static void
on_end_user_action(GtkTextBuffer * buffer,InsertData * data)919 on_end_user_action (GtkTextBuffer *buffer,
920 InsertData *data)
921 {
922 GSList *item;
923
924 if (--data->user_action > 0)
925 return;
926
927 /* Remove trackers */
928 for (item = data->trackers; item; item = g_slist_next (item))
929 {
930 InsertTracker *tracker = item->data;
931 GtkTextIter curloc;
932 GtkTextIter newloc;
933
934 /* Move the category to the line where the mark now is */
935 gtk_text_buffer_get_iter_at_mark (buffer,
936 &curloc,
937 GTK_TEXT_MARK (tracker->bookmark));
938
939 gtk_text_buffer_get_iter_at_mark (buffer,
940 &newloc,
941 tracker->mark);
942
943 if (gtk_text_iter_get_line (&curloc) != gtk_text_iter_get_line (&newloc))
944 {
945 gtk_text_iter_set_line_offset (&newloc, 0);
946 gtk_text_buffer_move_mark (buffer,
947 GTK_TEXT_MARK (tracker->bookmark),
948 &newloc);
949 }
950
951 gtk_text_buffer_delete_mark (buffer, tracker->mark);
952 g_slice_free (InsertTracker, tracker);
953 }
954
955 g_slist_free (data->trackers);
956 data->trackers = NULL;
957 }
958
959 static void
add_tracker(GtkTextBuffer * buffer,GtkTextIter * iter,GtkSourceMark * bookmark,InsertData * data)960 add_tracker (GtkTextBuffer *buffer,
961 GtkTextIter *iter,
962 GtkSourceMark *bookmark,
963 InsertData *data)
964 {
965 GSList *item;
966 InsertTracker *tracker;
967
968 for (item = data->trackers; item; item = g_slist_next (item))
969 {
970 tracker = item->data;
971
972 if (tracker->bookmark == bookmark)
973 return;
974 }
975
976 tracker = g_slice_new (InsertTracker);
977 tracker->bookmark = bookmark;
978 tracker->mark = gtk_text_buffer_create_mark (buffer,
979 NULL,
980 iter,
981 FALSE);
982
983 data->trackers = g_slist_prepend (data->trackers, tracker);
984 }
985
986 static void
on_insert_text_before(GtkTextBuffer * buffer,GtkTextIter * location,gchar * text,gint len,InsertData * data)987 on_insert_text_before (GtkTextBuffer *buffer,
988 GtkTextIter *location,
989 gchar *text,
990 gint len,
991 InsertData *data)
992 {
993 if (gtk_text_iter_starts_line (location))
994 {
995 GSList *marks;
996 marks = gtk_source_buffer_get_source_marks_at_iter (GTK_SOURCE_BUFFER (buffer),
997 location,
998 BOOKMARK_CATEGORY);
999
1000 if (marks != NULL)
1001 {
1002 add_tracker (buffer, location, marks->data, data);
1003 g_slist_free (marks);
1004 }
1005 }
1006 }
1007
1008 static GtkSourceMark *
get_bookmark_and_iter(GtkSourceBuffer * buffer,GtkTextIter * iter,GtkTextIter * start)1009 get_bookmark_and_iter (GtkSourceBuffer *buffer,
1010 GtkTextIter *iter,
1011 GtkTextIter *start)
1012 {
1013 GSList *marks;
1014 GtkSourceMark *ret = NULL;
1015
1016 if (!iter)
1017 gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
1018 start,
1019 gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer)));
1020 else
1021 *start = *iter;
1022
1023 gtk_text_iter_set_line_offset (start, 0);
1024
1025 marks = gtk_source_buffer_get_source_marks_at_iter (buffer, start, BOOKMARK_CATEGORY);
1026 if (marks != NULL)
1027 ret = GTK_SOURCE_MARK (marks->data);
1028
1029 g_slist_free (marks);
1030 return ret;
1031 }
1032
1033 static void
remove_bookmark(GtkSourceBuffer * buffer,GtkTextIter * iter)1034 remove_bookmark (GtkSourceBuffer *buffer,
1035 GtkTextIter *iter)
1036 {
1037 GtkTextIter start;
1038 GtkSourceMark *bookmark;
1039
1040 if ((bookmark = get_bookmark_and_iter (buffer, iter, &start)) != NULL)
1041 gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (buffer),
1042 GTK_TEXT_MARK (bookmark));
1043 }
1044
1045 static void
add_bookmark(GtkSourceBuffer * buffer,GtkTextIter * iter)1046 add_bookmark (GtkSourceBuffer *buffer,
1047 GtkTextIter *iter)
1048 {
1049 GtkTextIter start;
1050 GtkSourceMark *bookmark;
1051
1052 if ((bookmark = get_bookmark_and_iter (buffer, iter, &start)) == NULL)
1053 gtk_source_buffer_create_source_mark (GTK_SOURCE_BUFFER (buffer),
1054 NULL,
1055 BOOKMARK_CATEGORY,
1056 &start);
1057 }
1058
1059 static void
toggle_bookmark(GtkSourceBuffer * buffer,GtkTextIter * iter)1060 toggle_bookmark (GtkSourceBuffer *buffer,
1061 GtkTextIter *iter)
1062 {
1063 GtkTextIter start;
1064 GtkSourceMark *bookmark = NULL;
1065
1066 g_return_if_fail (buffer != NULL);
1067
1068 if ((bookmark = get_bookmark_and_iter (buffer, iter, &start)) != NULL)
1069 remove_bookmark (buffer, &start);
1070 else
1071 add_bookmark (buffer, &start);
1072 }
1073
1074 static void
on_toggle_bookmark_activate(GtkAction * action,PlumaBookmarksPlugin * plugin)1075 on_toggle_bookmark_activate (GtkAction *action,
1076 PlumaBookmarksPlugin *plugin)
1077 {
1078 toggle_bookmark (GTK_SOURCE_BUFFER (pluma_window_get_active_document (plugin->priv->window)),
1079 NULL);
1080 }
1081
1082 static void
on_next_bookmark_activate(GtkAction * action,PlumaBookmarksPlugin * plugin)1083 on_next_bookmark_activate (GtkAction *action,
1084 PlumaBookmarksPlugin *plugin)
1085 {
1086 goto_bookmark (plugin->priv->window,
1087 NULL,
1088 NULL,
1089 gtk_source_buffer_forward_iter_to_source_mark,
1090 gtk_text_buffer_get_start_iter);
1091 }
1092
1093 static void
on_previous_bookmark_activate(GtkAction * action,PlumaBookmarksPlugin * plugin)1094 on_previous_bookmark_activate (GtkAction *action,
1095 PlumaBookmarksPlugin *plugin)
1096 {
1097 goto_bookmark (plugin->priv->window,
1098 NULL,
1099 NULL,
1100 gtk_source_buffer_backward_iter_to_source_mark,
1101 gtk_text_buffer_get_end_iter);
1102 }
1103
1104 static void
on_document_loaded(PlumaDocument * doc,const GError * error,PlumaView * view)1105 on_document_loaded (PlumaDocument *doc,
1106 const GError *error,
1107 PlumaView *view)
1108 {
1109 if (error == NULL)
1110 {
1111 /* Reverting can leave one bookmark at the start, remove it. */
1112 remove_all_bookmarks (GTK_SOURCE_BUFFER (doc));
1113
1114 load_bookmark_metadata (view);
1115 }
1116 }
1117
1118 static void
on_document_saved(PlumaDocument * doc,const GError * error,PlumaView * view)1119 on_document_saved (PlumaDocument *doc,
1120 const GError *error,
1121 PlumaView *view)
1122 {
1123 if (error == NULL)
1124 save_bookmark_metadata (view);
1125 }
1126
1127 static void
on_tab_added(PlumaWindow * window,PlumaTab * tab,PlumaBookmarksPlugin * plugin)1128 on_tab_added (PlumaWindow *window,
1129 PlumaTab *tab,
1130 PlumaBookmarksPlugin *plugin)
1131 {
1132 PlumaDocument *doc;
1133 PlumaView *view;
1134
1135 doc = pluma_tab_get_document (tab);
1136 view = pluma_tab_get_view (tab);
1137
1138 g_signal_connect (doc, "loaded",
1139 G_CALLBACK (on_document_loaded),
1140 view);
1141 g_signal_connect (doc, "saved",
1142 G_CALLBACK (on_document_saved),
1143 view);
1144
1145 enable_bookmarks (view, plugin);
1146 }
1147
1148 static void
on_tab_removed(PlumaWindow * window,PlumaTab * tab,PlumaBookmarksPlugin * plugin)1149 on_tab_removed (PlumaWindow *window,
1150 PlumaTab *tab,
1151 PlumaBookmarksPlugin *plugin)
1152 {
1153 PlumaDocument *doc;
1154 PlumaView *view;
1155
1156 doc = pluma_tab_get_document (tab);
1157 view = pluma_tab_get_view (tab);
1158
1159 g_signal_handlers_disconnect_by_func (doc, on_document_loaded, view);
1160 g_signal_handlers_disconnect_by_func (doc, on_document_saved, view);
1161
1162 disable_bookmarks (view);
1163 }
1164
1165 static void
pluma_window_activatable_iface_init(PlumaWindowActivatableInterface * iface)1166 pluma_window_activatable_iface_init (PlumaWindowActivatableInterface *iface)
1167 {
1168 iface->activate = pluma_bookmarks_plugin_activate;
1169 iface->deactivate = pluma_bookmarks_plugin_deactivate;
1170 iface->update_state = pluma_bookmarks_plugin_update_state;
1171 }
1172
1173 G_MODULE_EXPORT void
peas_register_types(PeasObjectModule * module)1174 peas_register_types (PeasObjectModule *module)
1175 {
1176 pluma_bookmarks_plugin_register_type (G_TYPE_MODULE (module));
1177
1178 peas_object_module_register_extension_type (module,
1179 PLUMA_TYPE_WINDOW_ACTIVATABLE,
1180 PLUMA_TYPE_BOOKMARKS_PLUGIN);
1181 }
1182
1183 /* ex:set ts=8 noet: */
1184