1 /* ide-hover.c
2 *
3 * Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21 #define G_LOG_DOMAIN "ide-hover"
22
23 #include "config.h"
24
25 #include <dazzle.h>
26 #include <libide-code.h>
27 #include <libide-plugins.h>
28 #include <libpeas/peas.h>
29 #include <string.h>
30
31 #include "ide-hover-popover-private.h"
32 #include "ide-hover-private.h"
33 #include "ide-hover-provider.h"
34
35 #define GRACE_X 20
36 #define GRACE_Y 20
37 #define MOTION_SETTLE_TIMEOUT_MSEC 250
38
39 typedef enum
40 {
41 IDE_HOVER_STATE_INITIAL,
42 IDE_HOVER_STATE_DISPLAY,
43 IDE_HOVER_STATE_IN_POPOVER,
44 } IdeHoverState;
45
46 struct _IdeHover
47 {
48 GObject parent_instance;
49
50 /*
51 * Our signal group to handle the number of events on the textview so that
52 * we can update the hover provider and associated content.
53 */
54 DzlSignalGroup *signals;
55
56 /*
57 * Our plugins that can populate our IdeHoverContext with content to be
58 * displayed.
59 */
60 IdeExtensionSetAdapter *providers;
61
62 /*
63 * Our popover that will display content once the cursor has settled
64 * somewhere of importance.
65 */
66 IdeHoverPopover *popover;
67
68 /*
69 * Our last motion position, used to calculate where we should find
70 * our iter to display the popover.
71 */
72 gdouble motion_x;
73 gdouble motion_y;
74
75 /*
76 * Our state so that we can handle events in a sane manner without
77 * stomping all over things.
78 */
79 IdeHoverState state;
80
81 /*
82 * Our source which is continually delayed until the motion event has
83 * settled somewhere we can potentially display a popover.
84 */
85 guint delay_display_source;
86
87 /*
88 * We need to introduce some delay when we get a leave-notify-event
89 * because we might be entering the popover next.
90 */
91 guint dismiss_source;
92 };
93
94 static gboolean ide_hover_dismiss_cb (gpointer data);
95
G_DEFINE_FINAL_TYPE(IdeHover,ide_hover,G_TYPE_OBJECT)96 G_DEFINE_FINAL_TYPE (IdeHover, ide_hover, G_TYPE_OBJECT)
97
98 static void
99 ide_hover_queue_dismiss (IdeHover *self)
100 {
101 g_assert (IDE_IS_HOVER (self));
102
103 if (self->dismiss_source)
104 g_source_remove (self->dismiss_source);
105
106 /*
107 * Give ourselves just enough time to get the crossing event
108 * into the popover before we try to dismiss the popover.
109 */
110 self->dismiss_source =
111 gdk_threads_add_timeout_full (G_PRIORITY_HIGH,
112 10,
113 ide_hover_dismiss_cb,
114 self, NULL);
115 }
116
117 static void
ide_hover_popover_closed_cb(IdeHover * self,IdeHoverPopover * popover)118 ide_hover_popover_closed_cb (IdeHover *self,
119 IdeHoverPopover *popover)
120 {
121 g_assert (IDE_IS_HOVER (self));
122 g_assert (IDE_IS_HOVER_POPOVER (popover));
123
124 self->state = IDE_HOVER_STATE_INITIAL;
125 gtk_widget_destroy (GTK_WIDGET (popover));
126 dzl_clear_source (&self->dismiss_source);
127 dzl_clear_source (&self->delay_display_source);
128
129 g_assert (self->popover == NULL);
130 g_assert (self->state == IDE_HOVER_STATE_INITIAL);
131 g_assert (self->dismiss_source == 0);
132 g_assert (self->delay_display_source == 0);
133 }
134
135 static gboolean
ide_hover_popover_enter_notify_event_cb(IdeHover * self,const GdkEventCrossing * event,IdeHoverPopover * popover)136 ide_hover_popover_enter_notify_event_cb (IdeHover *self,
137 const GdkEventCrossing *event,
138 IdeHoverPopover *popover)
139 {
140 g_assert (IDE_IS_HOVER (self));
141 g_assert (event != NULL);
142 g_assert (IDE_IS_HOVER_POPOVER (popover));
143
144 /* Possible with DnD dragging? */
145 if (self->state != IDE_HOVER_STATE_DISPLAY)
146 return GDK_EVENT_PROPAGATE;
147
148 self->state = IDE_HOVER_STATE_IN_POPOVER;
149 dzl_clear_source (&self->dismiss_source);
150
151 return GDK_EVENT_PROPAGATE;
152 }
153
154 static gboolean
ide_hover_popover_leave_notify_event_cb(IdeHover * self,const GdkEventCrossing * event,IdeHoverPopover * popover)155 ide_hover_popover_leave_notify_event_cb (IdeHover *self,
156 const GdkEventCrossing *event,
157 IdeHoverPopover *popover)
158 {
159 GtkWidget *child;
160
161 g_assert (IDE_IS_HOVER (self));
162 g_assert (event != NULL);
163 g_assert (IDE_IS_HOVER_POPOVER (popover));
164
165 if (self->state == IDE_HOVER_STATE_IN_POPOVER)
166 self->state = IDE_HOVER_STATE_DISPLAY;
167
168 /* If the window that we are crossing into is not a descendant of our
169 * popover window, then we want to dismiss. This is rather annoying to
170 * track and suffers the same issue as with GtkNotebook tabs containing
171 * buttons (where it's possible to break the prelight state tracking).
172 *
173 * In future Gtk releases, we may be able to use GtkEventControllerMotion.
174 */
175
176 if ((child = gtk_bin_get_child (GTK_BIN (popover))))
177 {
178 GdkRectangle point = { event->x, event->y, 1, 1 };
179 GtkAllocation alloc;
180
181 gtk_widget_get_allocation (child, &alloc);
182
183 if (!dzl_cairo_rectangle_contains_rectangle (&alloc, &point))
184 ide_hover_queue_dismiss (self);
185 }
186
187 return GDK_EVENT_PROPAGATE;
188 }
189
190 static void
ide_hover_providers_foreach_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)191 ide_hover_providers_foreach_cb (IdeExtensionSetAdapter *set,
192 PeasPluginInfo *plugin_info,
193 PeasExtension *exten,
194 gpointer user_data)
195 {
196 IdeHoverPopover *popover = user_data;
197 IdeHoverProvider *provider = (IdeHoverProvider *)exten;
198
199 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
200 g_assert (plugin_info != NULL);
201 g_assert (IDE_IS_HOVER_PROVIDER (provider));
202 g_assert (IDE_IS_HOVER_POPOVER (popover));
203
204 _ide_hover_popover_add_provider (popover, provider);
205 }
206
207 static void
ide_hover_popover_destroy_cb(IdeHover * self,IdeHoverPopover * popover)208 ide_hover_popover_destroy_cb (IdeHover *self,
209 IdeHoverPopover *popover)
210 {
211 g_assert (IDE_IS_HOVER (self));
212 g_assert (IDE_IS_HOVER_POPOVER (popover));
213
214 self->popover = NULL;
215 self->state = IDE_HOVER_STATE_INITIAL;
216 }
217
218 static gboolean
ide_hover_get_bounds(IdeHover * self,GtkTextIter * begin,GtkTextIter * end,GtkTextIter * hover)219 ide_hover_get_bounds (IdeHover *self,
220 GtkTextIter *begin,
221 GtkTextIter *end,
222 GtkTextIter *hover)
223 {
224 GtkTextView *view;
225 GtkTextIter iter;
226 gint x, y;
227
228 g_assert (IDE_IS_HOVER (self));
229 g_assert (begin != NULL);
230 g_assert (end != NULL);
231 g_assert (hover != NULL);
232
233 memset (begin, 0, sizeof *begin);
234 memset (end, 0, sizeof *end);
235 memset (hover, 0, sizeof *hover);
236
237 if (!(view = dzl_signal_group_get_target (self->signals)))
238 return FALSE;
239
240 g_assert (GTK_IS_TEXT_VIEW (view));
241
242 gtk_text_view_window_to_buffer_coords (view,
243 GTK_TEXT_WINDOW_WIDGET,
244 self->motion_x,
245 self->motion_y,
246 &x, &y);
247
248 if (!gtk_text_view_get_iter_at_location (view, &iter, x, y))
249 return FALSE;
250
251 *hover = iter;
252
253 if (!_ide_source_iter_inside_word (&iter))
254 {
255 *begin = iter;
256 gtk_text_iter_set_line_offset (begin, 0);
257
258 *end = *begin;
259 gtk_text_iter_forward_to_line_end (end);
260
261 return TRUE;
262 }
263
264 if (!_ide_source_iter_starts_full_word (&iter))
265 _ide_source_iter_backward_full_word_start (&iter);
266
267 *begin = iter;
268 *end = iter;
269
270 _ide_source_iter_forward_full_word_end (end);
271
272 return TRUE;
273 }
274
275 static gboolean
ide_hover_motion_timeout_cb(gpointer data)276 ide_hover_motion_timeout_cb (gpointer data)
277 {
278 IdeHover *self = data;
279 IdeSourceView *view;
280 GdkRectangle rect;
281 GdkRectangle begin_rect;
282 GdkRectangle end_rect;
283 GdkRectangle hover_rect;
284 GtkTextIter begin;
285 GtkTextIter end;
286 GtkTextIter hover;
287
288 g_assert (IDE_IS_HOVER (self));
289
290 self->delay_display_source = 0;
291
292 if (!(view = dzl_signal_group_get_target (self->signals)))
293 return G_SOURCE_REMOVE;
294
295 /* Ignore signal if we're already processing */
296 if (self->state != IDE_HOVER_STATE_INITIAL)
297 return G_SOURCE_REMOVE;
298
299 /* Make sure we're over text */
300 if (!ide_hover_get_bounds (self, &begin, &end, &hover))
301 return G_SOURCE_REMOVE;
302
303 if (self->popover == NULL)
304 {
305 self->popover = g_object_new (IDE_TYPE_HOVER_POPOVER,
306 "modal", FALSE,
307 "position", GTK_POS_TOP,
308 "relative-to", view,
309 NULL);
310
311 g_signal_connect_object (self->popover,
312 "destroy",
313 G_CALLBACK (ide_hover_popover_destroy_cb),
314 self,
315 G_CONNECT_SWAPPED);
316
317 g_signal_connect_object (self->popover,
318 "closed",
319 G_CALLBACK (ide_hover_popover_closed_cb),
320 self,
321 G_CONNECT_SWAPPED);
322
323 g_signal_connect_object (self->popover,
324 "enter-notify-event",
325 G_CALLBACK (ide_hover_popover_enter_notify_event_cb),
326 self,
327 G_CONNECT_SWAPPED);
328
329 g_signal_connect_object (self->popover,
330 "leave-notify-event",
331 G_CALLBACK (ide_hover_popover_leave_notify_event_cb),
332 self,
333 G_CONNECT_SWAPPED);
334
335 if (self->providers != NULL)
336 ide_extension_set_adapter_foreach (self->providers,
337 ide_hover_providers_foreach_cb,
338 self->popover);
339 }
340
341 self->state = IDE_HOVER_STATE_DISPLAY;
342 gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), &begin, &begin_rect);
343 gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), &end, &end_rect);
344 gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), &hover, &hover_rect);
345 gdk_rectangle_union (&begin_rect, &end_rect, &rect);
346
347 gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (view),
348 GTK_TEXT_WINDOW_WIDGET,
349 rect.x, rect.y, &rect.x, &rect.y);
350
351 _ide_hover_popover_set_hovered_at (self->popover, &hover_rect);
352
353 if (gtk_text_iter_equal (&begin, &end) &&
354 gtk_text_iter_starts_line (&begin))
355 {
356 rect.width = 1;
357 gtk_popover_set_pointing_to (GTK_POPOVER (self->popover), &rect);
358 gtk_popover_set_position (GTK_POPOVER (self->popover), GTK_POS_RIGHT);
359 }
360 else
361 {
362 gtk_popover_set_pointing_to (GTK_POPOVER (self->popover), &rect);
363 gtk_popover_set_position (GTK_POPOVER (self->popover), GTK_POS_TOP);
364 }
365
366 _ide_hover_popover_show (self->popover);
367
368 return G_SOURCE_REMOVE;
369 }
370
371 static void
ide_hover_delay_display(IdeHover * self)372 ide_hover_delay_display (IdeHover *self)
373 {
374 g_assert (IDE_IS_HOVER (self));
375
376 if (self->delay_display_source)
377 g_source_remove (self->delay_display_source);
378
379 self->delay_display_source =
380 gdk_threads_add_timeout_full (G_PRIORITY_LOW,
381 MOTION_SETTLE_TIMEOUT_MSEC,
382 ide_hover_motion_timeout_cb,
383 self, NULL);
384 }
385
386 void
_ide_hover_display(IdeHover * self,const GtkTextIter * iter)387 _ide_hover_display (IdeHover *self,
388 const GtkTextIter *iter)
389 {
390 IdeSourceView *view;
391 GdkRectangle rect;
392
393 g_assert (IDE_IS_HOVER (self));
394 g_assert (iter != NULL);
395
396 if (self->state != IDE_HOVER_STATE_INITIAL)
397 return;
398
399 if (!(view = dzl_signal_group_get_target (self->signals)))
400 return;
401
402 g_assert (GTK_IS_TEXT_VIEW (view));
403
404 dzl_clear_source (&self->delay_display_source);
405
406 gtk_text_view_get_iter_location (GTK_TEXT_VIEW (view), iter, &rect);
407 gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (view),
408 GTK_TEXT_WINDOW_TEXT,
409 rect.x, rect.y,
410 &rect.x, &rect.y);
411
412 self->motion_x = rect.x;
413 self->motion_y = rect.y;
414
415 ide_hover_motion_timeout_cb (self);
416 }
417
418 static inline gboolean
should_ignore_event(IdeSourceView * view,GdkWindow * event_window)419 should_ignore_event (IdeSourceView *view,
420 GdkWindow *event_window)
421 {
422 GdkWindow *text_window;
423 GdkWindow *gutter_window;
424
425 g_assert (IDE_IS_SOURCE_VIEW (view));
426
427 text_window = gtk_text_view_get_window (GTK_TEXT_VIEW (view), GTK_TEXT_WINDOW_TEXT);
428 gutter_window = gtk_text_view_get_window (GTK_TEXT_VIEW (view), GTK_TEXT_WINDOW_LEFT);
429
430 return (event_window != text_window && event_window != gutter_window);
431 }
432
433 static gboolean
ide_hover_key_press_event_cb(IdeHover * self,const GdkEventKey * event,IdeSourceView * view)434 ide_hover_key_press_event_cb (IdeHover *self,
435 const GdkEventKey *event,
436 IdeSourceView *view)
437 {
438 g_assert (IDE_IS_HOVER (self));
439 g_assert (event != NULL);
440 g_assert (IDE_IS_SOURCE_VIEW (view));
441
442 if (self->popover != NULL)
443 gtk_widget_destroy (GTK_WIDGET (self->popover));
444
445 dzl_clear_source (&self->delay_display_source);
446 dzl_clear_source (&self->dismiss_source);
447
448 g_assert (self->popover == NULL);
449 g_assert (self->state == IDE_HOVER_STATE_INITIAL);
450 g_assert (self->delay_display_source == 0);
451 g_assert (self->dismiss_source == 0);
452
453 return GDK_EVENT_PROPAGATE;
454 }
455
456 static gboolean
ide_hover_enter_notify_event_cb(IdeHover * self,const GdkEventCrossing * event,IdeSourceView * view)457 ide_hover_enter_notify_event_cb (IdeHover *self,
458 const GdkEventCrossing *event,
459 IdeSourceView *view)
460 {
461 g_assert (IDE_IS_HOVER (self));
462 g_assert (event != NULL);
463 g_assert (event->type == GDK_ENTER_NOTIFY);
464 g_assert (IDE_IS_SOURCE_VIEW (view));
465
466 if (should_ignore_event (view, event->window))
467 return GDK_EVENT_PROPAGATE;
468
469 dzl_clear_source (&self->dismiss_source);
470
471 return GDK_EVENT_PROPAGATE;
472 }
473
474 static gboolean
ide_hover_dismiss_cb(gpointer data)475 ide_hover_dismiss_cb (gpointer data)
476 {
477 IdeHover *self = data;
478
479 g_assert (IDE_IS_HOVER (self));
480
481 self->dismiss_source = 0;
482
483 switch (self->state)
484 {
485 case IDE_HOVER_STATE_DISPLAY:
486 g_assert (IDE_IS_HOVER_POPOVER (self->popover));
487
488 _ide_hover_popover_hide (self->popover);
489
490 g_assert (self->state == IDE_HOVER_STATE_INITIAL);
491 g_assert (self->popover == NULL);
492
493 break;
494
495 case IDE_HOVER_STATE_INITIAL:
496 case IDE_HOVER_STATE_IN_POPOVER:
497 default:
498 dzl_clear_source (&self->delay_display_source);
499 break;
500 }
501
502 return G_SOURCE_REMOVE;
503 }
504
505 static gboolean
ide_hover_leave_notify_event_cb(IdeHover * self,const GdkEventCrossing * event,IdeSourceView * view)506 ide_hover_leave_notify_event_cb (IdeHover *self,
507 const GdkEventCrossing *event,
508 IdeSourceView *view)
509 {
510 g_assert (IDE_IS_HOVER (self));
511 g_assert (event != NULL);
512 g_assert (event->type == GDK_LEAVE_NOTIFY);
513 g_assert (IDE_IS_SOURCE_VIEW (view));
514
515 if (should_ignore_event (view, event->window))
516 return GDK_EVENT_PROPAGATE;
517
518 ide_hover_queue_dismiss (self);
519
520 return GDK_EVENT_PROPAGATE;
521 }
522
523 static gboolean
ide_hover_scroll_event_cb(IdeHover * self,const GdkEventScroll * event,IdeSourceView * view)524 ide_hover_scroll_event_cb (IdeHover *self,
525 const GdkEventScroll *event,
526 IdeSourceView *view)
527 {
528 g_assert (IDE_IS_HOVER (self));
529 g_assert (event != NULL);
530 g_assert (IDE_IS_SOURCE_VIEW (view));
531 g_assert (!self->popover || IDE_IS_HOVER_POPOVER (self->popover));
532
533 if (self->popover != NULL)
534 gtk_widget_destroy (GTK_WIDGET (self->popover));
535
536 return GDK_EVENT_PROPAGATE;
537 }
538
539 static gboolean
ide_hover_motion_notify_event_cb(IdeHover * self,const GdkEventMotion * event,IdeSourceView * view)540 ide_hover_motion_notify_event_cb (IdeHover *self,
541 const GdkEventMotion *event,
542 IdeSourceView *view)
543 {
544 GdkWindow *window;
545
546 g_assert (IDE_IS_HOVER (self));
547 g_assert (event != NULL);
548 g_assert (event->type == GDK_MOTION_NOTIFY);
549 g_assert (IDE_IS_SOURCE_VIEW (view));
550
551 window = gtk_text_view_get_window (GTK_TEXT_VIEW (view), GTK_TEXT_WINDOW_LEFT);
552
553 if (window != NULL)
554 {
555 gint left_width = gdk_window_get_width (window);
556
557 self->motion_x = event->x + left_width;
558 self->motion_y = event->y;
559 }
560
561 /*
562 * If we have a popover displayed, get it's allocation so that
563 * we can detect if our x/y coordinate is outside the threshold
564 * of the rectangle + grace area. If so, we'll dismiss the popover
565 * immediately.
566 */
567
568 if (self->popover != NULL)
569 {
570 GtkAllocation alloc;
571 GdkRectangle pointing_to;
572
573 gtk_widget_get_allocation (GTK_WIDGET (self->popover), &alloc);
574 gtk_widget_translate_coordinates (GTK_WIDGET (self->popover),
575 GTK_WIDGET (view),
576 alloc.x, alloc.y,
577 &alloc.x, &alloc.y);
578 gtk_popover_get_pointing_to (GTK_POPOVER (self->popover), &pointing_to);
579
580 alloc.x -= GRACE_X;
581 alloc.width += GRACE_X * 2;
582 alloc.y -= GRACE_Y;
583 alloc.height += GRACE_Y * 2;
584
585 gdk_rectangle_union (&alloc, &pointing_to, &alloc);
586
587 if (event->x < alloc.x ||
588 event->x > (alloc.x + alloc.width) ||
589 event->y < alloc.y ||
590 event->y > (alloc.y + alloc.height))
591 {
592 _ide_hover_popover_hide (self->popover);
593
594 g_assert (self->popover == NULL);
595 g_assert (self->state == IDE_HOVER_STATE_INITIAL);
596 }
597 }
598
599 dzl_clear_source (&self->dismiss_source);
600
601 ide_hover_delay_display (self);
602
603 return GDK_EVENT_PROPAGATE;
604 }
605
606 static void
ide_hover_destroy_cb(IdeHover * self,IdeSourceView * view)607 ide_hover_destroy_cb (IdeHover *self,
608 IdeSourceView *view)
609 {
610 g_assert (IDE_IS_HOVER (self));
611 g_assert (IDE_IS_SOURCE_VIEW (view));
612 g_assert (!self->popover || IDE_IS_HOVER_POPOVER (self->popover));
613
614 dzl_clear_source (&self->delay_display_source);
615
616 if (self->popover != NULL)
617 gtk_widget_destroy (GTK_WIDGET (self->popover));
618
619 g_assert (self->popover == NULL);
620 g_assert (self->delay_display_source == 0);
621 }
622
623 static void
ide_hover_dispose(GObject * object)624 ide_hover_dispose (GObject *object)
625 {
626 IdeHover *self = (IdeHover *)object;
627
628 ide_clear_and_destroy_object (&self->providers);
629
630 dzl_clear_source (&self->delay_display_source);
631 dzl_clear_source (&self->dismiss_source);
632 dzl_signal_group_set_target (self->signals, NULL);
633
634 if (self->popover != NULL)
635 gtk_widget_destroy (GTK_WIDGET (self->popover));
636
637 G_OBJECT_CLASS (ide_hover_parent_class)->dispose (object);
638
639 g_assert (self->popover == NULL);
640 g_assert (self->delay_display_source == 0);
641 g_assert (self->dismiss_source == 0);
642 }
643
644 static void
ide_hover_finalize(GObject * object)645 ide_hover_finalize (GObject *object)
646 {
647 IdeHover *self = (IdeHover *)object;
648
649 g_clear_object (&self->signals);
650
651 g_assert (self->signals == NULL);
652 g_assert (self->popover == NULL);
653 g_assert (self->providers == NULL);
654
655 g_assert (self->delay_display_source == 0);
656 g_assert (self->dismiss_source == 0);
657
658 G_OBJECT_CLASS (ide_hover_parent_class)->finalize (object);
659 }
660
661 static void
ide_hover_class_init(IdeHoverClass * klass)662 ide_hover_class_init (IdeHoverClass *klass)
663 {
664 GObjectClass *object_class = G_OBJECT_CLASS (klass);
665
666 object_class->dispose = ide_hover_dispose;
667 object_class->finalize = ide_hover_finalize;
668 }
669
670 static void
ide_hover_init(IdeHover * self)671 ide_hover_init (IdeHover *self)
672 {
673 self->signals = dzl_signal_group_new (IDE_TYPE_SOURCE_VIEW);
674
675 dzl_signal_group_connect_object (self->signals,
676 "key-press-event",
677 G_CALLBACK (ide_hover_key_press_event_cb),
678 self,
679 G_CONNECT_SWAPPED);
680
681 dzl_signal_group_connect_object (self->signals,
682 "enter-notify-event",
683 G_CALLBACK (ide_hover_enter_notify_event_cb),
684 self,
685 G_CONNECT_SWAPPED);
686
687 dzl_signal_group_connect_object (self->signals,
688 "leave-notify-event",
689 G_CALLBACK (ide_hover_leave_notify_event_cb),
690 self,
691 G_CONNECT_SWAPPED);
692
693 dzl_signal_group_connect_object (self->signals,
694 "motion-notify-event",
695 G_CALLBACK (ide_hover_motion_notify_event_cb),
696 self,
697 G_CONNECT_SWAPPED);
698
699 dzl_signal_group_connect_object (self->signals,
700 "scroll-event",
701 G_CALLBACK (ide_hover_scroll_event_cb),
702 self,
703 G_CONNECT_SWAPPED);
704
705 dzl_signal_group_connect_object (self->signals,
706 "destroy",
707 G_CALLBACK (ide_hover_destroy_cb),
708 self,
709 G_CONNECT_SWAPPED);
710 }
711
712 static void
ide_hover_extension_added_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)713 ide_hover_extension_added_cb (IdeExtensionSetAdapter *set,
714 PeasPluginInfo *plugin_info,
715 PeasExtension *exten,
716 gpointer user_data)
717 {
718 IdeHover *self = user_data;
719 IdeHoverProvider *provider = (IdeHoverProvider *)exten;
720 IdeSourceView *view;
721
722 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
723 g_assert (IDE_IS_HOVER (self));
724 g_assert (plugin_info != NULL);
725 g_assert (IDE_IS_HOVER_PROVIDER (provider));
726
727 view = dzl_signal_group_get_target (self->signals);
728 ide_hover_provider_load (provider, view);
729 }
730
731 static void
ide_hover_extension_removed_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)732 ide_hover_extension_removed_cb (IdeExtensionSetAdapter *set,
733 PeasPluginInfo *plugin_info,
734 PeasExtension *exten,
735 gpointer user_data)
736 {
737 IdeHover *self = user_data;
738 IdeHoverProvider *provider = (IdeHoverProvider *)exten;
739 IdeSourceView *view;
740
741 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
742 g_assert (IDE_IS_HOVER (self));
743 g_assert (plugin_info != NULL);
744 g_assert (IDE_IS_HOVER_PROVIDER (provider));
745
746 view = dzl_signal_group_get_target (self->signals);
747 ide_hover_provider_unload (provider, view);
748 }
749
750 void
_ide_hover_set_context(IdeHover * self,IdeContext * context)751 _ide_hover_set_context (IdeHover *self,
752 IdeContext *context)
753 {
754 g_return_if_fail (IDE_IS_HOVER (self));
755 g_return_if_fail (IDE_IS_CONTEXT (context));
756
757 if (self->providers != NULL)
758 return;
759
760 self->providers = ide_extension_set_adapter_new (IDE_OBJECT (context),
761 peas_engine_get_default (),
762 IDE_TYPE_HOVER_PROVIDER,
763 "Hover-Provider-Languages",
764 NULL);
765
766 g_signal_connect_object (self->providers,
767 "extension-added",
768 G_CALLBACK (ide_hover_extension_added_cb),
769 self, 0);
770
771 g_signal_connect_object (self->providers,
772 "extension-removed",
773 G_CALLBACK (ide_hover_extension_removed_cb),
774 self, 0);
775
776 ide_extension_set_adapter_foreach (self->providers,
777 ide_hover_extension_added_cb,
778 self);
779 }
780
781 void
_ide_hover_set_language(IdeHover * self,const gchar * language)782 _ide_hover_set_language (IdeHover *self,
783 const gchar *language)
784 {
785 g_return_if_fail (IDE_IS_HOVER (self));
786
787 if (self->providers != NULL)
788 ide_extension_set_adapter_set_value (self->providers, language);
789 }
790
791 IdeHover *
_ide_hover_new(IdeSourceView * view)792 _ide_hover_new (IdeSourceView *view)
793 {
794 IdeHover *self;
795
796 self = g_object_new (IDE_TYPE_HOVER, NULL);
797 dzl_signal_group_set_target (self->signals, view);
798
799 return self;
800 }
801