source: trunk/third/gtk/gtk/gtktooltips.c @ 14482

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