1/* gtktooltip.c
2 *
3 * Copyright (C) 2006-2007 Imendio AB
4 * Contact: Kristian Rietveld <kris@imendio.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "config.h"
21
22#include "gtktooltip.h"
23#include "gtktooltipprivate.h"
24
25#include <math.h>
26#include <string.h>
27
28#include "gtkintl.h"
29#include "gtkwindow.h"
30#include "gtkmain.h"
31#include "gtksettings.h"
32#include "gtksizerequest.h"
33#include "gtktooltipwindowprivate.h"
34#include "gtkwindowprivate.h"
35#include "gtkwidgetprivate.h"
36#include "gtknative.h"
37#include "gtkprivate.h"
38
39/**
40 * GtkTooltip:
41 *
42 * `GtkTooltip` is an object representing a widget tooltip.
43 *
44 * Basic tooltips can be realized simply by using
45 * [method@Gtk.Widget.set_tooltip_text] or
46 * [method@Gtk.Widget.set_tooltip_markup] without
47 * any explicit tooltip object.
48 *
49 * When you need a tooltip with a little more fancy contents,
50 * like adding an image, or you want the tooltip to have different
51 * contents per `GtkTreeView` row or cell, you will have to do a
52 * little more work:
53 *
54 * - Set the [property@Gtk.Widget:has-tooltip] property to %TRUE.
55 * This will make GTK monitor the widget for motion and related events
56 * which are needed to determine when and where to show a tooltip.
57 *
58 * - Connect to the [signal@Gtk.Widget::query-tooltip] signal.
59 * This signal will be emitted when a tooltip is supposed to be shown.
60 * One of the arguments passed to the signal handler is a `GtkTooltip`
61 * object. This is the object that we are about to display as a tooltip,
62 * and can be manipulated in your callback using functions like
63 * [method@Gtk.Tooltip.set_icon]. There are functions for setting
64 * the tooltip’s markup, setting an image from a named icon, or even
65 * putting in a custom widget.
66 *
67 * - Return %TRUE from your ::query-tooltip handler. This causes the tooltip
68 * to be show. If you return %FALSE, it will not be shown.
69 */
70
71
72#define HOVER_TIMEOUT 500
73#define BROWSE_TIMEOUT 60
74#define BROWSE_DISABLE_TIMEOUT 500
75
76#define GTK_TOOLTIP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TOOLTIP, GtkTooltipClass))
77#define GTK_IS_TOOLTIP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TOOLTIP))
78#define GTK_TOOLTIP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TOOLTIP, GtkTooltipClass))
79
80/* We keep a single GtkTooltip object per display. The tooltip object
81 * owns a GtkTooltipWindow widget, which is using a popup surface, similar
82 * to what a GtkPopover does. It gets reparented to the right native widget
83 * whenever a tooltip is to be shown. The tooltip object keeps a weak
84 * ref on the native in order to remove the tooltip window when the
85 * native goes away.
86 */
87typedef struct _GtkTooltipClass GtkTooltipClass;
88
89struct _GtkTooltip
90{
91 GObject parent_instance;
92
93 GtkWidget *window;
94
95 GtkWidget *tooltip_widget;
96
97 GtkWidget *native;
98
99 guint timeout_id;
100 guint browse_mode_timeout_id;
101
102 GdkRectangle tip_area;
103
104 guint browse_mode_enabled : 1;
105 guint tip_area_set : 1;
106 guint custom_was_reset : 1;
107};
108
109struct _GtkTooltipClass
110{
111 GObjectClass parent_class;
112};
113
114#define GTK_TOOLTIP_VISIBLE(tooltip) ((tooltip)->window && gtk_widget_get_visible (GTK_WIDGET((tooltip)->window)))
115
116static void gtk_tooltip_dispose (GObject *object);
117
118static void gtk_tooltip_window_hide (GtkWidget *widget,
119 gpointer user_data);
120static void gtk_tooltip_display_closed (GdkDisplay *display,
121 gboolean was_error,
122 GtkTooltip *tooltip);
123static void gtk_tooltip_set_surface (GtkTooltip *tooltip,
124 GdkSurface *surface);
125
126static void gtk_tooltip_handle_event_internal (GdkEventType event_type,
127 GdkSurface *surface,
128 GtkWidget *target_widget,
129 double dx,
130 double dy);
131
132static GQuark quark_current_tooltip;
133
134G_DEFINE_TYPE (GtkTooltip, gtk_tooltip, G_TYPE_OBJECT);
135
136static void
137gtk_tooltip_class_init (GtkTooltipClass *klass)
138{
139 GObjectClass *object_class;
140
141 quark_current_tooltip = g_quark_from_static_string (string: "gdk-display-current-tooltip");
142
143 object_class = G_OBJECT_CLASS (klass);
144
145 object_class->dispose = gtk_tooltip_dispose;
146}
147
148static void
149gtk_tooltip_init (GtkTooltip *tooltip)
150{
151 tooltip->timeout_id = 0;
152 tooltip->browse_mode_timeout_id = 0;
153
154 tooltip->browse_mode_enabled = FALSE;
155
156 tooltip->tooltip_widget = NULL;
157
158 tooltip->native = NULL;
159
160 tooltip->window = gtk_tooltip_window_new ();
161 g_object_ref_sink (tooltip->window);
162 g_signal_connect (tooltip->window, "hide",
163 G_CALLBACK (gtk_tooltip_window_hide),
164 tooltip);
165}
166
167static void
168gtk_tooltip_dispose (GObject *object)
169{
170 GtkTooltip *tooltip = GTK_TOOLTIP (object);
171
172 if (tooltip->timeout_id)
173 {
174 g_source_remove (tag: tooltip->timeout_id);
175 tooltip->timeout_id = 0;
176 }
177
178 if (tooltip->browse_mode_timeout_id)
179 {
180 g_source_remove (tag: tooltip->browse_mode_timeout_id);
181 tooltip->browse_mode_timeout_id = 0;
182 }
183
184 gtk_tooltip_set_custom (tooltip, NULL);
185 gtk_tooltip_set_surface (tooltip, NULL);
186
187 if (tooltip->window)
188 {
189 GdkDisplay *display;
190
191 display = gtk_widget_get_display (widget: tooltip->window);
192 g_signal_handlers_disconnect_by_func (display,
193 gtk_tooltip_display_closed,
194 tooltip);
195 gtk_tooltip_window_set_relative_to (window: GTK_TOOLTIP_WINDOW (ptr: tooltip->window), NULL);
196 g_clear_object (&tooltip->window);
197 }
198
199 G_OBJECT_CLASS (gtk_tooltip_parent_class)->dispose (object);
200}
201
202/* public API */
203
204/**
205 * gtk_tooltip_set_markup:
206 * @tooltip: a `GtkTooltip`
207 * @markup: (nullable): a string with Pango markup or %NLL
208 *
209 * Sets the text of the tooltip to be @markup.
210 *
211 * The string must be marked up with Pango markup.
212 * If @markup is %NULL, the label will be hidden.
213 */
214void
215gtk_tooltip_set_markup (GtkTooltip *tooltip,
216 const char *markup)
217{
218 g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
219
220 gtk_tooltip_window_set_label_markup (window: GTK_TOOLTIP_WINDOW (ptr: tooltip->window), markup);
221}
222
223/**
224 * gtk_tooltip_set_text:
225 * @tooltip: a `GtkTooltip`
226 * @text: (nullable): a text string
227 *
228 * Sets the text of the tooltip to be @text.
229 *
230 * If @text is %NULL, the label will be hidden.
231 * See also [method@Gtk.Tooltip.set_markup].
232 */
233void
234gtk_tooltip_set_text (GtkTooltip *tooltip,
235 const char *text)
236{
237 g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
238
239 gtk_tooltip_window_set_label_text (window: GTK_TOOLTIP_WINDOW (ptr: tooltip->window), text);
240}
241
242/**
243 * gtk_tooltip_set_icon:
244 * @tooltip: a `GtkTooltip`
245 * @paintable: (nullable): a `GdkPaintable`
246 *
247 * Sets the icon of the tooltip (which is in front of the text) to be
248 * @paintable. If @paintable is %NULL, the image will be hidden.
249 */
250void
251gtk_tooltip_set_icon (GtkTooltip *tooltip,
252 GdkPaintable *paintable)
253{
254 g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
255 g_return_if_fail (paintable == NULL || GDK_IS_PAINTABLE (paintable));
256
257 gtk_tooltip_window_set_image_icon (window: GTK_TOOLTIP_WINDOW (ptr: tooltip->window), paintable);
258}
259
260/**
261 * gtk_tooltip_set_icon_from_icon_name:
262 * @tooltip: a `GtkTooltip`
263 * @icon_name: (nullable): an icon name
264 *
265 * Sets the icon of the tooltip (which is in front of the text) to be
266 * the icon indicated by @icon_name with the size indicated
267 * by @size. If @icon_name is %NULL, the image will be hidden.
268 */
269void
270gtk_tooltip_set_icon_from_icon_name (GtkTooltip *tooltip,
271 const char *icon_name)
272{
273 g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
274
275 gtk_tooltip_window_set_image_icon_from_name (window: GTK_TOOLTIP_WINDOW (ptr: tooltip->window),
276 icon_name);
277}
278
279/**
280 * gtk_tooltip_set_icon_from_gicon:
281 * @tooltip: a `GtkTooltip`
282 * @gicon: (nullable): a `GIcon` representing the icon
283 *
284 * Sets the icon of the tooltip (which is in front of the text)
285 * to be the icon indicated by @gicon with the size indicated
286 * by @size. If @gicon is %NULL, the image will be hidden.
287 */
288void
289gtk_tooltip_set_icon_from_gicon (GtkTooltip *tooltip,
290 GIcon *gicon)
291{
292 g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
293
294 gtk_tooltip_window_set_image_icon_from_gicon (window: GTK_TOOLTIP_WINDOW (ptr: tooltip->window),
295 gicon);
296}
297
298/**
299 * gtk_tooltip_set_custom:
300 * @tooltip: a `GtkTooltip`
301 * @custom_widget: (nullable): a `GtkWidget`, or %NULL to unset the old custom widget.
302 *
303 * Replaces the widget packed into the tooltip with
304 * @custom_widget. @custom_widget does not get destroyed when the tooltip goes
305 * away.
306 * By default a box with a `GtkImage` and `GtkLabel` is embedded in
307 * the tooltip, which can be configured using gtk_tooltip_set_markup()
308 * and gtk_tooltip_set_icon().
309 */
310void
311gtk_tooltip_set_custom (GtkTooltip *tooltip,
312 GtkWidget *custom_widget)
313{
314 g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
315 g_return_if_fail (custom_widget == NULL || GTK_IS_WIDGET (custom_widget));
316
317 /* The custom widget has been updated from the query-tooltip
318 * callback, so we do not want to reset the custom widget later on.
319 */
320 tooltip->custom_was_reset = TRUE;
321
322 gtk_tooltip_window_set_custom_widget (window: GTK_TOOLTIP_WINDOW (ptr: tooltip->window), custom_widget);
323}
324
325/**
326 * gtk_tooltip_set_tip_area:
327 * @tooltip: a `GtkTooltip`
328 * @rect: a `GdkRectangle`
329 *
330 * Sets the area of the widget, where the contents of this tooltip apply,
331 * to be @rect (in widget coordinates). This is especially useful for
332 * properly setting tooltips on `GtkTreeView` rows and cells, `GtkIconViews`,
333 * etc.
334 *
335 * For setting tooltips on `GtkTreeView`, please refer to the convenience
336 * functions for this: gtk_tree_view_set_tooltip_row() and
337 * gtk_tree_view_set_tooltip_cell().
338 */
339void
340gtk_tooltip_set_tip_area (GtkTooltip *tooltip,
341 const GdkRectangle *rect)
342{
343 g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
344
345 if (!rect)
346 tooltip->tip_area_set = FALSE;
347 else
348 {
349 tooltip->tip_area_set = TRUE;
350 tooltip->tip_area = *rect;
351 }
352}
353
354/*
355 * gtk_tooltip_trigger_tooltip_query:
356 * @display: a `GdkDisplay`
357 *
358 * Triggers a new tooltip query on @display, in order to update the current
359 * visible tooltip, or to show/hide the current tooltip. This function is
360 * useful to call when, for example, the state of the widget changed by a
361 * key press.
362 */
363void
364gtk_tooltip_trigger_tooltip_query (GtkWidget *widget)
365{
366 GdkDisplay *display;
367 GdkSeat *seat;
368 GdkDevice *device;
369 GdkSurface *surface;
370 double x, y;
371 GtkWidget *toplevel;
372
373 g_return_if_fail (GTK_IS_WIDGET (widget));
374
375 display = gtk_widget_get_display (widget);
376
377 /* Trigger logic as if the mouse moved */
378 seat = gdk_display_get_default_seat (display);
379 if (seat)
380 device = gdk_seat_get_pointer (seat);
381 else
382 device = NULL;
383 if (device)
384 surface = gdk_device_get_surface_at_position (device, win_x: &x, win_y: &y);
385 else
386 surface = NULL;
387 if (!surface)
388 return;
389
390 toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
391
392 if (toplevel == NULL)
393 return;
394
395 if (gtk_native_get_surface (self: GTK_NATIVE (ptr: toplevel)) != surface)
396 return;
397
398 gtk_widget_translate_coordinates (src_widget: toplevel, dest_widget: widget, src_x: x, src_y: y, dest_x: &x, dest_y: &y);
399
400 gtk_tooltip_handle_event_internal (event_type: GDK_MOTION_NOTIFY, surface, target_widget: widget, dx: x, dy: y);
401}
402
403static void
404gtk_tooltip_window_hide (GtkWidget *widget,
405 gpointer user_data)
406{
407 GtkTooltip *tooltip = GTK_TOOLTIP (user_data);
408
409 gtk_tooltip_set_custom (tooltip, NULL);
410}
411
412GtkWidget *
413_gtk_widget_find_at_coords (GdkSurface *surface,
414 int surface_x,
415 int surface_y,
416 int *widget_x,
417 int *widget_y)
418{
419 GtkWidget *event_widget;
420 GtkWidget *picked_widget;
421 double x, y;
422 double native_x, native_y;
423
424 g_return_val_if_fail (GDK_IS_SURFACE (surface), NULL);
425
426 event_widget = GTK_WIDGET (gtk_native_get_for_surface (surface));
427
428 if (!event_widget)
429 return NULL;
430
431 gtk_native_get_surface_transform (self: GTK_NATIVE (ptr: event_widget), x: &native_x, y: &native_y);
432 x = surface_x - native_x;
433 y = surface_y - native_y;
434
435 picked_widget = gtk_widget_pick (widget: event_widget, x, y, flags: GTK_PICK_INSENSITIVE);
436
437 if (picked_widget != NULL)
438 gtk_widget_translate_coordinates (src_widget: event_widget, dest_widget: picked_widget, src_x: x, src_y: y, dest_x: &x, dest_y: &y);
439
440 *widget_x = x;
441 *widget_y = y;
442
443 return picked_widget;
444}
445
446static int
447tooltip_browse_mode_expired (gpointer data)
448{
449 GtkTooltip *tooltip;
450 GdkDisplay *display;
451
452 tooltip = GTK_TOOLTIP (data);
453
454 tooltip->browse_mode_enabled = FALSE;
455 tooltip->browse_mode_timeout_id = 0;
456
457 if (tooltip->timeout_id)
458 {
459 g_source_remove (tag: tooltip->timeout_id);
460 tooltip->timeout_id = 0;
461 }
462
463 /* destroy tooltip */
464 display = gtk_widget_get_display (widget: tooltip->window);
465 g_object_set_qdata (G_OBJECT (display), quark: quark_current_tooltip, NULL);
466
467 return FALSE;
468}
469
470static void
471gtk_tooltip_display_closed (GdkDisplay *display,
472 gboolean was_error,
473 GtkTooltip *tooltip)
474{
475 if (tooltip->timeout_id)
476 {
477 g_source_remove (tag: tooltip->timeout_id);
478 tooltip->timeout_id = 0;
479 }
480
481 g_object_set_qdata (G_OBJECT (display), quark: quark_current_tooltip, NULL);
482}
483
484static void
485native_weak_notify (gpointer data, GObject *former_object)
486{
487 GtkTooltip *tooltip = data;
488
489 gtk_tooltip_window_set_relative_to (window: GTK_TOOLTIP_WINDOW (ptr: tooltip->window), NULL);
490 tooltip->native = NULL;
491}
492
493static void
494gtk_tooltip_set_surface (GtkTooltip *tooltip,
495 GdkSurface *surface)
496{
497 GtkWidget *native;
498
499 if (surface)
500 native = GTK_WIDGET (gtk_native_get_for_surface (surface));
501 else
502 native = NULL;
503
504 if (tooltip->native == native)
505 return;
506
507 if (GTK_IS_TOOLTIP_WINDOW (ptr: native))
508 return;
509
510 if (tooltip->native)
511 g_object_weak_unref (G_OBJECT (tooltip->native), notify: native_weak_notify, data: tooltip);
512
513 tooltip->native = native;
514
515 if (tooltip->native)
516 g_object_weak_ref (G_OBJECT (tooltip->native), notify: native_weak_notify, data: tooltip);
517
518 if (native)
519 gtk_tooltip_window_set_relative_to (window: GTK_TOOLTIP_WINDOW (ptr: tooltip->window), relative_to: native);
520 else
521 gtk_tooltip_window_set_relative_to (window: GTK_TOOLTIP_WINDOW (ptr: tooltip->window), NULL);
522}
523
524static gboolean
525gtk_tooltip_run_requery (GtkWidget **widget,
526 GtkTooltip *tooltip,
527 int *x,
528 int *y)
529{
530 gboolean has_tooltip = FALSE;
531 gboolean return_value = FALSE;
532
533 /* Reset tooltip */
534 gtk_tooltip_set_markup (tooltip, NULL);
535 gtk_tooltip_set_icon (tooltip, NULL);
536 gtk_tooltip_set_tip_area (tooltip, NULL);
537
538 /* See if the custom widget is again set from the query-tooltip
539 * callback.
540 */
541 tooltip->custom_was_reset = FALSE;
542
543 do
544 {
545 has_tooltip = gtk_widget_get_has_tooltip (widget: *widget);
546
547 if (has_tooltip)
548 return_value = gtk_widget_query_tooltip (widget: *widget, x: *x, y: *y, FALSE, tooltip);
549
550 if (!return_value)
551 {
552 GtkWidget *parent = gtk_widget_get_parent (widget: *widget);
553
554 if (parent)
555 {
556 double xx = *x;
557 double yy = *y;
558
559 if (gtk_widget_get_native (widget: parent) != gtk_widget_get_native (widget: *widget))
560 break;
561
562 gtk_widget_translate_coordinates (src_widget: *widget, dest_widget: parent, src_x: xx, src_y: yy, dest_x: &xx, dest_y: &yy);
563
564 *x = xx;
565 *y = yy;
566 }
567
568 *widget = parent;
569 }
570 else
571 break;
572 }
573 while (*widget);
574
575 /* If the custom widget was not reset in the query-tooltip
576 * callback, we clear it here.
577 */
578 if (!tooltip->custom_was_reset)
579 gtk_tooltip_set_custom (tooltip, NULL);
580
581 return return_value;
582}
583
584static void
585gtk_tooltip_position (GtkTooltip *tooltip,
586 GdkDisplay *display,
587 GtkWidget *new_tooltip_widget,
588 GdkDevice *device)
589{
590 GtkSettings *settings;
591 graphene_rect_t anchor_bounds;
592 GdkRectangle anchor_rect;
593 GdkSurface *effective_toplevel;
594 GtkWidget *toplevel;
595 int rect_anchor_dx = 0;
596 int cursor_size;
597 int anchor_rect_padding;
598 double native_x, native_y;
599
600 gtk_widget_realize (GTK_WIDGET (tooltip->window));
601
602 tooltip->tooltip_widget = new_tooltip_widget;
603
604 toplevel = GTK_WIDGET (gtk_widget_get_native (new_tooltip_widget));
605 gtk_native_get_surface_transform (self: GTK_NATIVE (ptr: toplevel), x: &native_x, y: &native_y);
606
607 if (gtk_widget_compute_bounds (widget: new_tooltip_widget, target: toplevel, out_bounds: &anchor_bounds))
608 {
609 anchor_rect = (GdkRectangle) {
610 floorf (x: anchor_bounds.origin.x + native_x),
611 floorf (x: anchor_bounds.origin.y + native_y),
612 ceilf (x: anchor_bounds.size.width),
613 ceilf (x: anchor_bounds.size.height)
614 };
615 }
616 else
617 {
618 anchor_rect = (GdkRectangle) { 0, 0, 0, 0 };
619 }
620
621 settings = gtk_settings_get_for_display (display);
622 g_object_get (object: settings,
623 first_property_name: "gtk-cursor-theme-size", &cursor_size,
624 NULL);
625
626 if (cursor_size == 0)
627 cursor_size = 16;
628
629 if (device)
630 anchor_rect_padding = MAX (4, cursor_size - 32);
631 else
632 anchor_rect_padding = 4;
633
634 anchor_rect.x -= anchor_rect_padding;
635 anchor_rect.y -= anchor_rect_padding;
636 anchor_rect.width += anchor_rect_padding * 2;
637 anchor_rect.height += anchor_rect_padding * 2;
638
639 if (device)
640 {
641 const int max_x_distance = 32;
642 /* Max 48x48 icon + default padding */
643 const int max_anchor_rect_height = 48 + 8;
644 double px, py;
645 int pointer_x, pointer_y;
646
647 /*
648 * For pointer position triggered tooltips, implement the following
649 * semantics:
650 *
651 * If the anchor rectangle is too tall (meaning if we'd be constrained
652 * and flip, it'd flip too far away), rely only on the pointer position
653 * to position the tooltip. The approximate pointer cursorrectangle is
654 * used as an anchor rectangle.
655 *
656 * If the anchor rectangle isn't to tall, make sure the tooltip isn't too
657 * far away from the pointer position.
658 */
659 effective_toplevel = gtk_native_get_surface (self: GTK_NATIVE (ptr: toplevel));
660 gdk_surface_get_device_position (surface: effective_toplevel, device, x: &px, y: &py, NULL);
661 pointer_x = round (x: px);
662 pointer_y = round (x: py);
663
664 if (anchor_rect.height > max_anchor_rect_height)
665 {
666 anchor_rect.x = pointer_x - 4;
667 anchor_rect.y = pointer_y - 4;
668 anchor_rect.width = cursor_size;
669 anchor_rect.height = cursor_size;
670 }
671 else
672 {
673 int anchor_point_x;
674 int x_distance;
675
676 anchor_point_x = anchor_rect.x + anchor_rect.width / 2;
677 x_distance = pointer_x - anchor_point_x;
678
679 if (x_distance > max_x_distance)
680 rect_anchor_dx = x_distance - max_x_distance;
681 else if (x_distance < -max_x_distance)
682 rect_anchor_dx = x_distance + max_x_distance;
683 }
684 }
685
686 gtk_tooltip_window_position (window: GTK_TOOLTIP_WINDOW (ptr: tooltip->window),
687 rect: &anchor_rect,
688 rect_anchor: GDK_GRAVITY_SOUTH,
689 surface_anchor: GDK_GRAVITY_NORTH,
690 anchor_hints: GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_SLIDE_X,
691 dx: rect_anchor_dx, dy: 0);
692}
693
694static void
695gtk_tooltip_show_tooltip (GdkDisplay *display)
696{
697 double px, py;
698 int x, y;
699 GdkSurface *surface;
700 GtkWidget *tooltip_widget;
701 GdkSeat *seat;
702 GdkDevice *device;
703 GtkTooltip *tooltip;
704 gboolean return_value = FALSE;
705
706 tooltip = g_object_get_qdata (G_OBJECT (display), quark: quark_current_tooltip);
707
708 if (!tooltip->native)
709 return;
710
711 surface = gtk_native_get_surface (self: GTK_NATIVE (ptr: tooltip->native));
712
713 seat = gdk_display_get_default_seat (display);
714 if (seat)
715 device = gdk_seat_get_pointer (seat);
716 else
717 device = NULL;
718
719 if (device)
720 gdk_surface_get_device_position (surface, device, x: &px, y: &py, NULL);
721 else
722 px = py = 0;
723
724 x = round (x: px);
725 y = round (x: py);
726
727 tooltip_widget = _gtk_widget_find_at_coords (surface, surface_x: x, surface_y: y, widget_x: &x, widget_y: &y);
728
729 if (!tooltip_widget)
730 return;
731
732 return_value = gtk_tooltip_run_requery (widget: &tooltip_widget, tooltip, x: &x, y: &y);
733 if (!return_value)
734 return;
735
736 /* FIXME: should use tooltip->window iso tooltip->window */
737 if (display != gtk_widget_get_display (widget: tooltip->window))
738 {
739 g_signal_handlers_disconnect_by_func (display,
740 gtk_tooltip_display_closed,
741 tooltip);
742
743 gtk_window_set_display (GTK_WINDOW (tooltip->window), display);
744
745 g_signal_connect (display, "closed",
746 G_CALLBACK (gtk_tooltip_display_closed), tooltip);
747 }
748
749 gtk_tooltip_position (tooltip, display, new_tooltip_widget: tooltip_widget, device);
750
751 gtk_widget_show (GTK_WIDGET (tooltip->window));
752
753 /* Now a tooltip is visible again on the display, make sure browse
754 * mode is enabled.
755 */
756 tooltip->browse_mode_enabled = TRUE;
757 if (tooltip->browse_mode_timeout_id)
758 {
759 g_source_remove (tag: tooltip->browse_mode_timeout_id);
760 tooltip->browse_mode_timeout_id = 0;
761 }
762}
763
764static void
765gtk_tooltip_hide_tooltip (GtkTooltip *tooltip)
766{
767 guint timeout = BROWSE_DISABLE_TIMEOUT;
768
769 if (!tooltip)
770 return;
771
772 if (tooltip->timeout_id)
773 {
774 g_source_remove (tag: tooltip->timeout_id);
775 tooltip->timeout_id = 0;
776 }
777
778 if (!GTK_TOOLTIP_VISIBLE (tooltip))
779 return;
780
781 tooltip->tooltip_widget = NULL;
782
783 /* The tooltip is gone, after (by default, should be configurable) 500ms
784 * we want to turn off browse mode
785 */
786 if (!tooltip->browse_mode_timeout_id)
787 {
788 tooltip->browse_mode_timeout_id =
789 g_timeout_add_full (priority: 0, interval: timeout,
790 function: tooltip_browse_mode_expired,
791 g_object_ref (tooltip),
792 notify: g_object_unref);
793 gdk_source_set_static_name_by_id (tag: tooltip->browse_mode_timeout_id, name: "[gtk] tooltip_browse_mode_expired");
794 }
795
796 if (tooltip->window)
797 gtk_widget_hide (widget: tooltip->window);
798}
799
800static int
801tooltip_popup_timeout (gpointer data)
802{
803 GdkDisplay *display;
804 GtkTooltip *tooltip;
805
806 display = GDK_DISPLAY (data);
807 tooltip = g_object_get_qdata (G_OBJECT (display), quark: quark_current_tooltip);
808
809 /* This usually does not happen. However, it does occur in language
810 * bindings were reference counting of objects behaves differently.
811 */
812 if (!tooltip)
813 return FALSE;
814
815 gtk_tooltip_show_tooltip (display);
816
817 tooltip->timeout_id = 0;
818
819 return FALSE;
820}
821
822static void
823gtk_tooltip_start_delay (GdkDisplay *display)
824{
825 guint timeout;
826 GtkTooltip *tooltip;
827
828 tooltip = g_object_get_qdata (G_OBJECT (display), quark: quark_current_tooltip);
829
830 if (!tooltip || GTK_TOOLTIP_VISIBLE (tooltip))
831 return;
832
833 if (tooltip->timeout_id)
834 g_source_remove (tag: tooltip->timeout_id);
835
836 if (tooltip->browse_mode_enabled)
837 timeout = BROWSE_TIMEOUT;
838 else
839 timeout = HOVER_TIMEOUT;
840
841 tooltip->timeout_id = g_timeout_add_full (priority: 0, interval: timeout,
842 function: tooltip_popup_timeout,
843 g_object_ref (display),
844 notify: g_object_unref);
845 gdk_source_set_static_name_by_id (tag: tooltip->timeout_id, name: "[gtk] tooltip_popup_timeout");
846}
847
848void
849_gtk_tooltip_hide (GtkWidget *widget)
850{
851 GdkDisplay *display;
852 GtkTooltip *tooltip;
853
854 display = gtk_widget_get_display (widget);
855 tooltip = g_object_get_qdata (G_OBJECT (display), quark: quark_current_tooltip);
856
857 if (!tooltip || !tooltip->window || !tooltip->tooltip_widget)
858 return;
859
860 if (widget == tooltip->tooltip_widget)
861 gtk_tooltip_hide_tooltip (tooltip);
862}
863
864static gboolean
865tooltips_enabled (GdkEvent *event)
866{
867 GdkDevice *source_device;
868 GdkInputSource source;
869 GdkModifierType event_state = 0;
870
871 switch ((guint)gdk_event_get_event_type (event))
872 {
873 case GDK_ENTER_NOTIFY:
874 case GDK_LEAVE_NOTIFY:
875 case GDK_BUTTON_PRESS:
876 case GDK_KEY_PRESS:
877 case GDK_DRAG_ENTER:
878 case GDK_GRAB_BROKEN:
879 case GDK_MOTION_NOTIFY:
880 case GDK_TOUCH_UPDATE:
881 case GDK_SCROLL:
882 break; /* OK */
883
884 default:
885 return FALSE;
886 }
887
888 event_state = gdk_event_get_modifier_state (event);
889 if ((event_state &
890 (GDK_BUTTON1_MASK |
891 GDK_BUTTON2_MASK |
892 GDK_BUTTON3_MASK |
893 GDK_BUTTON4_MASK |
894 GDK_BUTTON5_MASK)) != 0)
895 return FALSE;
896
897 source_device = gdk_event_get_device (event);
898
899 if (!source_device)
900 return FALSE;
901
902 source = gdk_device_get_source (device: source_device);
903
904 if (source != GDK_SOURCE_TOUCHSCREEN)
905 return TRUE;
906
907 return FALSE;
908}
909
910void
911_gtk_tooltip_handle_event (GtkWidget *target,
912 GdkEvent *event)
913{
914 GdkEventType event_type;
915 GdkSurface *surface;
916 double x, y;
917 double nx, ny;
918 GtkNative *native;
919
920 if (!tooltips_enabled (event))
921 return;
922
923 native = gtk_widget_get_native (widget: target);
924 if (!native)
925 return;
926
927 event_type = gdk_event_get_event_type (event);
928 surface = gdk_event_get_surface (event);
929 gdk_event_get_position (event, x: &x, y: &y);
930
931 /* ignore synthetic motion events */
932 if (event_type == GDK_MOTION_NOTIFY &&
933 gdk_event_get_time (event) == GDK_CURRENT_TIME)
934 return;
935
936 gtk_native_get_surface_transform (self: native, x: &nx, y: &ny);
937 gtk_widget_translate_coordinates (GTK_WIDGET (native), dest_widget: target, src_x: x - nx, src_y: y - ny, dest_x: &x, dest_y: &y);
938 gtk_tooltip_handle_event_internal (event_type, surface, target_widget: target, dx: x, dy: y);
939}
940
941/* dx/dy must be in @target_widget's coordinates */
942static void
943gtk_tooltip_handle_event_internal (GdkEventType event_type,
944 GdkSurface *surface,
945 GtkWidget *target_widget,
946 double dx,
947 double dy)
948{
949 int x = dx, y = dy;
950 GdkDisplay *display;
951 GtkTooltip *tooltip;
952
953 display = gdk_surface_get_display (surface);
954 tooltip = g_object_get_qdata (G_OBJECT (display), quark: quark_current_tooltip);
955
956 if (tooltip)
957 gtk_tooltip_set_surface (tooltip, surface);
958
959 /* Hide the tooltip when there's no new tooltip widget */
960 if (!target_widget)
961 {
962 if (tooltip)
963 gtk_tooltip_hide_tooltip (tooltip);
964
965 return;
966 }
967
968 switch ((guint) event_type)
969 {
970 case GDK_BUTTON_PRESS:
971 case GDK_KEY_PRESS:
972 case GDK_DRAG_ENTER:
973 case GDK_GRAB_BROKEN:
974 case GDK_SCROLL:
975 gtk_tooltip_hide_tooltip (tooltip);
976 break;
977
978 case GDK_MOTION_NOTIFY:
979 case GDK_ENTER_NOTIFY:
980 case GDK_LEAVE_NOTIFY:
981 if (tooltip)
982 {
983 gboolean tip_area_set;
984 GdkRectangle tip_area;
985 gboolean hide_tooltip;
986
987 tip_area_set = tooltip->tip_area_set;
988 tip_area = tooltip->tip_area;
989
990 gtk_tooltip_run_requery (widget: &target_widget, tooltip, x: &x, y: &y);
991
992 /* Leave notify should override the query function */
993 hide_tooltip = (event_type == GDK_LEAVE_NOTIFY);
994
995 /* Is the pointer above another widget now? */
996 if (GTK_TOOLTIP_VISIBLE (tooltip))
997 hide_tooltip |= target_widget != tooltip->tooltip_widget;
998
999 /* Did the pointer move out of the previous "context area"? */
1000 if (tip_area_set)
1001 hide_tooltip |= !gdk_rectangle_contains_point (rect: &tip_area, x, y);
1002
1003 if (hide_tooltip)
1004 gtk_tooltip_hide_tooltip (tooltip);
1005 else
1006 gtk_tooltip_start_delay (display);
1007 }
1008 else
1009 {
1010 /* Need a new tooltip for this display */
1011 tooltip = g_object_new (GTK_TYPE_TOOLTIP, NULL);
1012 g_object_set_qdata_full (G_OBJECT (display), quark: quark_current_tooltip,
1013 data: tooltip, destroy: g_object_unref);
1014 g_signal_connect (display, "closed",
1015 G_CALLBACK (gtk_tooltip_display_closed), tooltip);
1016
1017 gtk_tooltip_set_surface (tooltip, surface);
1018
1019 gtk_tooltip_start_delay (display);
1020 }
1021 break;
1022
1023 default:
1024 break;
1025 }
1026}
1027
1028void
1029gtk_tooltip_maybe_allocate (GtkNative *native)
1030{
1031 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (native));
1032 GtkTooltip *tooltip;
1033
1034 tooltip = g_object_get_qdata (G_OBJECT (display), quark: quark_current_tooltip);
1035 if (!tooltip || GTK_NATIVE (ptr: tooltip->native) != native)
1036 return;
1037
1038 gtk_tooltip_window_present (window: GTK_TOOLTIP_WINDOW (ptr: tooltip->window));
1039}
1040
1041void
1042gtk_tooltip_unset_surface (GtkNative *native)
1043{
1044 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (native));
1045 GtkTooltip *tooltip;
1046
1047 tooltip = g_object_get_qdata (G_OBJECT (display), quark: quark_current_tooltip);
1048 if (!tooltip || GTK_NATIVE (ptr: tooltip->native) != native)
1049 return;
1050
1051 gtk_tooltip_set_surface (tooltip, NULL);
1052}
1053
1054

source code of gtk/gtk/gtktooltip.c