Tip.c revision 775e7de9
1/*
2 * Copyright (c) 1999 by The XFree86 Project, Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
17 * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
19 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 *
22 * Except as contained in this notice, the name of the XFree86 Project shall
23 * not be used in advertising or otherwise to promote the sale, use or other
24 * dealings in this Software without prior written authorization from the
25 * XFree86 Project.
26 *
27 * Author: Paulo César Pereira de Andrade
28 */
29
30/* $XFree86: xc/lib/Xaw/Tip.c,v 1.4 1999/07/11 08:49:16 dawes Exp $ */
31
32#ifdef HAVE_CONFIG_H
33#include <config.h>
34#endif
35#include <X11/IntrinsicP.h>
36#include <X11/StringDefs.h>
37#include <X11/Xos.h>
38#include <X11/Xaw/TipP.h>
39#include <X11/Xaw/XawInit.h>
40#include <X11/Xmu/Converters.h>
41#include "Private.h"
42
43#define	TIP_EVENT_MASK (ButtonPressMask	  |	\
44			ButtonReleaseMask |	\
45			PointerMotionMask |	\
46			ButtonMotionMask  |	\
47			KeyPressMask	  |	\
48			KeyReleaseMask	  |	\
49			EnterWindowMask	  |	\
50			LeaveWindowMask)
51
52/*
53 * Types
54 */
55typedef struct _XawTipInfo {
56    Screen *screen;
57    TipWidget tip;
58    Widget widget;
59    Bool mapped;
60    struct _XawTipInfo *next;
61} XawTipInfo;
62
63/*
64 * Class Methods
65 */
66static void XawTipClassInitialize(void);
67static void XawTipInitialize(Widget, Widget, ArgList, Cardinal*);
68static void XawTipDestroy(Widget);
69static void XawTipExpose(Widget, XEvent*, Region);
70static void XawTipRealize(Widget, Mask*, XSetWindowAttributes*);
71static Boolean XawTipSetValues(Widget, Widget, Widget, ArgList, Cardinal*);
72
73/*
74 * Prototypes
75 */
76static void TipEventHandler(Widget, XtPointer, XEvent*, Boolean*);
77static void TipShellEventHandler(Widget, XtPointer, XEvent*, Boolean*);
78static XawTipInfo *CreateTipInfo(Widget);
79static XawTipInfo *FindTipInfo(Widget);
80static void ResetTip(XawTipInfo*, Bool);
81static void TipTimeoutCallback(XtPointer, XtIntervalId*);
82static void TipLayout(XawTipInfo*);
83static void TipPosition(XawTipInfo*);
84
85/*
86 * Initialization
87 */
88#define offset(field) XtOffsetOf(TipRec, tip.field)
89static XtResource resources[] = {
90  {
91    XtNforeground,
92    XtCForeground,
93    XtRPixel,
94    sizeof(Pixel),
95    offset(foreground),
96    XtRString,
97    XtDefaultForeground,
98  },
99  {
100    XtNfont,
101    XtCFont,
102    XtRFontStruct,
103    sizeof(XFontStruct*),
104    offset(font),
105    XtRString,
106    XtDefaultFont
107  },
108  {
109    XtNfontSet,
110    XtCFontSet,
111    XtRFontSet,
112    sizeof(XFontSet),
113    offset(fontset),
114    XtRString,
115    XtDefaultFontSet
116  },
117  {
118    XtNtopMargin,
119    XtCVerticalMargins,
120    XtRDimension,
121    sizeof(Dimension),
122    offset(top_margin),
123    XtRImmediate,
124    (XtPointer)2
125  },
126  {
127    XtNbottomMargin,
128    XtCVerticalMargins,
129    XtRDimension,
130    sizeof(Dimension),
131    offset(bottom_margin),
132    XtRImmediate,
133    (XtPointer)2
134  },
135  {
136    XtNleftMargin,
137    XtCHorizontalMargins,
138    XtRDimension,
139    sizeof(Dimension),
140    offset(left_margin),
141    XtRImmediate,
142    (XtPointer)6
143  },
144  {
145    XtNrightMargin,
146    XtCHorizontalMargins,
147    XtRDimension,
148    sizeof(Dimension),
149    offset(right_margin),
150    XtRImmediate,
151    (XtPointer)6
152  },
153  {
154    XtNbackingStore,
155    XtCBackingStore,
156    XtRBackingStore,
157    sizeof(int),
158    offset(backing_store),
159    XtRImmediate,
160    (XtPointer)(Always + WhenMapped + NotUseful)
161  },
162  {
163    XtNtimeout,
164    XtCTimeout,
165    XtRInt,
166    sizeof(int),
167    offset(timeout),
168    XtRImmediate,
169    (XtPointer)500
170  },
171  {
172    XawNdisplayList,
173    XawCDisplayList,
174    XawRDisplayList,
175    sizeof(XawDisplayList*),
176    offset(display_list),
177    XtRImmediate,
178    NULL
179  },
180};
181#undef offset
182
183TipClassRec tipClassRec = {
184  /* core */
185  {
186    (WidgetClass)&widgetClassRec,	/* superclass */
187    "Tip",				/* class_name */
188    sizeof(TipRec),			/* widget_size */
189    XawTipClassInitialize,		/* class_initialize */
190    NULL,				/* class_part_initialize */
191    False,				/* class_inited */
192    XawTipInitialize,			/* initialize */
193    NULL,				/* initialize_hook */
194    XawTipRealize,			/* realize */
195    NULL,				/* actions */
196    0,					/* num_actions */
197    resources,				/* resources */
198    XtNumber(resources),		/* num_resources */
199    NULLQUARK,				/* xrm_class */
200    True,				/* compress_motion */
201    True,				/* compress_exposure */
202    True,				/* compress_enterleave */
203    False,				/* visible_interest */
204    XawTipDestroy,			/* destroy */
205    NULL,				/* resize */
206    XawTipExpose,			/* expose */
207    XawTipSetValues,			/* set_values */
208    NULL,				/* set_values_hook */
209    XtInheritSetValuesAlmost,		/* set_values_almost */
210    NULL,				/* get_values_hook */
211    NULL,				/* accept_focus */
212    XtVersion,				/* version */
213    NULL,				/* callback_private */
214    NULL,				/* tm_table */
215    XtInheritQueryGeometry,		/* query_geometry */
216    XtInheritDisplayAccelerator,	/* display_accelerator */
217    NULL,				/* extension */
218  },
219  /* tip */
220  {
221    NULL,				/* extension */
222  },
223};
224
225WidgetClass tipWidgetClass = (WidgetClass)&tipClassRec;
226
227static XawTipInfo *first_tip;
228
229/*
230 * Implementation
231 */
232static void
233XawTipClassInitialize(void)
234{
235    XawInitializeWidgetSet();
236    XtAddConverter(XtRString, XtRBackingStore, XmuCvtStringToBackingStore,
237		   NULL, 0);
238    XtSetTypeConverter(XtRBackingStore, XtRString, XmuCvtBackingStoreToString,
239		       NULL, 0, XtCacheNone, NULL);
240}
241
242/*ARGSUSED*/
243static void
244XawTipInitialize(Widget req, Widget w, ArgList args, Cardinal *num_args)
245{
246    TipWidget tip = (TipWidget)w;
247    XGCValues values;
248
249    if (!tip->tip.font) XtError("Aborting: no font found\n");
250    if (tip->tip.international && !tip->tip.fontset)
251	XtError("Aborting: no fontset found\n");
252
253    tip->tip.timer = 0;
254
255    values.foreground = tip->tip.foreground;
256    values.background = tip->core.background_pixel;
257    values.font = tip->tip.font->fid;
258    values.graphics_exposures = False;
259
260    tip->tip.gc = XtAllocateGC(w, 0, GCForeground | GCBackground | GCFont |
261			       GCGraphicsExposures, &values, GCFont, 0);
262}
263
264static void
265XawTipDestroy(Widget w)
266{
267    XawTipInfo *info = FindTipInfo(w);
268    TipWidget tip = (TipWidget)w;
269
270    if (tip->tip.timer)
271	XtRemoveTimeOut(tip->tip.timer);
272
273    XtReleaseGC(w, tip->tip.gc);
274
275    XtRemoveEventHandler(XtParent(w), KeyPressMask, False, TipShellEventHandler,
276			 (XtPointer)NULL);
277    if (info == first_tip)
278	first_tip = first_tip->next;
279    else {
280	XawTipInfo *p = first_tip;
281
282	while (p && p->next != info)
283	    p = p->next;
284	if (p)
285	    p->next = info->next;
286    }
287    XtFree((char*)info);
288}
289
290static void
291XawTipRealize(Widget w, Mask *mask, XSetWindowAttributes *attr)
292{
293    TipWidget tip = (TipWidget)w;
294
295    if (tip->tip.backing_store == Always ||
296	tip->tip.backing_store == NotUseful ||
297	tip->tip.backing_store == WhenMapped) {
298	*mask |= CWBackingStore;
299	attr->backing_store = tip->tip.backing_store;
300    }
301    else
302	*mask &= ~CWBackingStore;
303    *mask |= CWOverrideRedirect;
304    attr->override_redirect = True;
305
306    XtWindow(w) = XCreateWindow(DisplayOfScreen(XtScreen(w)),
307				RootWindowOfScreen(XtScreen(w)),
308				XtX(w), XtY(w),
309				XtWidth(w) ? XtWidth(w) : 1,
310				XtHeight(w) ? XtHeight(w) : 1,
311				XtBorderWidth(w),
312				DefaultDepthOfScreen(XtScreen(w)),
313				InputOutput,
314				(Visual *)CopyFromParent,
315				*mask, attr);
316}
317
318static void
319XawTipExpose(Widget w, XEvent *event, Region region)
320{
321    TipWidget tip = (TipWidget)w;
322    GC gc = tip->tip.gc;
323    char *nl, *label = tip->tip.label;
324    Position y = tip->tip.top_margin + tip->tip.font->max_bounds.ascent;
325    int len;
326
327    if (tip->tip.display_list)
328	XawRunDisplayList(w, tip->tip.display_list, event, region);
329
330    if (tip->tip.international == True) {
331	Position ksy = tip->tip.top_margin;
332	XFontSetExtents *ext = XExtentsOfFontSet(tip->tip.fontset);
333
334	ksy += XawAbs(ext->max_ink_extent.y);
335
336	while ((nl = index(label, '\n')) != NULL) {
337	    XmbDrawString(XtDisplay(w), XtWindow(w), tip->tip.fontset,
338			  gc, tip->tip.left_margin, ksy, label,
339			  (int)(nl - label));
340	    ksy += ext->max_ink_extent.height;
341	    label = nl + 1;
342	}
343	len = strlen(label);
344	if (len)
345	    XmbDrawString(XtDisplay(w), XtWindow(w), tip->tip.fontset, gc,
346			  tip->tip.left_margin, ksy, label, len);
347    }
348    else {
349	while ((nl = index(label, '\n')) != NULL) {
350	    if (tip->tip.encoding)
351		XDrawString16(XtDisplay(w), XtWindow(w), gc,
352			      tip->tip.left_margin, y,
353			      (XChar2b*)label, (int)(nl - label) >> 1);
354	    else
355		XDrawString(XtDisplay(w), XtWindow(w), gc,
356			    tip->tip.left_margin, y, label, (int)(nl - label));
357	    y += tip->tip.font->max_bounds.ascent +
358		 tip->tip.font->max_bounds.descent;
359	    label = nl + 1;
360	}
361	len = strlen(label);
362	if (len) {
363	    if (tip->tip.encoding)
364		XDrawString16(XtDisplay(w), XtWindow(w), gc,
365			      tip->tip.left_margin, y, (XChar2b*)label, len >> 1);
366	    else
367		XDrawString(XtDisplay(w), XtWindow(w), gc,
368			    tip->tip.left_margin, y, label, len);
369	}
370    }
371}
372
373/*ARGSUSED*/
374static Boolean
375XawTipSetValues(Widget current, Widget request, Widget cnew,
376		ArgList args, Cardinal *num_args)
377{
378    TipWidget curtip = (TipWidget)current;
379    TipWidget newtip = (TipWidget)cnew;
380    Boolean redisplay = False;
381
382    if (curtip->tip.font->fid != newtip->tip.font->fid ||
383	curtip->tip.foreground != newtip->tip.foreground) {
384	XGCValues values;
385
386	values.foreground = newtip->tip.foreground;
387	values.background = newtip->core.background_pixel;
388	values.font = newtip->tip.font->fid;
389	values.graphics_exposures = False;
390	XtReleaseGC(cnew, curtip->tip.gc);
391	newtip->tip.gc = XtAllocateGC(cnew, 0, GCForeground | GCBackground |
392				      GCFont | GCGraphicsExposures, &values,
393				      GCFont, 0);
394	redisplay = True;
395    }
396    if (curtip->tip.display_list != newtip->tip.display_list)
397	redisplay = True;
398
399    return (redisplay);
400}
401
402static void
403TipLayout(XawTipInfo *info)
404{
405    XFontStruct	*fs = info->tip->tip.font;
406    int width = 0, height;
407    char *nl, *label = info->tip->tip.label;
408
409    if (info->tip->tip.international == True) {
410	XFontSet fset = info->tip->tip.fontset;
411	XFontSetExtents *ext = XExtentsOfFontSet(fset);
412
413	height = ext->max_ink_extent.height;
414	if ((nl = index(label, '\n')) != NULL) {
415	    /*CONSTCOND*/
416	    while (True) {
417		int w = XmbTextEscapement(fset, label, (int)(nl - label));
418
419		if (w > width)
420		    width = w;
421		if (*nl == '\0')
422		    break;
423		label = nl + 1;
424		if (*label)
425		    height += ext->max_ink_extent.height;
426		if ((nl = index(label, '\n')) == NULL)
427		    nl = index(label, '\0');
428	    }
429	}
430	else
431	    width = XmbTextEscapement(fset, label, strlen(label));
432    }
433    else {
434	height = fs->max_bounds.ascent + fs->max_bounds.descent;
435	if ((nl = index(label, '\n')) != NULL) {
436	    /*CONSTCOND*/
437	    while (True) {
438		int w = info->tip->tip.encoding ?
439		    XTextWidth16(fs, (XChar2b*)label, (int)(nl - label) >> 1) :
440		    XTextWidth(fs, label, (int)(nl - label));
441		if (w > width)
442		    width = w;
443		if (*nl == '\0')
444		    break;
445		label = nl + 1;
446		if (*label)
447		    height += fs->max_bounds.ascent + fs->max_bounds.descent;
448		if ((nl = index(label, '\n')) == NULL)
449		    nl = index(label, '\0');
450	    }
451	}
452	else
453	    width = info->tip->tip.encoding ?
454		XTextWidth16(fs, (XChar2b*)label, strlen(label) >> 1) :
455		XTextWidth(fs, label, strlen(label));
456    }
457    XtWidth(info->tip) = width + info->tip->tip.left_margin +
458			 info->tip->tip.right_margin;
459    XtHeight(info->tip) = height + info->tip->tip.top_margin +
460			  info->tip->tip.bottom_margin;
461}
462
463#define	DEFAULT_TIP_Y_OFFSET	12
464static void
465TipPosition(XawTipInfo *info)
466{
467    Window r, c;
468    int rx, ry, wx, wy;
469    unsigned mask;
470    Position x, y;
471
472    XQueryPointer(XtDisplay((Widget)info->tip), XtWindow((Widget)info->tip),
473		  &r, &c, &rx, &ry, &wx, &wy, &mask);
474    x = rx - (XtWidth(info->tip) >> 1);
475    y = ry + DEFAULT_TIP_Y_OFFSET;
476
477    if (x >= 0) {
478	int scr_width = WidthOfScreen(XtScreen(info->tip));
479
480	if (x + XtWidth(info->tip) + XtBorderWidth(info->tip) > scr_width)
481	    x = scr_width - XtWidth(info->tip) - XtBorderWidth(info->tip);
482    }
483    if (x < 0)
484	x = 0;
485    if (y >= 0) {
486	int scr_height = HeightOfScreen(XtScreen(info->tip));
487
488	if (y + XtHeight(info->tip) + XtBorderWidth(info->tip) > scr_height)
489	    y -= XtHeight(info->tip) + XtBorderWidth(info->tip) +
490		 (DEFAULT_TIP_Y_OFFSET << 1);
491    }
492    if (y < 0)
493	y = 0;
494
495    XMoveResizeWindow(XtDisplay(info->tip), XtWindow(info->tip),
496		      (int)(XtX(info->tip) = x), (int)(XtY(info->tip) = y),
497		      (unsigned)XtWidth(info->tip), (unsigned)XtHeight(info->tip));
498}
499
500static XawTipInfo *
501CreateTipInfo(Widget w)
502{
503    XawTipInfo *info = XtNew(XawTipInfo);
504    Widget shell = w;
505
506    info->screen = XtScreen(w);
507
508    while (XtParent(shell))
509	shell = XtParent(shell);
510
511    info->tip = (TipWidget)XtCreateWidget("tip", tipWidgetClass, shell, NULL, 0);
512    XtRealizeWidget((Widget)info->tip);
513    info->widget = NULL;
514    info->mapped = False;
515    info->next = NULL;
516    XtAddEventHandler(shell, KeyPressMask, False, TipShellEventHandler,
517		      (XtPointer)NULL);
518
519    return (info);
520}
521
522static XawTipInfo *
523FindTipInfo(Widget w)
524{
525    XawTipInfo *ptip, *tip = first_tip;
526    Screen *screen = XtScreenOfObject(w);
527
528    if (tip == NULL)
529	return (first_tip = tip = CreateTipInfo(w));
530
531    for (ptip = tip; tip; ptip = tip, tip = tip->next)
532	if (tip->screen == screen)
533	    return (tip);
534
535    return (ptip->next = CreateTipInfo(w));
536}
537
538static void
539ResetTip(XawTipInfo *info, Bool add_timeout)
540{
541    if (info->tip->tip.timer) {
542	XtRemoveTimeOut(info->tip->tip.timer);
543	info->tip->tip.timer = 0;
544    }
545    if (info->mapped) {
546	XtRemoveGrab(XtParent((Widget)info->tip));
547	XUnmapWindow(XtDisplay((Widget)info->tip), XtWindow((Widget)info->tip));
548	info->mapped = False;
549    }
550    if (add_timeout) {
551	info->tip->tip.timer =
552	    XtAppAddTimeOut(XtWidgetToApplicationContext((Widget)info->tip),
553			    info->tip->tip.timeout, TipTimeoutCallback,
554			    (XtPointer)info);
555    }
556}
557
558static void
559TipTimeoutCallback(XtPointer closure, XtIntervalId *id)
560{
561    XawTipInfo *info = (XawTipInfo*)closure;
562    Arg args[3];
563
564    info->tip->tip.label = NULL;
565    info->tip->tip.international = False;
566    info->tip->tip.encoding = 0;
567    XtSetArg(args[0], XtNtip, &info->tip->tip.label);
568    XtSetArg(args[1], XtNinternational, &info->tip->tip.international);
569    XtSetArg(args[2], XtNencoding, &info->tip->tip.encoding);
570    XtGetValues(info->widget, args, 3);
571
572    if (info->tip->tip.label) {
573	TipLayout(info);
574	TipPosition(info);
575	XMapRaised(XtDisplay((Widget)info->tip), XtWindow((Widget)info->tip));
576	XtAddGrab(XtParent((Widget)info->tip), True, True);
577	info->mapped = True;
578    }
579}
580
581/*ARGSUSED*/
582static void
583TipShellEventHandler(Widget w, XtPointer client_data, XEvent *event,
584		     Boolean *continue_to_dispatch)
585{
586    ResetTip(FindTipInfo(w), False);
587}
588
589/*ARGSUSED*/
590static void
591TipEventHandler(Widget w, XtPointer client_data, XEvent *event,
592		Boolean *continue_to_dispatch)
593{
594    XawTipInfo *info = FindTipInfo(w);
595    Boolean add_timeout;
596
597    if (info->widget != w) {
598	ResetTip(info, False);
599	info->widget = w;
600    }
601
602    switch (event->type) {
603	case EnterNotify:
604	    add_timeout = True;
605	    break;
606	case MotionNotify:
607	    /* If any button is pressed, timer is 0 */
608	    if (info->mapped)
609		return;
610	    add_timeout = info->tip->tip.timer != 0;
611	    break;
612	default:
613	    add_timeout = False;
614	    break;
615    }
616    ResetTip(info, add_timeout);
617}
618
619/*
620 * Public routines
621 */
622void
623XawTipEnable(Widget w)
624{
625    XtAddEventHandler(w, TIP_EVENT_MASK, False, TipEventHandler,
626		      (XtPointer)NULL);
627}
628
629void
630XawTipDisable(Widget w)
631{
632    XawTipInfo *info = FindTipInfo(w);
633
634    XtRemoveEventHandler(w, TIP_EVENT_MASK, False, TipEventHandler,
635			 (XtPointer)NULL);
636    if (info->widget == w)
637	ResetTip(info, False);
638}
639