1/* GTK - The GIMP Toolkit
2 * Copyright 2015 Emmanuele Bassi
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18/*
19 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
20 * file for a list of people on the GTK+ Team. See the ChangeLog
21 * files for a list of changes. These files are distributed with
22 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 */
24
25#include "config.h"
26
27#include "gtktooltipwindowprivate.h"
28
29#include "gtkbox.h"
30#include "gtkimage.h"
31#include "gtkintl.h"
32#include "gtklabel.h"
33#include "gtkprivate.h"
34#include "gtksettings.h"
35#include "gtksizerequest.h"
36#include "gtkwindowprivate.h"
37#include "gtkwidgetprivate.h"
38#include "gtknativeprivate.h"
39#include "gtkcssboxesimplprivate.h"
40
41#include "gdk/gdksurfaceprivate.h"
42
43struct _GtkTooltipWindow
44{
45 GtkWidget parent_instance;
46
47 GdkSurface *surface;
48 GskRenderer *renderer;
49
50 GtkWidget *relative_to;
51 GdkRectangle rect;
52 GdkGravity rect_anchor;
53 GdkGravity surface_anchor;
54 GdkAnchorHints anchor_hints;
55 int dx;
56 int dy;
57 guint surface_transform_changed_cb;
58
59 GtkWidget *box;
60 GtkWidget *image;
61 GtkWidget *label;
62 GtkWidget *custom_widget;
63};
64
65struct _GtkTooltipWindowClass
66{
67 GtkWidgetClass parent_class;
68};
69
70static void gtk_tooltip_window_native_init (GtkNativeInterface *iface);
71
72G_DEFINE_TYPE_WITH_CODE (GtkTooltipWindow, gtk_tooltip_window, GTK_TYPE_WIDGET,
73 G_IMPLEMENT_INTERFACE (GTK_TYPE_NATIVE,
74 gtk_tooltip_window_native_init))
75
76
77static GdkSurface *
78gtk_tooltip_window_native_get_surface (GtkNative *native)
79{
80 GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: native);
81
82 return window->surface;
83}
84
85static GskRenderer *
86gtk_tooltip_window_native_get_renderer (GtkNative *native)
87{
88 GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: native);
89
90 return window->renderer;
91}
92
93static void
94gtk_tooltip_window_native_get_surface_transform (GtkNative *native,
95 double *x,
96 double *y)
97{
98 GtkCssBoxes css_boxes;
99 const graphene_rect_t *margin_rect;
100
101 gtk_css_boxes_init (boxes: &css_boxes, GTK_WIDGET (native));
102 margin_rect = gtk_css_boxes_get_margin_rect (boxes: &css_boxes);
103
104 *x = - margin_rect->origin.x;
105 *y = - margin_rect->origin.y;
106}
107
108static GdkPopupLayout *
109create_popup_layout (GtkTooltipWindow *window)
110{
111 GdkPopupLayout *layout;
112
113 layout = gdk_popup_layout_new (anchor_rect: &window->rect,
114 rect_anchor: window->rect_anchor,
115 surface_anchor: window->surface_anchor);
116 gdk_popup_layout_set_anchor_hints (layout, anchor_hints: window->anchor_hints);
117 gdk_popup_layout_set_offset (layout, dx: window->dx, dy: window->dy);
118
119 return layout;
120}
121
122static void
123gtk_tooltip_window_relayout (GtkTooltipWindow *window)
124{
125 GtkRequisition req;
126 GdkPopupLayout *layout;
127
128 if (!gtk_widget_get_visible (GTK_WIDGET (window)) ||
129 window->surface == NULL)
130 return;
131
132 gtk_widget_get_preferred_size (GTK_WIDGET (window), NULL, natural_size: &req);
133 layout = create_popup_layout (window);
134 gdk_popup_present (popup: GDK_POPUP (ptr: window->surface),
135 MAX (req.width, 1),
136 MAX (req.height, 1),
137 layout);
138 gdk_popup_layout_unref (layout);
139}
140
141void
142gtk_tooltip_window_present (GtkTooltipWindow *window)
143{
144 GtkWidget *widget = GTK_WIDGET (window);
145
146 if (!_gtk_widget_get_alloc_needed (widget))
147 {
148 gtk_widget_ensure_allocate (widget);
149 }
150 else if (gtk_widget_get_visible (widget))
151 {
152 gtk_tooltip_window_relayout (window);
153 }
154}
155
156static void
157gtk_tooltip_window_native_layout (GtkNative *native,
158 int width,
159 int height)
160{
161 GtkWidget *widget = GTK_WIDGET (native);
162
163 if (gtk_widget_needs_allocate (widget))
164 gtk_widget_allocate (widget, width, height, baseline: -1, NULL);
165 else
166 gtk_widget_ensure_allocate (widget);
167
168}
169
170static void
171gtk_tooltip_window_native_init (GtkNativeInterface *iface)
172{
173 iface->get_surface = gtk_tooltip_window_native_get_surface;
174 iface->get_renderer = gtk_tooltip_window_native_get_renderer;
175 iface->get_surface_transform = gtk_tooltip_window_native_get_surface_transform;
176 iface->layout = gtk_tooltip_window_native_layout;
177}
178
179static void
180mapped_changed (GdkSurface *surface,
181 GParamSpec *pspec,
182 GtkWidget *widget)
183{
184 if (!gdk_surface_get_mapped (surface))
185 gtk_widget_hide (widget);
186}
187
188static gboolean
189surface_render (GdkSurface *surface,
190 cairo_region_t *region,
191 GtkWidget *widget)
192{
193 gtk_widget_render (widget, surface, region);
194 return TRUE;
195}
196
197static gboolean
198surface_event (GdkSurface *surface,
199 GdkEvent *event,
200 GtkWidget *widget)
201{
202 gtk_main_do_event (event);
203 return TRUE;
204}
205
206static void
207gtk_tooltip_window_realize (GtkWidget *widget)
208{
209 GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: widget);
210 GdkSurface *parent;
211
212 parent = gtk_native_get_surface (self: gtk_widget_get_native (widget: window->relative_to));
213 window->surface = gdk_surface_new_popup (parent, FALSE);
214
215 gdk_surface_set_widget (self: window->surface, widget);
216
217 g_signal_connect (window->surface, "notify::mapped", G_CALLBACK (mapped_changed), widget);
218 g_signal_connect (window->surface, "render", G_CALLBACK (surface_render), widget);
219 g_signal_connect (window->surface, "event", G_CALLBACK (surface_event), widget);
220
221 GTK_WIDGET_CLASS (gtk_tooltip_window_parent_class)->realize (widget);
222
223 window->renderer = gsk_renderer_new_for_surface (surface: window->surface);
224
225 gtk_native_realize (self: GTK_NATIVE (ptr: window));
226}
227
228static void
229gtk_tooltip_window_unrealize (GtkWidget *widget)
230{
231 GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: widget);
232
233 gtk_native_unrealize (self: GTK_NATIVE (ptr: window));
234
235 GTK_WIDGET_CLASS (gtk_tooltip_window_parent_class)->unrealize (widget);
236
237 gsk_renderer_unrealize (renderer: window->renderer);
238 g_clear_object (&window->renderer);
239
240 g_signal_handlers_disconnect_by_func (window->surface, mapped_changed, widget);
241 g_signal_handlers_disconnect_by_func (window->surface, surface_render, widget);
242 g_signal_handlers_disconnect_by_func (window->surface, surface_event, widget);
243 gdk_surface_set_widget (self: window->surface, NULL);
244 gdk_surface_destroy (surface: window->surface);
245 g_clear_object (&window->surface);
246}
247
248
249static void
250unset_surface_transform_changed_cb (gpointer data)
251{
252 GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: data);
253
254 window->surface_transform_changed_cb = 0;
255}
256
257static gboolean
258surface_transform_changed_cb (GtkWidget *widget,
259 const graphene_matrix_t *transform,
260 gpointer user_data)
261{
262 GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: widget);
263
264 gtk_tooltip_window_relayout (window);
265
266 return G_SOURCE_CONTINUE;
267}
268
269
270static void
271gtk_tooltip_window_map (GtkWidget *widget)
272{
273 GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: widget);
274 GdkPopupLayout *layout;
275
276 layout = create_popup_layout (window);
277 gdk_popup_present (popup: GDK_POPUP (ptr: window->surface),
278 width: gdk_surface_get_width (surface: window->surface),
279 height: gdk_surface_get_height (surface: window->surface),
280 layout);
281 gdk_popup_layout_unref (layout);
282
283 window->surface_transform_changed_cb =
284 gtk_widget_add_surface_transform_changed_callback (widget: window->relative_to,
285 callback: surface_transform_changed_cb,
286 user_data: window,
287 notify: unset_surface_transform_changed_cb);
288
289 GTK_WIDGET_CLASS (gtk_tooltip_window_parent_class)->map (widget);
290
291 if (gtk_widget_get_visible (widget: window->box))
292 gtk_widget_map (widget: window->box);
293}
294
295static void
296gtk_tooltip_window_unmap (GtkWidget *widget)
297{
298 GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: widget);
299
300 gtk_widget_remove_surface_transform_changed_callback (widget: window->relative_to,
301 id: window->surface_transform_changed_cb);
302 window->surface_transform_changed_cb = 0;
303
304 GTK_WIDGET_CLASS (gtk_tooltip_window_parent_class)->unmap (widget);
305 gdk_surface_hide (surface: window->surface);
306
307 gtk_widget_unmap (widget: window->box);
308}
309
310static void
311gtk_tooltip_window_measure (GtkWidget *widget,
312 GtkOrientation orientation,
313 int for_size,
314 int *minimum,
315 int *natural,
316 int *minimum_baseline,
317 int *natural_baseline)
318{
319 GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: widget);
320
321 if (window->box)
322 gtk_widget_measure (widget: window->box,
323 orientation, for_size,
324 minimum, natural,
325 minimum_baseline, natural_baseline);
326}
327
328static void
329gtk_tooltip_window_size_allocate (GtkWidget *widget,
330 int width,
331 int height,
332 int baseline)
333{
334 GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: widget);
335
336 if (window->box)
337 gtk_widget_allocate (widget: window->box, width, height, baseline, NULL);
338}
339
340static void
341gtk_tooltip_window_show (GtkWidget *widget)
342{
343 _gtk_widget_set_visible_flag (widget, TRUE);
344 gtk_widget_realize (widget);
345 gtk_tooltip_window_present (window: GTK_TOOLTIP_WINDOW (ptr: widget));
346 gtk_widget_map (widget);
347}
348
349static void
350gtk_tooltip_window_hide (GtkWidget *widget)
351{
352 _gtk_widget_set_visible_flag (widget, FALSE);
353 gtk_widget_unmap (widget);
354}
355
356static void
357gtk_tooltip_window_dispose (GObject *object)
358{
359 GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: object);
360
361 if (window->relative_to)
362 gtk_widget_unparent (GTK_WIDGET (window));
363
364 g_clear_pointer (&window->box, gtk_widget_unparent);
365
366 G_OBJECT_CLASS (gtk_tooltip_window_parent_class)->dispose (object);
367}
368
369static void
370gtk_tooltip_window_class_init (GtkTooltipWindowClass *klass)
371{
372 GObjectClass *object_class = G_OBJECT_CLASS (klass);
373 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
374
375 object_class->dispose = gtk_tooltip_window_dispose;
376 widget_class->realize = gtk_tooltip_window_realize;
377 widget_class->unrealize = gtk_tooltip_window_unrealize;
378 widget_class->map = gtk_tooltip_window_map;
379 widget_class->unmap = gtk_tooltip_window_unmap;
380 widget_class->measure = gtk_tooltip_window_measure;
381 widget_class->size_allocate = gtk_tooltip_window_size_allocate;
382 widget_class->show = gtk_tooltip_window_show;
383 widget_class->hide = gtk_tooltip_window_hide;
384
385 gtk_widget_class_set_css_name (widget_class, I_("tooltip"));
386 gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/ui/gtktooltipwindow.ui");
387
388 gtk_widget_class_bind_template_child (widget_class, GtkTooltipWindow, box);
389 gtk_widget_class_bind_template_child (widget_class, GtkTooltipWindow, image);
390 gtk_widget_class_bind_template_child (widget_class, GtkTooltipWindow, label);
391}
392
393static void
394gtk_tooltip_window_init (GtkTooltipWindow *self)
395{
396 gtk_widget_init_template (GTK_WIDGET (self));
397}
398
399GtkWidget *
400gtk_tooltip_window_new (void)
401{
402 return g_object_new (GTK_TYPE_TOOLTIP_WINDOW, NULL);
403}
404
405static void
406update_label_width (GtkLabel *label)
407{
408 const char *text;
409
410 text = gtk_label_get_text (self: label);
411 if (strchr (s: text, c: '\n'))
412 {
413 gtk_label_set_wrap (self: label, FALSE);
414 }
415 else
416 {
417 int len;
418
419 len = g_utf8_strlen (p: text, max: -1);
420
421 gtk_label_set_max_width_chars (self: label, MIN (len, 50));
422 gtk_label_set_wrap (self: label, TRUE);
423 }
424}
425
426void
427gtk_tooltip_window_set_label_markup (GtkTooltipWindow *window,
428 const char *markup)
429{
430 if (markup != NULL)
431 {
432 gtk_label_set_markup (GTK_LABEL (window->label), str: markup);
433 update_label_width (GTK_LABEL (window->label));
434 gtk_widget_show (widget: window->label);
435 }
436 else
437 {
438 gtk_widget_hide (widget: window->label);
439 }
440}
441
442void
443gtk_tooltip_window_set_label_text (GtkTooltipWindow *window,
444 const char *text)
445{
446 if (text != NULL)
447 {
448 gtk_label_set_text (GTK_LABEL (window->label), str: text);
449 update_label_width (GTK_LABEL (window->label));
450 gtk_widget_show (widget: window->label);
451 }
452 else
453 {
454 gtk_widget_hide (widget: window->label);
455 }
456}
457
458void
459gtk_tooltip_window_set_image_icon (GtkTooltipWindow *window,
460 GdkPaintable *paintable)
461{
462 if (paintable != NULL)
463 {
464 gtk_image_set_from_paintable (GTK_IMAGE (window->image), paintable);
465 gtk_widget_show (widget: window->image);
466 }
467 else
468 {
469 gtk_widget_hide (widget: window->image);
470 }
471}
472
473void
474gtk_tooltip_window_set_image_icon_from_name (GtkTooltipWindow *window,
475 const char *icon_name)
476{
477 if (icon_name)
478 {
479 gtk_image_set_from_icon_name (GTK_IMAGE (window->image), icon_name);
480 gtk_widget_show (widget: window->image);
481 }
482 else
483 {
484 gtk_widget_hide (widget: window->image);
485 }
486}
487
488void
489gtk_tooltip_window_set_image_icon_from_gicon (GtkTooltipWindow *window,
490 GIcon *gicon)
491{
492 if (gicon != NULL)
493 {
494 gtk_image_set_from_gicon (GTK_IMAGE (window->image), icon: gicon);
495 gtk_widget_show (widget: window->image);
496 }
497 else
498 {
499 gtk_widget_hide (widget: window->image);
500 }
501}
502
503void
504gtk_tooltip_window_set_custom_widget (GtkTooltipWindow *window,
505 GtkWidget *custom_widget)
506{
507 /* No need to do anything if the custom widget stays the same */
508 if (window->custom_widget == custom_widget)
509 return;
510
511 if (window->custom_widget != NULL)
512 {
513 GtkWidget *custom = window->custom_widget;
514
515 /* Note: We must reset window->custom_widget first,
516 * since gtk_container_remove() will recurse into
517 * gtk_tooltip_set_custom()
518 */
519 window->custom_widget = NULL;
520 gtk_box_remove (GTK_BOX (window->box), child: custom);
521 g_object_unref (object: custom);
522 }
523
524 if (custom_widget != NULL)
525 {
526 window->custom_widget = g_object_ref (custom_widget);
527
528 gtk_box_append (GTK_BOX (window->box), child: custom_widget);
529 gtk_widget_show (widget: custom_widget);
530 gtk_widget_hide (widget: window->image);
531 gtk_widget_hide (widget: window->label);
532 }
533}
534
535void
536gtk_tooltip_window_set_relative_to (GtkTooltipWindow *window,
537 GtkWidget *relative_to)
538{
539 g_return_if_fail (GTK_WIDGET (window) != relative_to);
540
541 if (window->relative_to == relative_to)
542 return;
543
544 g_object_ref (window);
545
546 if (window->relative_to)
547 gtk_widget_unparent (GTK_WIDGET (window));
548
549 window->relative_to = relative_to;
550
551 if (window->relative_to)
552 gtk_widget_set_parent (GTK_WIDGET (window), parent: relative_to);
553
554 g_object_unref (object: window);
555}
556
557void
558gtk_tooltip_window_position (GtkTooltipWindow *window,
559 GdkRectangle *rect,
560 GdkGravity rect_anchor,
561 GdkGravity surface_anchor,
562 GdkAnchorHints anchor_hints,
563 int dx,
564 int dy)
565{
566 window->rect = *rect;
567 window->rect_anchor = rect_anchor;
568 window->surface_anchor = surface_anchor;
569 window->anchor_hints = anchor_hints;
570 window->dx = dx;
571 window->dy = dy;
572
573 gtk_tooltip_window_relayout (window);
574}
575

source code of gtk/gtk/gtktooltipwindow.c