]> Pileus Git - ~andy/gtk/blob - gtk/gtktooltips.c
Merge from themes-2. See the ChangeLog for a somewhat detailed
[~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 Library 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  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library 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 #include <stdlib.h>
20 #include <string.h>
21 #include <stdio.h>
22
23 #include "gtkmain.h"
24 #include "gtkwidget.h"
25 #include "gtkdrawwindow.h"
26 #include "gtksignal.h"
27 #include "gtkstyle.h"
28 #include "gtktooltips.h"
29
30
31 #define DEFAULT_DELAY 500           /* Default delay in ms */
32
33 static void gtk_tooltips_class_init        (GtkTooltipsClass *klass);
34 static void gtk_tooltips_init              (GtkTooltips      *tooltips);
35 static void gtk_tooltips_destroy           (GtkObject        *object);
36
37 static gint gtk_tooltips_event_handler     (GtkWidget   *widget,
38                                             GdkEvent    *event);
39 static void gtk_tooltips_widget_unmap      (GtkWidget   *widget,
40                                             gpointer     data);
41 static void gtk_tooltips_widget_remove     (GtkWidget   *widget,
42                                             gpointer     data);
43 static void gtk_tooltips_set_active_widget (GtkTooltips *tooltips,
44                                             GtkWidget   *widget);
45 static gint gtk_tooltips_timeout           (gpointer     data);
46 static gint gtk_tooltips_expose            (GtkTooltips    *tooltips, 
47                                             GdkEventExpose *event);
48
49 static void gtk_tooltips_draw_tips         (GtkTooltips *tooltips);
50
51 static GtkDataClass *parent_class;
52 static const gchar  *tooltips_data_key = "_GtkTooltipsData";
53
54 GtkType
55 gtk_tooltips_get_type (void)
56 {
57   static GtkType tooltips_type = 0;
58
59   if (!tooltips_type)
60     {
61       GtkTypeInfo tooltips_info =
62       {
63         "GtkTooltips",
64         sizeof (GtkTooltips),
65         sizeof (GtkTooltipsClass),
66         (GtkClassInitFunc) gtk_tooltips_class_init,
67         (GtkObjectInitFunc) gtk_tooltips_init,
68         /* reserved_1 */ NULL,
69         /* reserved_2 */ NULL,
70         (GtkClassInitFunc) NULL,
71       };
72
73       tooltips_type = gtk_type_unique (GTK_TYPE_DATA, &tooltips_info);
74     }
75
76   return tooltips_type;
77 }
78
79 static void
80 gtk_tooltips_class_init (GtkTooltipsClass *class)
81 {
82   GtkObjectClass *object_class;
83
84   object_class = (GtkObjectClass*) class;
85   parent_class = gtk_type_class (GTK_TYPE_DATA);
86
87   object_class->destroy = gtk_tooltips_destroy;
88 }
89
90 static void
91 gtk_tooltips_init (GtkTooltips *tooltips)
92 {
93   tooltips->tip_window = NULL;
94   tooltips->active_tips_data = NULL;
95   tooltips->tips_data_list = NULL;
96   tooltips->gc = NULL;
97   tooltips->foreground = NULL;
98   tooltips->background = NULL;
99   
100   tooltips->delay = DEFAULT_DELAY;
101   tooltips->enabled = TRUE;
102   tooltips->timer_tag = 0;
103 }
104
105 GtkTooltips *
106 gtk_tooltips_new (void)
107 {
108   return gtk_type_new (GTK_TYPE_TOOLTIPS);
109 }
110
111 static void
112 gtk_tooltips_free_string (gpointer data, gpointer user_data)
113 {
114   if (data)
115     g_free (data);
116 }
117
118 static void
119 gtk_tooltips_destroy_data (GtkTooltipsData *tooltipsdata)
120 {
121   g_free (tooltipsdata->tip_text);
122   g_free (tooltipsdata->tip_private);
123   g_list_foreach (tooltipsdata->row, gtk_tooltips_free_string, 0);
124   if (tooltipsdata->row)
125     g_list_free (tooltipsdata->row);
126   gtk_signal_disconnect_by_data (GTK_OBJECT (tooltipsdata->widget),
127                                  (gpointer) tooltipsdata);
128   gtk_object_remove_data (GTK_OBJECT (tooltipsdata->widget), tooltips_data_key);
129   gtk_widget_unref (tooltipsdata->widget);
130   g_free (tooltipsdata);
131 }
132
133 static void
134 gtk_tooltips_destroy (GtkObject *object)
135 {
136   GtkTooltips *tooltips = GTK_TOOLTIPS (object);
137   GList *current;
138   GtkTooltipsData *tooltipsdata;
139
140   g_return_if_fail (tooltips != NULL);
141
142   if (tooltips->timer_tag)
143     {
144       gtk_timeout_remove (tooltips->timer_tag);
145       tooltips->timer_tag = 0;
146     }
147
148   if (tooltips->tips_data_list != NULL)
149     {
150       current = g_list_first (tooltips->tips_data_list);
151       while (current != NULL)
152         {
153           tooltipsdata = (GtkTooltipsData*) current->data;
154           current = current->next;
155           gtk_tooltips_widget_remove (tooltipsdata->widget, tooltipsdata);
156         }
157     }
158
159   if (tooltips->tip_window)
160     gtk_widget_destroy (tooltips->tip_window);
161
162   if (tooltips->gc != NULL)
163     {
164       gdk_gc_destroy (tooltips->gc);
165       tooltips->gc = NULL;
166     }
167 }
168
169 void
170 gtk_tooltips_force_window (GtkTooltips *tooltips)
171 {
172   g_return_if_fail (tooltips != NULL);
173   g_return_if_fail (GTK_IS_TOOLTIPS (tooltips));
174
175   if (!tooltips->tip_window)
176     {
177       tooltips->tip_window = gtk_draw_window_new (GTK_WINDOW_POPUP);
178       gtk_widget_ref (tooltips->tip_window);
179       gtk_window_set_policy (GTK_WINDOW (tooltips->tip_window), FALSE, FALSE, TRUE);
180
181       gtk_signal_connect_object (GTK_OBJECT (tooltips->tip_window), 
182                                  "expose_event",
183                                  GTK_SIGNAL_FUNC (gtk_tooltips_expose), 
184                                  GTK_OBJECT (tooltips));
185
186       gtk_signal_connect (GTK_OBJECT (tooltips->tip_window),
187                           "destroy",
188                           gtk_widget_destroyed,
189                           &tooltips->tip_window);
190     }
191 }
192
193 static void
194 gtk_tooltips_layout_text (GtkTooltips *tooltips, GtkTooltipsData *data)
195 {
196   gchar *row_end, *text, *row_text, *break_pos;
197   gint i, row_width, window_width = 0;
198   size_t len;
199
200   if (!tooltips->tip_window)
201     gtk_tooltips_force_window (tooltips);
202
203   g_list_foreach (data->row, gtk_tooltips_free_string, 0);
204   if (data->row)
205     g_list_free (data->row);
206   data->row = 0;
207   data->font = tooltips->tip_window->style->font;
208   data->width = 0;
209
210   text = data->tip_text;
211   if (!text)
212     return;
213
214   while (*text)
215     {
216       row_end = strchr (text, '\n');
217       if (!row_end)
218        row_end = strchr (text, '\0');
219
220       len = row_end - text + 1;
221       row_text = g_new(gchar, len);
222       memcpy (row_text, text, len - 1);
223       row_text[len - 1] = '\0';
224
225       /* now either adjust the window's width or shorten the row until
226         it fits in the window */
227
228       while (1)
229        {
230          row_width = gdk_string_width (data->font, row_text);
231          if (!window_width)
232            {
233              /* make an initial guess at window's width: */
234
235              if (row_width > gdk_screen_width () / 4)
236                window_width = gdk_screen_width () / 4;
237              else
238                window_width = row_width;
239            }
240          if (row_width <= window_width)
241            break;
242
243          if (strchr (row_text, ' '))
244            {
245              /* the row is currently too wide, but we have blanks in
246                 the row so we can break it into smaller pieces */
247
248              gint avg_width = row_width / strlen (row_text);
249
250              i = window_width;
251              if (avg_width)
252                i /= avg_width;
253              if ((size_t) i >= len)
254                i = len - 1;
255
256              break_pos = strchr (row_text + i, ' ');
257              if (!break_pos)
258                {
259                  break_pos = row_text + i;
260                  while (*--break_pos != ' ');
261                }
262              *break_pos = '\0';
263            }
264          else
265            {
266              /* we can't break this row into any smaller pieces, so
267                 we have no choice but to widen the window: */
268
269              window_width = row_width;
270              break;
271            }
272        }
273       if (row_width > data->width)
274        data->width = row_width;
275       data->row = g_list_append (data->row, row_text);
276       text += strlen (row_text);
277       if (!*text)
278        break;
279
280       if (text[0] == '\n' && text[1])
281        /* end of paragraph and there is more text to come */
282        data->row = g_list_append (data->row, 0);
283       ++text;  /* skip blank or newline */
284     }
285   data->width += 8;    /* leave some border */
286 }
287
288 void
289 gtk_tooltips_enable (GtkTooltips *tooltips)
290 {
291   g_return_if_fail (tooltips != NULL);
292
293   tooltips->enabled = TRUE;
294 }
295
296 void
297 gtk_tooltips_disable (GtkTooltips *tooltips)
298 {
299   g_return_if_fail (tooltips != NULL);
300
301   gtk_tooltips_set_active_widget (tooltips, NULL);
302
303   tooltips->enabled = FALSE;
304 }
305
306 void
307 gtk_tooltips_set_delay (GtkTooltips *tooltips,
308                         guint         delay)
309 {
310   g_return_if_fail (tooltips != NULL);
311
312   tooltips->delay = delay;
313 }
314
315 GtkTooltipsData*
316 gtk_tooltips_data_get (GtkWidget       *widget)
317 {
318   g_return_val_if_fail (widget != NULL, NULL);
319
320   return gtk_object_get_data ((GtkObject*) widget, tooltips_data_key);
321 }
322
323 void
324 gtk_tooltips_set_tip (GtkTooltips *tooltips,
325                       GtkWidget   *widget,
326                       const gchar *tip_text,
327                       const gchar *tip_private)
328 {
329   GtkTooltipsData *tooltipsdata;
330
331   g_return_if_fail (tooltips != NULL);
332   g_return_if_fail (GTK_IS_TOOLTIPS (tooltips));
333   g_return_if_fail (widget != NULL);
334
335   tooltipsdata = gtk_tooltips_data_get (widget);
336   if (tooltipsdata)
337     gtk_tooltips_widget_remove (tooltipsdata->widget, tooltipsdata);
338
339   if (!tip_text)
340     return;
341
342   tooltipsdata = g_new0 (GtkTooltipsData, 1);
343
344   if (tooltipsdata != NULL)
345     {
346       tooltipsdata->tooltips = tooltips;
347       tooltipsdata->widget = widget;
348       gtk_widget_ref (widget);
349
350       tooltipsdata->tip_text = g_strdup (tip_text);
351       tooltipsdata->tip_private = g_strdup (tip_private);
352
353       gtk_tooltips_layout_text (tooltips, tooltipsdata);
354       tooltips->tips_data_list = g_list_append (tooltips->tips_data_list,
355                                              tooltipsdata);
356       gtk_signal_connect_after(GTK_OBJECT (widget), "event",
357                                (GtkSignalFunc) gtk_tooltips_event_handler,
358                                (gpointer) tooltipsdata);
359
360       gtk_object_set_data (GTK_OBJECT (widget), tooltips_data_key,
361                            (gpointer) tooltipsdata);
362
363       gtk_signal_connect (GTK_OBJECT (widget), "unmap",
364                           (GtkSignalFunc) gtk_tooltips_widget_unmap,
365                           (gpointer) tooltipsdata);
366
367       gtk_signal_connect (GTK_OBJECT (widget), "unrealize",
368                           (GtkSignalFunc) gtk_tooltips_widget_unmap,
369                           (gpointer) tooltipsdata);
370
371       gtk_signal_connect (GTK_OBJECT (widget), "destroy",
372                           (GtkSignalFunc) gtk_tooltips_widget_remove,
373                           (gpointer) tooltipsdata);
374     }
375 }
376
377 void
378 gtk_tooltips_set_colors (GtkTooltips *tooltips,
379                          GdkColor    *background,
380                          GdkColor    *foreground)
381 {
382   g_return_if_fail (tooltips != NULL);
383
384   if (background != NULL)
385     tooltips->foreground = foreground;
386   if (foreground != NULL)
387     tooltips->background = background;
388 }
389
390 static gint
391 gtk_tooltips_expose (GtkTooltips *tooltips, GdkEventExpose *event)
392 {
393   GtkStyle *style;
394   gint y, baseline_skip, gap;
395   GtkTooltipsData *data;
396   GList *el;
397
398   style = tooltips->tip_window->style;
399
400   gap = (style->font->ascent + style->font->descent) / 4;
401   if (gap < 2)
402     gap = 2;
403   baseline_skip = style->font->ascent + style->font->descent + gap;
404
405   data = tooltips->active_tips_data;
406   if (!data)
407     return FALSE;
408
409   gtk_paint_flat_box(style, tooltips->tip_window->window,
410                      GTK_STATE_NORMAL, GTK_SHADOW_OUT, 
411                      NULL, GTK_WIDGET(tooltips->tip_window), "tooltip",
412                      0, 0, -1, -1);
413
414   y = style->font->ascent + 4;
415   
416   for (el = data->row; el; el = el->next)
417     {
418       if (el->data)
419         {
420           gtk_paint_string (style, tooltips->tip_window->window, 
421                             GTK_STATE_NORMAL, 
422                             NULL, GTK_WIDGET(tooltips->tip_window), "tooltip",
423                             4, y, el->data);
424           y += baseline_skip;
425         }
426       else
427         y += baseline_skip / 2;
428     }
429
430   return FALSE;
431 }
432
433 static void
434 gtk_tooltips_draw_tips (GtkTooltips * tooltips)
435 {
436   GtkWidget *widget;
437   GtkStyle *style;
438   gint gap, x, y, w, h, scr_w, scr_h, baseline_skip;
439   GtkTooltipsData *data;
440   GList *el;
441
442   if (!tooltips->tip_window)
443     gtk_tooltips_force_window (tooltips);
444   else if (GTK_WIDGET_VISIBLE (tooltips->tip_window))
445     gtk_widget_hide (tooltips->tip_window);
446
447   style = tooltips->tip_window->style;
448   
449   widget = tooltips->active_tips_data->widget;
450
451   scr_w = gdk_screen_width ();
452   scr_h = gdk_screen_height ();
453
454   data = tooltips->active_tips_data;
455   if (data->font != style->font)
456     gtk_tooltips_layout_text (tooltips, data);
457
458   gap = (style->font->ascent + style->font->descent) / 4;
459   if (gap < 2)
460     gap = 2;
461   baseline_skip = style->font->ascent + style->font->descent + gap;
462
463   w = data->width;
464   h = 8 - gap;
465   for (el = data->row; el; el = el->next)
466     if (el->data)
467       h += baseline_skip;
468     else
469       h += baseline_skip / 2;
470   if (h < 8)
471     h = 8;
472
473   gdk_window_get_pointer (NULL, &x, NULL, NULL);
474   gdk_window_get_origin (widget->window, NULL, &y);
475
476   x -= ((w >> 1) + 4);
477
478   if ((x + w) > scr_w)
479     x -= (x + w) - scr_w;
480   else if (x < 0)
481     x = 0;
482
483   if ((y + h + widget->allocation.height + 4) > scr_h)
484     y = y - h - 4;
485   else
486     y = y + widget->allocation.height + 4;
487
488   gtk_widget_set_usize (tooltips->tip_window, w, h);
489   gtk_widget_popup (tooltips->tip_window, x, y);
490 }
491
492 static gint
493 gtk_tooltips_timeout (gpointer data)
494 {
495   GtkTooltips *tooltips = (GtkTooltips *) data;
496
497   if (tooltips->active_tips_data != NULL &&
498       GTK_WIDGET_DRAWABLE (tooltips->active_tips_data->widget))
499     gtk_tooltips_draw_tips (tooltips);
500
501   return FALSE;
502 }
503
504 static void
505 gtk_tooltips_set_active_widget (GtkTooltips *tooltips,
506                                 GtkWidget   *widget)
507 {
508   if (tooltips->tip_window)
509     gtk_widget_hide (tooltips->tip_window);
510   if (tooltips->timer_tag)
511     {
512       gtk_timeout_remove (tooltips->timer_tag);
513       tooltips->timer_tag = 0;
514     }
515   
516   tooltips->active_tips_data = NULL;
517   
518   if (widget)
519     {
520       GList *list;
521       
522       for (list = tooltips->tips_data_list; list; list = list->next)
523         {
524           GtkTooltipsData *tooltipsdata;
525           
526           tooltipsdata = list->data;
527           
528           if (tooltipsdata->widget == widget &&
529               GTK_WIDGET_DRAWABLE (widget))
530             {
531               tooltips->active_tips_data = tooltipsdata;
532               break;
533             }
534         }
535     }
536 }
537
538 static gint
539 gtk_tooltips_event_handler (GtkWidget *widget,
540                             GdkEvent  *event)
541 {
542   GtkTooltips *tooltips;
543   GtkTooltipsData *old_tips_data;
544   GtkWidget *event_widget;
545
546   if ((event->type == GDK_LEAVE_NOTIFY || event->type == GDK_ENTER_NOTIFY) &&
547       event->crossing.detail == GDK_NOTIFY_INFERIOR)
548     return FALSE;
549
550   event_widget = gtk_get_event_widget (event);
551   if (event_widget != widget)
552     return FALSE;
553   
554   old_tips_data = gtk_tooltips_data_get (widget);
555   tooltips = old_tips_data->tooltips;
556
557   switch (event->type)
558     {
559     case GDK_MOTION_NOTIFY:
560     case GDK_EXPOSE:
561       /* do nothing */
562       break;
563       
564     case GDK_ENTER_NOTIFY:
565       old_tips_data = tooltips->active_tips_data;
566       if (tooltips->enabled &&
567           (!old_tips_data || old_tips_data->widget != widget))
568         {
569           gtk_tooltips_set_active_widget (tooltips, widget);
570           
571           tooltips->timer_tag = gtk_timeout_add (tooltips->delay,
572                                                  gtk_tooltips_timeout,
573                                                  (gpointer) tooltips);
574         }
575       break;
576
577     default:
578       gtk_tooltips_set_active_widget (tooltips, NULL);
579       break;
580     }
581
582   return FALSE;
583 }
584
585 static void
586 gtk_tooltips_widget_unmap (GtkWidget *widget,
587                            gpointer   data)
588 {
589   GtkTooltipsData *tooltipsdata = (GtkTooltipsData *)data;
590   GtkTooltips *tooltips = tooltipsdata->tooltips;
591   
592   if (tooltips->active_tips_data &&
593       (tooltips->active_tips_data->widget == widget))
594     gtk_tooltips_set_active_widget (tooltips, NULL);
595 }
596
597 static void
598 gtk_tooltips_widget_remove (GtkWidget *widget,
599                             gpointer   data)
600 {
601   GtkTooltipsData *tooltipsdata = (GtkTooltipsData*) data;
602   GtkTooltips *tooltips = tooltipsdata->tooltips;
603
604   gtk_tooltips_widget_unmap (widget, data);
605   tooltips->tips_data_list = g_list_remove (tooltips->tips_data_list,
606                                             tooltipsdata);
607   gtk_tooltips_destroy_data (tooltipsdata);
608 }