]> Pileus Git - ~andy/gtk/blob - gtk/gtktooltips.c
cbe423751ef0d94584f6f09e223f7f5007821d73
[~andy/gtk] / gtk / gtktooltips.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
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, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
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/. 
25  */
26
27 #include <stdlib.h>
28 #include <string.h>
29 #include <stdio.h>
30
31 #include "gtklabel.h"
32 #include "gtkmain.h"
33 #include "gtkwidget.h"
34 #include "gtkwindow.h"
35 #include "gtksignal.h"
36 #include "gtkstyle.h"
37 #include "gtktooltips.h"
38
39
40 #define DEFAULT_DELAY 500           /* Default delay in ms */
41 #define STICKY_DELAY 0              /* Delay before popping up next tip
42                                      * if we're sticky
43                                      */
44 #define STICKY_REVERT_DELAY 1000    /* Delay before sticky tooltips revert
45                                      * to normal
46                                      */
47
48 static void gtk_tooltips_class_init        (GtkTooltipsClass *klass);
49 static void gtk_tooltips_init              (GtkTooltips      *tooltips);
50 static void gtk_tooltips_destroy           (GtkObject        *object);
51
52 static gint gtk_tooltips_event_handler     (GtkWidget   *widget,
53                                             GdkEvent    *event);
54 static void gtk_tooltips_widget_unmap      (GtkWidget   *widget,
55                                             gpointer     data);
56 static void gtk_tooltips_widget_remove     (GtkWidget   *widget,
57                                             gpointer     data);
58 static void gtk_tooltips_set_active_widget (GtkTooltips *tooltips,
59                                             GtkWidget   *widget);
60 static gint gtk_tooltips_timeout           (gpointer     data);
61
62 static gint gtk_tooltips_paint_window      (GtkTooltips *tooltips);
63 static void gtk_tooltips_draw_tips         (GtkTooltips *tooltips);
64
65 static GtkObjectClass *parent_class;
66 static const gchar  *tooltips_data_key = "_GtkTooltipsData";
67
68 GtkType
69 gtk_tooltips_get_type (void)
70 {
71   static GtkType tooltips_type = 0;
72
73   if (!tooltips_type)
74     {
75       static const GtkTypeInfo tooltips_info =
76       {
77         "GtkTooltips",
78         sizeof (GtkTooltips),
79         sizeof (GtkTooltipsClass),
80         (GtkClassInitFunc) gtk_tooltips_class_init,
81         (GtkObjectInitFunc) gtk_tooltips_init,
82         /* reserved_1 */ NULL,
83         /* reserved_2 */ NULL,
84         (GtkClassInitFunc) NULL,
85       };
86
87       tooltips_type = gtk_type_unique (GTK_TYPE_OBJECT, &tooltips_info);
88     }
89
90   return tooltips_type;
91 }
92
93 static void
94 gtk_tooltips_class_init (GtkTooltipsClass *class)
95 {
96   GtkObjectClass *object_class;
97
98   object_class = (GtkObjectClass*) class;
99   parent_class = gtk_type_class (GTK_TYPE_OBJECT);
100
101   object_class->destroy = gtk_tooltips_destroy;
102 }
103
104 static void
105 gtk_tooltips_init (GtkTooltips *tooltips)
106 {
107   tooltips->tip_window = NULL;
108   tooltips->active_tips_data = NULL;
109   tooltips->tips_data_list = NULL;
110   
111   tooltips->delay = DEFAULT_DELAY;
112   tooltips->enabled = TRUE;
113   tooltips->timer_tag = 0;
114   tooltips->use_sticky_delay = FALSE;
115   tooltips->last_popdown.tv_sec = -1;
116   tooltips->last_popdown.tv_usec = -1;
117 }
118
119 GtkTooltips *
120 gtk_tooltips_new (void)
121 {
122   return gtk_type_new (GTK_TYPE_TOOLTIPS);
123 }
124
125 static void
126 gtk_tooltips_destroy_data (GtkTooltipsData *tooltipsdata)
127 {
128   g_free (tooltipsdata->tip_text);
129   g_free (tooltipsdata->tip_private);
130   gtk_signal_disconnect_by_data (GTK_OBJECT (tooltipsdata->widget),
131                                  (gpointer) tooltipsdata);
132   gtk_object_remove_data (GTK_OBJECT (tooltipsdata->widget), tooltips_data_key);
133   gtk_widget_unref (tooltipsdata->widget);
134   g_free (tooltipsdata);
135 }
136
137 static void
138 gtk_tooltips_destroy (GtkObject *object)
139 {
140   GtkTooltips *tooltips = GTK_TOOLTIPS (object);
141   GList *current;
142   GtkTooltipsData *tooltipsdata;
143
144   g_return_if_fail (tooltips != NULL);
145
146   if (tooltips->timer_tag)
147     {
148       gtk_timeout_remove (tooltips->timer_tag);
149       tooltips->timer_tag = 0;
150     }
151
152   if (tooltips->tips_data_list != NULL)
153     {
154       current = g_list_first (tooltips->tips_data_list);
155       while (current != NULL)
156         {
157           tooltipsdata = (GtkTooltipsData*) current->data;
158           current = current->next;
159           gtk_tooltips_widget_remove (tooltipsdata->widget, tooltipsdata);
160         }
161     }
162
163   if (tooltips->tip_window)
164     gtk_widget_destroy (tooltips->tip_window);
165 }
166
167 void
168 gtk_tooltips_force_window (GtkTooltips *tooltips)
169 {
170   g_return_if_fail (tooltips != NULL);
171   g_return_if_fail (GTK_IS_TOOLTIPS (tooltips));
172
173   if (!tooltips->tip_window)
174     {
175       tooltips->tip_window = gtk_window_new (GTK_WINDOW_POPUP);
176       gtk_widget_set_app_paintable (tooltips->tip_window, TRUE);
177       gtk_window_set_policy (GTK_WINDOW (tooltips->tip_window), FALSE, FALSE, TRUE);
178       gtk_widget_set_name (tooltips->tip_window, "gtk-tooltips");
179       gtk_container_set_border_width (GTK_CONTAINER (tooltips->tip_window), 4);
180
181       gtk_signal_connect_object (GTK_OBJECT (tooltips->tip_window), 
182                                  "expose_event",
183                                  GTK_SIGNAL_FUNC (gtk_tooltips_paint_window), 
184                                  GTK_OBJECT (tooltips));
185
186       tooltips->tip_label = gtk_label_new (NULL);
187       gtk_label_set_line_wrap (GTK_LABEL (tooltips->tip_label), TRUE);
188       gtk_misc_set_alignment (GTK_MISC (tooltips->tip_label), 0.5, 0.5);
189       gtk_widget_show (tooltips->tip_label);
190       
191       gtk_container_add (GTK_CONTAINER (tooltips->tip_window), tooltips->tip_label);
192
193       gtk_signal_connect (GTK_OBJECT (tooltips->tip_window),
194                           "destroy",
195                           GTK_SIGNAL_FUNC (gtk_widget_destroyed),
196                           &tooltips->tip_window);
197     }
198 }
199
200 void
201 gtk_tooltips_enable (GtkTooltips *tooltips)
202 {
203   g_return_if_fail (tooltips != NULL);
204
205   tooltips->enabled = TRUE;
206 }
207
208 void
209 gtk_tooltips_disable (GtkTooltips *tooltips)
210 {
211   g_return_if_fail (tooltips != NULL);
212
213   gtk_tooltips_set_active_widget (tooltips, NULL);
214
215   tooltips->enabled = FALSE;
216 }
217
218 void
219 gtk_tooltips_set_delay (GtkTooltips *tooltips,
220                         guint         delay)
221 {
222   g_return_if_fail (tooltips != NULL);
223
224   tooltips->delay = delay;
225 }
226
227 GtkTooltipsData*
228 gtk_tooltips_data_get (GtkWidget       *widget)
229 {
230   g_return_val_if_fail (widget != NULL, NULL);
231
232   return gtk_object_get_data ((GtkObject*) widget, tooltips_data_key);
233 }
234
235 void
236 gtk_tooltips_set_tip (GtkTooltips *tooltips,
237                       GtkWidget   *widget,
238                       const gchar *tip_text,
239                       const gchar *tip_private)
240 {
241   GtkTooltipsData *tooltipsdata;
242
243   g_return_if_fail (tooltips != NULL);
244   g_return_if_fail (GTK_IS_TOOLTIPS (tooltips));
245   g_return_if_fail (widget != NULL);
246
247   tooltipsdata = gtk_tooltips_data_get (widget);
248   if (tooltipsdata)
249     gtk_tooltips_widget_remove (tooltipsdata->widget, tooltipsdata);
250
251   if (!tip_text)
252     return;
253
254   tooltipsdata = g_new0 (GtkTooltipsData, 1);
255
256   if (tooltipsdata != NULL)
257     {
258       tooltipsdata->tooltips = tooltips;
259       tooltipsdata->widget = widget;
260       gtk_widget_ref (widget);
261
262       tooltipsdata->tip_text = g_strdup (tip_text);
263       tooltipsdata->tip_private = g_strdup (tip_private);
264
265       tooltips->tips_data_list = g_list_append (tooltips->tips_data_list,
266                                              tooltipsdata);
267       gtk_signal_connect_after(GTK_OBJECT (widget), "event",
268                                (GtkSignalFunc) gtk_tooltips_event_handler,
269                                (gpointer) tooltipsdata);
270
271       gtk_object_set_data (GTK_OBJECT (widget), tooltips_data_key,
272                            (gpointer) tooltipsdata);
273
274       gtk_signal_connect (GTK_OBJECT (widget), "unmap",
275                           (GtkSignalFunc) gtk_tooltips_widget_unmap,
276                           (gpointer) tooltipsdata);
277
278       gtk_signal_connect (GTK_OBJECT (widget), "unrealize",
279                           (GtkSignalFunc) gtk_tooltips_widget_unmap,
280                           (gpointer) tooltipsdata);
281
282       gtk_signal_connect (GTK_OBJECT (widget), "destroy",
283                           (GtkSignalFunc) gtk_tooltips_widget_remove,
284                           (gpointer) tooltipsdata);
285     }
286 }
287
288 static gint
289 gtk_tooltips_paint_window (GtkTooltips *tooltips)
290 {
291   gtk_paint_flat_box (tooltips->tip_window->style, tooltips->tip_window->window,
292                       GTK_STATE_NORMAL, GTK_SHADOW_OUT, 
293                       NULL, GTK_WIDGET(tooltips->tip_window), "tooltip",
294                       0, 0, -1, -1);
295
296   return FALSE;
297 }
298
299 static void
300 gtk_tooltips_draw_tips (GtkTooltips * tooltips)
301 {
302   GtkRequisition requisition;
303   GtkWidget *widget;
304   GtkStyle *style;
305   gint x, y, w, h, scr_w, scr_h;
306   GtkTooltipsData *data;
307
308   if (!tooltips->tip_window)
309     gtk_tooltips_force_window (tooltips);
310   else if (GTK_WIDGET_VISIBLE (tooltips->tip_window))
311     {
312       gtk_widget_hide (tooltips->tip_window);
313       g_get_current_time (&tooltips->last_popdown);
314     }
315
316   gtk_widget_ensure_style (tooltips->tip_window);
317   style = tooltips->tip_window->style;
318   
319   widget = tooltips->active_tips_data->widget;
320
321   scr_w = gdk_screen_width ();
322   scr_h = gdk_screen_height ();
323
324   data = tooltips->active_tips_data;
325
326   gtk_label_set_text (GTK_LABEL (tooltips->tip_label), data->tip_text);
327
328   gtk_widget_size_request (tooltips->tip_window, &requisition);
329   w = requisition.width;
330   h = requisition.height;
331
332   gdk_window_get_pointer (NULL, &x, NULL, NULL);
333   gdk_window_get_origin (widget->window, NULL, &y);
334   if (GTK_WIDGET_NO_WINDOW (widget))
335     y += widget->allocation.y;
336
337   x -= (w / 2 + 4);
338
339   if ((x + w) > scr_w)
340     x -= (x + w) - scr_w;
341   else if (x < 0)
342     x = 0;
343
344   if ((y + h + widget->allocation.height + 4) > scr_h)
345     y = y - h - 4;
346   else
347     y = y + widget->allocation.height + 4;
348
349   gtk_widget_set_uposition (tooltips->tip_window, x, y);
350   gtk_widget_show (tooltips->tip_window);
351 }
352
353 static gint
354 gtk_tooltips_timeout (gpointer data)
355 {
356   GtkTooltips *tooltips = (GtkTooltips *) data;
357
358   GDK_THREADS_ENTER ();
359   
360   if (tooltips->active_tips_data != NULL &&
361       GTK_WIDGET_DRAWABLE (tooltips->active_tips_data->widget))
362     gtk_tooltips_draw_tips (tooltips);
363
364   GDK_THREADS_LEAVE ();
365
366   return FALSE;
367 }
368
369 static void
370 gtk_tooltips_set_active_widget (GtkTooltips *tooltips,
371                                 GtkWidget   *widget)
372 {
373   if (tooltips->tip_window)
374     {
375       if (GTK_WIDGET_VISIBLE (tooltips->tip_window))
376         g_get_current_time (&tooltips->last_popdown);
377       gtk_widget_hide (tooltips->tip_window);
378     }
379   if (tooltips->timer_tag)
380     {
381       gtk_timeout_remove (tooltips->timer_tag);
382       tooltips->timer_tag = 0;
383     }
384   
385   tooltips->active_tips_data = NULL;
386   
387   if (widget)
388     {
389       GList *list;
390       
391       for (list = tooltips->tips_data_list; list; list = list->next)
392         {
393           GtkTooltipsData *tooltipsdata;
394           
395           tooltipsdata = list->data;
396           
397           if (tooltipsdata->widget == widget &&
398               GTK_WIDGET_DRAWABLE (widget))
399             {
400               tooltips->active_tips_data = tooltipsdata;
401               break;
402             }
403         }
404     }
405   else
406     {
407       tooltips->use_sticky_delay = FALSE;
408     }
409 }
410
411 static gboolean
412 gtk_tooltips_recently_shown (GtkTooltips *tooltips)
413 {
414   GTimeVal now;
415   glong msec;
416   
417   g_get_current_time (&now);
418   msec = (now.tv_sec  - tooltips->last_popdown.tv_sec) * 1000 +
419           (now.tv_usec - tooltips->last_popdown.tv_usec) / 1000;
420   return (msec < STICKY_REVERT_DELAY);
421 }
422
423 static gint
424 gtk_tooltips_event_handler (GtkWidget *widget,
425                             GdkEvent  *event)
426 {
427   GtkTooltips *tooltips;
428   GtkTooltipsData *old_tips_data;
429   GtkWidget *event_widget;
430
431   if ((event->type == GDK_LEAVE_NOTIFY || event->type == GDK_ENTER_NOTIFY) &&
432       event->crossing.detail == GDK_NOTIFY_INFERIOR)
433     return FALSE;
434
435   event_widget = gtk_get_event_widget (event);
436   if (event_widget != widget)
437     return FALSE;
438   
439   old_tips_data = gtk_tooltips_data_get (widget);
440   tooltips = old_tips_data->tooltips;
441
442   switch (event->type)
443     {
444     case GDK_MOTION_NOTIFY:
445     case GDK_EXPOSE:
446       /* do nothing */
447       break;
448       
449     case GDK_ENTER_NOTIFY:
450       old_tips_data = tooltips->active_tips_data;
451       if (tooltips->enabled &&
452           (!old_tips_data || old_tips_data->widget != widget))
453         {
454           guint delay;
455           
456           gtk_tooltips_set_active_widget (tooltips, widget);
457           
458           if (tooltips->use_sticky_delay  &&
459               gtk_tooltips_recently_shown (tooltips))
460             delay = STICKY_DELAY;
461           else
462             delay = tooltips->delay;
463           tooltips->timer_tag = gtk_timeout_add (delay,
464                                                  gtk_tooltips_timeout,
465                                                  (gpointer) tooltips);
466         }
467       break;
468
469     case GDK_LEAVE_NOTIFY:
470       {
471         gboolean use_sticky_delay;
472
473         use_sticky_delay = tooltips->tip_window &&
474                 GTK_WIDGET_VISIBLE (tooltips->tip_window);
475         gtk_tooltips_set_active_widget (tooltips, NULL);
476         tooltips->use_sticky_delay = use_sticky_delay;
477       }
478       break;
479       
480     default:
481       gtk_tooltips_set_active_widget (tooltips, NULL);
482       return FALSE;
483       break;
484     }
485
486   return FALSE;
487 }
488
489 static void
490 gtk_tooltips_widget_unmap (GtkWidget *widget,
491                            gpointer   data)
492 {
493   GtkTooltipsData *tooltipsdata = (GtkTooltipsData *)data;
494   GtkTooltips *tooltips = tooltipsdata->tooltips;
495   
496   if (tooltips->active_tips_data &&
497       (tooltips->active_tips_data->widget == widget))
498     gtk_tooltips_set_active_widget (tooltips, NULL);
499 }
500
501 static void
502 gtk_tooltips_widget_remove (GtkWidget *widget,
503                             gpointer   data)
504 {
505   GtkTooltipsData *tooltipsdata = (GtkTooltipsData*) data;
506   GtkTooltips *tooltips = tooltipsdata->tooltips;
507
508   gtk_tooltips_widget_unmap (widget, data);
509   tooltips->tips_data_list = g_list_remove (tooltips->tips_data_list,
510                                             tooltipsdata);
511   gtk_tooltips_destroy_data (tooltipsdata);
512 }
513
514 void
515 _gtk_tooltips_show_tip (GtkWidget *widget)
516 {
517   /* Showing the tip from the keyboard */
518
519   /* FIXME this function is completely broken right now,
520    * popdown doesn't occur when it should.
521    */
522   
523   GtkTooltipsData *tooltipsdata;
524
525   tooltipsdata = gtk_tooltips_data_get (widget);
526
527   if (tooltipsdata == NULL)
528     return;
529
530   gtk_tooltips_set_active_widget (tooltipsdata->tooltips,
531                                   widget);
532
533   gtk_tooltips_timeout (tooltipsdata->tooltips);
534 }