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 | |
43 | struct _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 | |
65 | struct _GtkTooltipWindowClass |
66 | { |
67 | GtkWidgetClass parent_class; |
68 | }; |
69 | |
70 | static void gtk_tooltip_window_native_init (GtkNativeInterface *iface); |
71 | |
72 | G_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 | |
77 | static GdkSurface * |
78 | gtk_tooltip_window_native_get_surface (GtkNative *native) |
79 | { |
80 | GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: native); |
81 | |
82 | return window->surface; |
83 | } |
84 | |
85 | static GskRenderer * |
86 | gtk_tooltip_window_native_get_renderer (GtkNative *native) |
87 | { |
88 | GtkTooltipWindow *window = GTK_TOOLTIP_WINDOW (ptr: native); |
89 | |
90 | return window->renderer; |
91 | } |
92 | |
93 | static void |
94 | gtk_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 | |
108 | static GdkPopupLayout * |
109 | (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 | |
122 | static void |
123 | gtk_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 | |
141 | void |
142 | gtk_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 | |
156 | static void |
157 | gtk_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 | |
170 | static void |
171 | gtk_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 | |
179 | static void |
180 | mapped_changed (GdkSurface *surface, |
181 | GParamSpec *pspec, |
182 | GtkWidget *widget) |
183 | { |
184 | if (!gdk_surface_get_mapped (surface)) |
185 | gtk_widget_hide (widget); |
186 | } |
187 | |
188 | static gboolean |
189 | surface_render (GdkSurface *surface, |
190 | cairo_region_t *region, |
191 | GtkWidget *widget) |
192 | { |
193 | gtk_widget_render (widget, surface, region); |
194 | return TRUE; |
195 | } |
196 | |
197 | static gboolean |
198 | surface_event (GdkSurface *surface, |
199 | GdkEvent *event, |
200 | GtkWidget *widget) |
201 | { |
202 | gtk_main_do_event (event); |
203 | return TRUE; |
204 | } |
205 | |
206 | static void |
207 | gtk_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 | |
228 | static void |
229 | gtk_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 | |
249 | static void |
250 | unset_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 | |
257 | static gboolean |
258 | surface_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 | |
270 | static void |
271 | gtk_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 | |
295 | static void |
296 | gtk_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 | |
310 | static void |
311 | gtk_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 | |
328 | static void |
329 | gtk_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 | |
340 | static void |
341 | gtk_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 | |
349 | static void |
350 | gtk_tooltip_window_hide (GtkWidget *widget) |
351 | { |
352 | _gtk_widget_set_visible_flag (widget, FALSE); |
353 | gtk_widget_unmap (widget); |
354 | } |
355 | |
356 | static void |
357 | gtk_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 | |
369 | static void |
370 | gtk_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 | |
393 | static void |
394 | gtk_tooltip_window_init (GtkTooltipWindow *self) |
395 | { |
396 | gtk_widget_init_template (GTK_WIDGET (self)); |
397 | } |
398 | |
399 | GtkWidget * |
400 | gtk_tooltip_window_new (void) |
401 | { |
402 | return g_object_new (GTK_TYPE_TOOLTIP_WINDOW, NULL); |
403 | } |
404 | |
405 | static void |
406 | update_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 | |
426 | void |
427 | gtk_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 | |
442 | void |
443 | gtk_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 | |
458 | void |
459 | gtk_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 | |
473 | void |
474 | gtk_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 | |
488 | void |
489 | gtk_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 | |
503 | void |
504 | gtk_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 | |
535 | void |
536 | gtk_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 | |
557 | void |
558 | gtk_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 | |