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