1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
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.
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.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
21 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
22 * file for a list of people on the GTK+ Team. See the ChangeLog
23 * files for a list of changes. These files are distributed with
24 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
35 #include "gtkmenuitem.h"
36 #include "gtkprivate.h"
37 #include "gtkwidget.h"
38 #include "gtkwindow.h"
40 #include "gtktooltips.h"
43 #define DEFAULT_DELAY 500 /* Default delay in ms */
44 #define STICKY_DELAY 0 /* Delay before popping up next tip
47 #define STICKY_REVERT_DELAY 1000 /* Delay before sticky tooltips revert
51 static void gtk_tooltips_class_init (GtkTooltipsClass *klass);
52 static void gtk_tooltips_init (GtkTooltips *tooltips);
53 static void gtk_tooltips_destroy (GtkObject *object);
55 static void gtk_tooltips_event_handler (GtkWidget *widget,
57 static void gtk_tooltips_widget_unmap (GtkWidget *widget,
59 static void gtk_tooltips_widget_remove (GtkWidget *widget,
61 static void gtk_tooltips_set_active_widget (GtkTooltips *tooltips,
63 static gint gtk_tooltips_timeout (gpointer data);
65 static gint gtk_tooltips_paint_window (GtkTooltips *tooltips);
66 static void gtk_tooltips_draw_tips (GtkTooltips *tooltips);
67 static void gtk_tooltips_unset_tip_window (GtkTooltips *tooltips);
69 static gboolean get_keyboard_mode (GtkWidget *widget);
71 static GtkObjectClass *parent_class;
72 static const gchar *tooltips_data_key = "_GtkTooltipsData";
73 static const gchar *tooltips_info_key = "_GtkTooltipsInfo";
76 gtk_tooltips_get_type (void)
78 static GType tooltips_type = 0;
82 static const GTypeInfo tooltips_info =
84 sizeof (GtkTooltipsClass),
86 NULL, /* base_finalize */
87 (GClassInitFunc) gtk_tooltips_class_init,
88 NULL, /* class_finalize */
89 NULL, /* class_data */
92 (GInstanceInitFunc) gtk_tooltips_init,
95 tooltips_type = g_type_register_static (GTK_TYPE_OBJECT, "GtkTooltips",
103 gtk_tooltips_class_init (GtkTooltipsClass *class)
105 GtkObjectClass *object_class;
107 object_class = (GtkObjectClass*) class;
109 parent_class = g_type_class_peek_parent (class);
111 object_class->destroy = gtk_tooltips_destroy;
115 gtk_tooltips_init (GtkTooltips *tooltips)
117 tooltips->tip_window = NULL;
118 tooltips->active_tips_data = NULL;
119 tooltips->tips_data_list = NULL;
121 tooltips->delay = DEFAULT_DELAY;
122 tooltips->enabled = TRUE;
123 tooltips->timer_tag = 0;
124 tooltips->use_sticky_delay = FALSE;
125 tooltips->last_popdown.tv_sec = -1;
126 tooltips->last_popdown.tv_usec = -1;
130 gtk_tooltips_new (void)
132 return g_object_new (GTK_TYPE_TOOLTIPS, NULL);
136 gtk_tooltips_destroy_data (GtkTooltipsData *tooltipsdata)
138 g_free (tooltipsdata->tip_text);
139 g_free (tooltipsdata->tip_private);
141 g_signal_handlers_disconnect_by_func (tooltipsdata->widget,
142 gtk_tooltips_event_handler,
144 g_signal_handlers_disconnect_by_func (tooltipsdata->widget,
145 gtk_tooltips_widget_unmap,
147 g_signal_handlers_disconnect_by_func (tooltipsdata->widget,
148 gtk_tooltips_widget_remove,
151 g_object_set_data (G_OBJECT (tooltipsdata->widget), tooltips_data_key, NULL);
152 g_object_unref (tooltipsdata->widget);
153 g_free (tooltipsdata);
157 tip_window_display_closed (GdkDisplay *display,
159 GtkTooltips *tooltips)
161 gtk_tooltips_unset_tip_window (tooltips);
165 disconnect_tip_window_display_closed (GtkTooltips *tooltips)
167 g_signal_handlers_disconnect_by_func (gtk_widget_get_display (tooltips->tip_window),
168 (gpointer) tip_window_display_closed,
173 gtk_tooltips_unset_tip_window (GtkTooltips *tooltips)
175 if (tooltips->tip_window)
177 disconnect_tip_window_display_closed (tooltips);
179 gtk_widget_destroy (tooltips->tip_window);
180 tooltips->tip_window = NULL;
185 gtk_tooltips_destroy (GtkObject *object)
187 GtkTooltips *tooltips = GTK_TOOLTIPS (object);
189 GtkTooltipsData *tooltipsdata;
191 g_return_if_fail (tooltips != NULL);
193 if (tooltips->timer_tag)
195 g_source_remove (tooltips->timer_tag);
196 tooltips->timer_tag = 0;
199 if (tooltips->tips_data_list != NULL)
201 current = g_list_first (tooltips->tips_data_list);
202 while (current != NULL)
204 tooltipsdata = (GtkTooltipsData*) current->data;
205 current = current->next;
206 gtk_tooltips_widget_remove (tooltipsdata->widget, tooltipsdata);
210 gtk_tooltips_unset_tip_window (tooltips);
212 GTK_OBJECT_CLASS (parent_class)->destroy (object);
216 gtk_tooltips_update_screen (GtkTooltips *tooltips,
219 gboolean screen_changed = FALSE;
221 if (tooltips->active_tips_data &&
222 tooltips->active_tips_data->widget)
224 GdkScreen *screen = gtk_widget_get_screen (tooltips->active_tips_data->widget);
226 screen_changed = (screen != gtk_widget_get_screen (tooltips->tip_window));
231 disconnect_tip_window_display_closed (tooltips);
233 gtk_window_set_screen (GTK_WINDOW (tooltips->tip_window), screen);
237 if (screen_changed || new_window)
238 g_signal_connect (gtk_widget_get_display (tooltips->tip_window), "closed",
239 G_CALLBACK (tip_window_display_closed), tooltips);
244 gtk_tooltips_force_window (GtkTooltips *tooltips)
246 g_return_if_fail (GTK_IS_TOOLTIPS (tooltips));
248 if (!tooltips->tip_window)
250 tooltips->tip_window = gtk_window_new (GTK_WINDOW_POPUP);
251 gtk_tooltips_update_screen (tooltips, TRUE);
252 gtk_widget_set_app_paintable (tooltips->tip_window, TRUE);
253 gtk_window_set_resizable (GTK_WINDOW (tooltips->tip_window), FALSE);
254 gtk_widget_set_name (tooltips->tip_window, "gtk-tooltips");
255 gtk_container_set_border_width (GTK_CONTAINER (tooltips->tip_window), 4);
257 g_signal_connect_swapped (tooltips->tip_window,
259 G_CALLBACK (gtk_tooltips_paint_window),
262 tooltips->tip_label = gtk_label_new (NULL);
263 gtk_label_set_line_wrap (GTK_LABEL (tooltips->tip_label), TRUE);
264 gtk_misc_set_alignment (GTK_MISC (tooltips->tip_label), 0.5, 0.5);
265 gtk_widget_show (tooltips->tip_label);
267 gtk_container_add (GTK_CONTAINER (tooltips->tip_window), tooltips->tip_label);
269 g_signal_connect (tooltips->tip_window,
271 G_CALLBACK (gtk_widget_destroyed),
272 &tooltips->tip_window);
277 gtk_tooltips_enable (GtkTooltips *tooltips)
279 g_return_if_fail (tooltips != NULL);
281 tooltips->enabled = TRUE;
285 gtk_tooltips_disable (GtkTooltips *tooltips)
287 g_return_if_fail (tooltips != NULL);
289 gtk_tooltips_set_active_widget (tooltips, NULL);
291 tooltips->enabled = FALSE;
295 gtk_tooltips_set_delay (GtkTooltips *tooltips,
298 g_return_if_fail (tooltips != NULL);
300 tooltips->delay = delay;
304 gtk_tooltips_data_get (GtkWidget *widget)
306 g_return_val_if_fail (widget != NULL, NULL);
308 return g_object_get_data (G_OBJECT (widget), tooltips_data_key);
312 gtk_tooltips_set_tip (GtkTooltips *tooltips,
314 const gchar *tip_text,
315 const gchar *tip_private)
317 GtkTooltipsData *tooltipsdata;
319 g_return_if_fail (GTK_IS_TOOLTIPS (tooltips));
320 g_return_if_fail (widget != NULL);
322 tooltipsdata = gtk_tooltips_data_get (widget);
327 gtk_tooltips_widget_remove (tooltipsdata->widget, tooltipsdata);
331 if (tooltips->active_tips_data
332 && tooltips->active_tips_data->widget == widget
333 && GTK_WIDGET_DRAWABLE (tooltips->active_tips_data->widget))
335 g_free (tooltipsdata->tip_text);
336 g_free (tooltipsdata->tip_private);
338 tooltipsdata->tip_text = g_strdup (tip_text);
339 tooltipsdata->tip_private = g_strdup (tip_private);
341 gtk_tooltips_draw_tips (tooltips);
345 g_object_ref (widget);
348 gtk_tooltips_widget_remove (tooltipsdata->widget, tooltipsdata);
350 tooltipsdata = g_new0 (GtkTooltipsData, 1);
352 tooltipsdata->tooltips = tooltips;
353 tooltipsdata->widget = widget;
355 tooltipsdata->tip_text = g_strdup (tip_text);
356 tooltipsdata->tip_private = g_strdup (tip_private);
358 tooltips->tips_data_list = g_list_append (tooltips->tips_data_list,
360 g_signal_connect_after (widget, "event-after",
361 G_CALLBACK (gtk_tooltips_event_handler),
364 g_object_set_data (G_OBJECT (widget), tooltips_data_key,
367 g_signal_connect (widget, "unmap",
368 G_CALLBACK (gtk_tooltips_widget_unmap),
371 g_signal_connect (widget, "unrealize",
372 G_CALLBACK (gtk_tooltips_widget_unmap),
375 g_signal_connect (widget, "destroy",
376 G_CALLBACK (gtk_tooltips_widget_remove),
382 gtk_tooltips_paint_window (GtkTooltips *tooltips)
386 gtk_widget_size_request (tooltips->tip_window, &req);
387 gtk_paint_flat_box (tooltips->tip_window->style, tooltips->tip_window->window,
388 GTK_STATE_NORMAL, GTK_SHADOW_OUT,
389 NULL, GTK_WIDGET(tooltips->tip_window), "tooltip",
390 0, 0, req.width, req.height);
396 gtk_tooltips_draw_tips (GtkTooltips *tooltips)
398 GtkRequisition requisition;
402 GtkTooltipsData *data;
403 gboolean keyboard_mode;
405 GdkScreen *pointer_screen;
406 gint monitor_num, px, py;
407 GdkRectangle monitor;
409 if (!tooltips->tip_window)
410 gtk_tooltips_force_window (tooltips);
411 else if (GTK_WIDGET_VISIBLE (tooltips->tip_window))
412 g_get_current_time (&tooltips->last_popdown);
414 gtk_widget_ensure_style (tooltips->tip_window);
415 style = tooltips->tip_window->style;
417 widget = tooltips->active_tips_data->widget;
418 g_object_set_data (G_OBJECT (tooltips->tip_window), tooltips_info_key,
421 keyboard_mode = get_keyboard_mode (widget);
423 gtk_tooltips_update_screen (tooltips, FALSE);
425 screen = gtk_widget_get_screen (widget);
427 data = tooltips->active_tips_data;
429 gtk_label_set_text (GTK_LABEL (tooltips->tip_label), data->tip_text);
431 gtk_widget_size_request (tooltips->tip_window, &requisition);
432 w = requisition.width;
433 h = requisition.height;
435 gdk_window_get_origin (widget->window, &x, &y);
436 if (GTK_WIDGET_NO_WINDOW (widget))
438 x += widget->allocation.x;
439 y += widget->allocation.y;
442 x += widget->allocation.width / 2;
445 gdk_window_get_pointer (gdk_screen_get_root_window (screen),
450 gdk_display_get_pointer (gdk_screen_get_display (screen),
451 &pointer_screen, &px, &py, NULL);
452 if (pointer_screen != screen)
457 monitor_num = gdk_screen_get_monitor_at_point (screen, px, py);
458 gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
460 if ((x + w) > monitor.x + monitor.width)
461 x -= (x + w) - (monitor.x + monitor.width);
462 else if (x < monitor.x)
465 if ((y + h + widget->allocation.height + 4) > monitor.y + monitor.height)
468 y = y + widget->allocation.height + 4;
470 gtk_window_move (GTK_WINDOW (tooltips->tip_window), x, y);
471 gtk_widget_show (tooltips->tip_window);
475 gtk_tooltips_timeout (gpointer data)
477 GtkTooltips *tooltips = (GtkTooltips *) data;
479 GDK_THREADS_ENTER ();
481 if (tooltips->active_tips_data != NULL &&
482 GTK_WIDGET_DRAWABLE (tooltips->active_tips_data->widget))
483 gtk_tooltips_draw_tips (tooltips);
485 GDK_THREADS_LEAVE ();
491 gtk_tooltips_set_active_widget (GtkTooltips *tooltips,
494 if (tooltips->tip_window)
496 if (GTK_WIDGET_VISIBLE (tooltips->tip_window))
497 g_get_current_time (&tooltips->last_popdown);
498 gtk_widget_hide (tooltips->tip_window);
500 if (tooltips->timer_tag)
502 g_source_remove (tooltips->timer_tag);
503 tooltips->timer_tag = 0;
506 tooltips->active_tips_data = NULL;
512 for (list = tooltips->tips_data_list; list; list = list->next)
514 GtkTooltipsData *tooltipsdata;
516 tooltipsdata = list->data;
518 if (tooltipsdata->widget == widget &&
519 GTK_WIDGET_DRAWABLE (widget))
521 tooltips->active_tips_data = tooltipsdata;
528 tooltips->use_sticky_delay = FALSE;
533 gtk_tooltips_show_tip (GtkWidget *widget)
535 GtkTooltipsData *tooltipsdata;
537 tooltipsdata = gtk_tooltips_data_get (widget);
540 (!tooltipsdata->tooltips->active_tips_data ||
541 tooltipsdata->tooltips->active_tips_data->widget != widget))
543 gtk_tooltips_set_active_widget (tooltipsdata->tooltips, widget);
544 gtk_tooltips_draw_tips (tooltipsdata->tooltips);
549 gtk_tooltips_hide_tip (GtkWidget *widget)
551 GtkTooltipsData *tooltipsdata;
553 tooltipsdata = gtk_tooltips_data_get (widget);
556 (tooltipsdata->tooltips->active_tips_data &&
557 tooltipsdata->tooltips->active_tips_data->widget == widget))
558 gtk_tooltips_set_active_widget (tooltipsdata->tooltips, NULL);
562 gtk_tooltips_recently_shown (GtkTooltips *tooltips)
567 g_get_current_time (&now);
568 msec = (now.tv_sec - tooltips->last_popdown.tv_sec) * 1000 +
569 (now.tv_usec - tooltips->last_popdown.tv_usec) / 1000;
570 return (msec < STICKY_REVERT_DELAY);
574 get_keyboard_mode (GtkWidget *widget)
576 GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
577 if (GTK_IS_WINDOW (toplevel))
578 return GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (toplevel), "gtk-tooltips-keyboard-mode"));
584 start_keyboard_mode (GtkWidget *widget)
586 GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
587 if (GTK_IS_WINDOW (toplevel))
589 GtkWidget *focus = GTK_WINDOW (toplevel)->focus_widget;
591 g_object_set_data (G_OBJECT (toplevel), "gtk-tooltips-keyboard-mode", GUINT_TO_POINTER (TRUE));
594 gtk_tooltips_show_tip (focus);
599 stop_keyboard_mode (GtkWidget *widget)
601 GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
602 if (GTK_IS_WINDOW (toplevel))
604 GtkWidget *focus = GTK_WINDOW (toplevel)->focus_widget;
606 gtk_tooltips_hide_tip (focus);
608 g_object_set_data (G_OBJECT (toplevel), "gtk-tooltips-keyboard-mode", GUINT_TO_POINTER (FALSE));
613 gtk_tooltips_start_delay (GtkTooltips *tooltips,
616 GtkTooltipsData *old_tips_data;
618 old_tips_data = tooltips->active_tips_data;
619 if (tooltips->enabled &&
620 (!old_tips_data || old_tips_data->widget != widget))
624 gtk_tooltips_set_active_widget (tooltips, widget);
626 if (tooltips->use_sticky_delay &&
627 gtk_tooltips_recently_shown (tooltips))
628 delay = STICKY_DELAY;
630 delay = tooltips->delay;
631 tooltips->timer_tag = g_timeout_add (delay,
632 gtk_tooltips_timeout,
633 (gpointer) tooltips);
638 gtk_tooltips_event_handler (GtkWidget *widget,
641 GtkTooltips *tooltips;
642 GtkTooltipsData *old_tips_data;
643 GtkWidget *event_widget;
644 gboolean keyboard_mode = get_keyboard_mode (widget);
646 if ((event->type == GDK_LEAVE_NOTIFY || event->type == GDK_ENTER_NOTIFY) &&
647 event->crossing.detail == GDK_NOTIFY_INFERIOR)
650 old_tips_data = gtk_tooltips_data_get (widget);
651 tooltips = old_tips_data->tooltips;
657 case GDK_FOCUS_CHANGE:
658 if (event->focus_change.in)
659 gtk_tooltips_show_tip (widget);
661 gtk_tooltips_hide_tip (widget);
669 if (event->type != GDK_KEY_PRESS && event->type != GDK_KEY_RELEASE)
671 event_widget = gtk_get_event_widget (event);
672 if (event_widget != widget)
681 case GDK_ENTER_NOTIFY:
682 if (!(GTK_IS_MENU_ITEM (widget) && GTK_MENU_ITEM (widget)->submenu))
683 gtk_tooltips_start_delay (tooltips, widget);
686 case GDK_LEAVE_NOTIFY:
688 gboolean use_sticky_delay;
690 use_sticky_delay = tooltips->tip_window &&
691 GTK_WIDGET_VISIBLE (tooltips->tip_window);
692 gtk_tooltips_set_active_widget (tooltips, NULL);
693 tooltips->use_sticky_delay = use_sticky_delay;
697 case GDK_MOTION_NOTIFY:
698 /* Handle menu items specially ... pend popup for each motion
699 * on other widgets, we ignore motion.
701 if (GTK_IS_MENU_ITEM (widget) && !GTK_MENU_ITEM (widget)->submenu)
703 /* Completely evil hack to make sure we get the LEAVE_NOTIFY
705 GTK_PRIVATE_SET_FLAG (widget, GTK_LEAVE_PENDING);
706 gtk_tooltips_set_active_widget (tooltips, NULL);
707 gtk_tooltips_start_delay (tooltips, widget);
711 case GDK_BUTTON_PRESS:
712 case GDK_BUTTON_RELEASE:
714 case GDK_KEY_RELEASE:
715 case GDK_PROXIMITY_IN:
717 gtk_tooltips_set_active_widget (tooltips, NULL);
726 gtk_tooltips_widget_unmap (GtkWidget *widget,
729 GtkTooltipsData *tooltipsdata = (GtkTooltipsData *)data;
730 GtkTooltips *tooltips = tooltipsdata->tooltips;
732 if (tooltips->active_tips_data &&
733 (tooltips->active_tips_data->widget == widget))
734 gtk_tooltips_set_active_widget (tooltips, NULL);
738 gtk_tooltips_widget_remove (GtkWidget *widget,
741 GtkTooltipsData *tooltipsdata = (GtkTooltipsData*) data;
742 GtkTooltips *tooltips = tooltipsdata->tooltips;
744 gtk_tooltips_widget_unmap (widget, data);
745 tooltips->tips_data_list = g_list_remove (tooltips->tips_data_list,
747 gtk_tooltips_destroy_data (tooltipsdata);
751 _gtk_tooltips_toggle_keyboard_mode (GtkWidget *widget)
753 if (get_keyboard_mode (widget))
754 stop_keyboard_mode (widget);
756 start_keyboard_mode (widget);
760 * gtk_tooltips_get_info_from_tip_window:
761 * @tip_window: a #GtkWindow
762 * @tooltips: the return location for the tooltips which are displayed
763 * in @tip_window, or %NULL
764 * @current_widget: the return location for the widget whose tooltips
765 * are displayed, or %NULL
767 * Determines the tooltips and the widget they belong to from the window in
768 * which they are displayed.
770 * This function is mostly intended for use by accessibility technologies;
771 * applications should have little use for it.
773 * Return value: %TRUE if @tip_window is displaying tooltips, otherwise %FALSE.
778 gtk_tooltips_get_info_from_tip_window (GtkWindow *tip_window,
779 GtkTooltips **tooltips,
780 GtkWidget **current_widget)
782 GtkTooltips *current_tooltips;
785 g_return_val_if_fail (GTK_IS_WINDOW (tip_window), FALSE);
787 current_tooltips = g_object_get_data (G_OBJECT (tip_window), tooltips_info_key);
789 has_tips = current_tooltips != NULL;
792 *tooltips = current_tooltips;
794 *current_widget = has_tips ? current_tooltips->active_tips_data->widget : NULL;