/* * Copyright (c) 1999 by The XFree86 Project, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Except as contained in this notice, the name of the XFree86 Project shall * not be used in advertising or otherwise to promote the sale, use or other * dealings in this Software without prior written authorization from the * XFree86 Project. * * Author: Paulo César Pereira de Andrade */ /* $XFree86: xc/lib/Xaw/Tip.c,v 1.4 1999/07/11 08:49:16 dawes Exp $ */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <X11/IntrinsicP.h> #include <X11/StringDefs.h> #include <X11/Xos.h> #include <X11/Xaw/TipP.h> #include <X11/Xaw/XawInit.h> #include <X11/Xmu/Converters.h> #include "Private.h" #define TIP_EVENT_MASK (ButtonPressMask | \ ButtonReleaseMask | \ PointerMotionMask | \ ButtonMotionMask | \ KeyPressMask | \ KeyReleaseMask | \ EnterWindowMask | \ LeaveWindowMask) /* * Types */ typedef struct _XawTipInfo { Screen *screen; TipWidget tip; Widget widget; Bool mapped; struct _XawTipInfo *next; } XawTipInfo; /* * Class Methods */ static void XawTipClassInitialize(void); static void XawTipInitialize(Widget, Widget, ArgList, Cardinal*); static void XawTipDestroy(Widget); static void XawTipExpose(Widget, XEvent*, Region); static void XawTipRealize(Widget, Mask*, XSetWindowAttributes*); static Boolean XawTipSetValues(Widget, Widget, Widget, ArgList, Cardinal*); /* * Prototypes */ static void TipEventHandler(Widget, XtPointer, XEvent*, Boolean*); static void TipShellEventHandler(Widget, XtPointer, XEvent*, Boolean*); static XawTipInfo *CreateTipInfo(Widget); static XawTipInfo *FindTipInfo(Widget); static void ResetTip(XawTipInfo*, Bool); static void TipTimeoutCallback(XtPointer, XtIntervalId*); static void TipLayout(XawTipInfo*); static void TipPosition(XawTipInfo*); /* * Initialization */ #define offset(field) XtOffsetOf(TipRec, tip.field) static XtResource resources[] = { { XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel), offset(foreground), XtRString, XtDefaultForeground, }, { XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct*), offset(font), XtRString, XtDefaultFont }, { XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet), offset(fontset), XtRString, XtDefaultFontSet }, { XtNtopMargin, XtCVerticalMargins, XtRDimension, sizeof(Dimension), offset(top_margin), XtRImmediate, (XtPointer)2 }, { XtNbottomMargin, XtCVerticalMargins, XtRDimension, sizeof(Dimension), offset(bottom_margin), XtRImmediate, (XtPointer)2 }, { XtNleftMargin, XtCHorizontalMargins, XtRDimension, sizeof(Dimension), offset(left_margin), XtRImmediate, (XtPointer)6 }, { XtNrightMargin, XtCHorizontalMargins, XtRDimension, sizeof(Dimension), offset(right_margin), XtRImmediate, (XtPointer)6 }, { XtNbackingStore, XtCBackingStore, XtRBackingStore, sizeof(int), offset(backing_store), XtRImmediate, (XtPointer)(Always + WhenMapped + NotUseful) }, { XtNtimeout, XtCTimeout, XtRInt, sizeof(int), offset(timeout), XtRImmediate, (XtPointer)500 }, { XawNdisplayList, XawCDisplayList, XawRDisplayList, sizeof(XawDisplayList*), offset(display_list), XtRImmediate, NULL }, }; #undef offset TipClassRec tipClassRec = { /* core */ { (WidgetClass)&widgetClassRec, /* superclass */ "Tip", /* class_name */ sizeof(TipRec), /* widget_size */ XawTipClassInitialize, /* class_initialize */ NULL, /* class_part_initialize */ False, /* class_inited */ XawTipInitialize, /* initialize */ NULL, /* initialize_hook */ XawTipRealize, /* realize */ NULL, /* actions */ 0, /* num_actions */ resources, /* resources */ XtNumber(resources), /* num_resources */ NULLQUARK, /* xrm_class */ True, /* compress_motion */ True, /* compress_exposure */ True, /* compress_enterleave */ False, /* visible_interest */ XawTipDestroy, /* destroy */ NULL, /* resize */ XawTipExpose, /* expose */ XawTipSetValues, /* set_values */ NULL, /* set_values_hook */ XtInheritSetValuesAlmost, /* set_values_almost */ NULL, /* get_values_hook */ NULL, /* accept_focus */ XtVersion, /* version */ NULL, /* callback_private */ NULL, /* tm_table */ XtInheritQueryGeometry, /* query_geometry */ XtInheritDisplayAccelerator, /* display_accelerator */ NULL, /* extension */ }, /* tip */ { NULL, /* extension */ }, }; WidgetClass tipWidgetClass = (WidgetClass)&tipClassRec; static XawTipInfo *first_tip; /* * Implementation */ static void XawTipClassInitialize(void) { XawInitializeWidgetSet(); XtAddConverter(XtRString, XtRBackingStore, XmuCvtStringToBackingStore, NULL, 0); XtSetTypeConverter(XtRBackingStore, XtRString, XmuCvtBackingStoreToString, NULL, 0, XtCacheNone, NULL); } /*ARGSUSED*/ static void XawTipInitialize(Widget req, Widget w, ArgList args, Cardinal *num_args) { TipWidget tip = (TipWidget)w; XGCValues values; if (!tip->tip.font) XtError("Aborting: no font found\n"); if (tip->tip.international && !tip->tip.fontset) XtError("Aborting: no fontset found\n"); tip->tip.timer = 0; values.foreground = tip->tip.foreground; values.background = tip->core.background_pixel; values.font = tip->tip.font->fid; values.graphics_exposures = False; tip->tip.gc = XtAllocateGC(w, 0, GCForeground | GCBackground | GCFont | GCGraphicsExposures, &values, GCFont, 0); } static void XawTipDestroy(Widget w) { XawTipInfo *info = FindTipInfo(w); TipWidget tip = (TipWidget)w; if (tip->tip.timer) XtRemoveTimeOut(tip->tip.timer); XtReleaseGC(w, tip->tip.gc); XtRemoveEventHandler(XtParent(w), KeyPressMask, False, TipShellEventHandler, (XtPointer)NULL); if (info == first_tip) first_tip = first_tip->next; else { XawTipInfo *p = first_tip; while (p && p->next != info) p = p->next; if (p) p->next = info->next; } XtFree((char*)info); } static void XawTipRealize(Widget w, Mask *mask, XSetWindowAttributes *attr) { TipWidget tip = (TipWidget)w; if (tip->tip.backing_store == Always || tip->tip.backing_store == NotUseful || tip->tip.backing_store == WhenMapped) { *mask |= CWBackingStore; attr->backing_store = tip->tip.backing_store; } else *mask &= ~CWBackingStore; *mask |= CWOverrideRedirect; attr->override_redirect = True; XtWindow(w) = XCreateWindow(DisplayOfScreen(XtScreen(w)), RootWindowOfScreen(XtScreen(w)), XtX(w), XtY(w), XtWidth(w) ? XtWidth(w) : 1, XtHeight(w) ? XtHeight(w) : 1, XtBorderWidth(w), DefaultDepthOfScreen(XtScreen(w)), InputOutput, CopyFromParent, *mask, attr); } static void XawTipExpose(Widget w, XEvent *event, Region region) { TipWidget tip = (TipWidget)w; GC gc = tip->tip.gc; char *nl, *label = tip->tip.label; Position y = tip->tip.top_margin + tip->tip.font->max_bounds.ascent; int len; if (tip->tip.display_list) XawRunDisplayList(w, tip->tip.display_list, event, region); if (tip->tip.international == True) { Position ksy = tip->tip.top_margin; XFontSetExtents *ext = XExtentsOfFontSet(tip->tip.fontset); ksy += XawAbs(ext->max_ink_extent.y); while ((nl = index(label, '\n')) != NULL) { XmbDrawString(XtDisplay(w), XtWindow(w), tip->tip.fontset, gc, tip->tip.left_margin, ksy, label, (int)(nl - label)); ksy += ext->max_ink_extent.height; label = nl + 1; } len = strlen(label); if (len) XmbDrawString(XtDisplay(w), XtWindow(w), tip->tip.fontset, gc, tip->tip.left_margin, ksy, label, len); } else { while ((nl = index(label, '\n')) != NULL) { if (tip->tip.encoding) XDrawString16(XtDisplay(w), XtWindow(w), gc, tip->tip.left_margin, y, (XChar2b*)label, (int)(nl - label) >> 1); else XDrawString(XtDisplay(w), XtWindow(w), gc, tip->tip.left_margin, y, label, (int)(nl - label)); y += tip->tip.font->max_bounds.ascent + tip->tip.font->max_bounds.descent; label = nl + 1; } len = strlen(label); if (len) { if (tip->tip.encoding) XDrawString16(XtDisplay(w), XtWindow(w), gc, tip->tip.left_margin, y, (XChar2b*)label, len >> 1); else XDrawString(XtDisplay(w), XtWindow(w), gc, tip->tip.left_margin, y, label, len); } } } /*ARGSUSED*/ static Boolean XawTipSetValues(Widget current, Widget request, Widget cnew, ArgList args, Cardinal *num_args) { TipWidget curtip = (TipWidget)current; TipWidget newtip = (TipWidget)cnew; Boolean redisplay = False; if (curtip->tip.font->fid != newtip->tip.font->fid || curtip->tip.foreground != newtip->tip.foreground) { XGCValues values; values.foreground = newtip->tip.foreground; values.background = newtip->core.background_pixel; values.font = newtip->tip.font->fid; values.graphics_exposures = False; XtReleaseGC(cnew, curtip->tip.gc); newtip->tip.gc = XtAllocateGC(cnew, 0, GCForeground | GCBackground | GCFont | GCGraphicsExposures, &values, GCFont, 0); redisplay = True; } if (curtip->tip.display_list != newtip->tip.display_list) redisplay = True; return (redisplay); } static void TipLayout(XawTipInfo *info) { XFontStruct *fs = info->tip->tip.font; int width = 0, height; char *nl, *label = info->tip->tip.label; if (info->tip->tip.international == True) { XFontSet fset = info->tip->tip.fontset; XFontSetExtents *ext = XExtentsOfFontSet(fset); height = ext->max_ink_extent.height; if ((nl = index(label, '\n')) != NULL) { /*CONSTCOND*/ while (True) { int w = XmbTextEscapement(fset, label, (int)(nl - label)); if (w > width) width = w; if (*nl == '\0') break; label = nl + 1; if (*label) height += ext->max_ink_extent.height; if ((nl = index(label, '\n')) == NULL) nl = index(label, '\0'); } } else width = XmbTextEscapement(fset, label, strlen(label)); } else { height = fs->max_bounds.ascent + fs->max_bounds.descent; if ((nl = index(label, '\n')) != NULL) { /*CONSTCOND*/ while (True) { int w = info->tip->tip.encoding ? XTextWidth16(fs, (XChar2b*)label, (int)(nl - label) >> 1) : XTextWidth(fs, label, (int)(nl - label)); if (w > width) width = w; if (*nl == '\0') break; label = nl + 1; if (*label) height += fs->max_bounds.ascent + fs->max_bounds.descent; if ((nl = index(label, '\n')) == NULL) nl = index(label, '\0'); } } else width = info->tip->tip.encoding ? XTextWidth16(fs, (XChar2b*)label, strlen(label) >> 1) : XTextWidth(fs, label, strlen(label)); } XtWidth(info->tip) = width + info->tip->tip.left_margin + info->tip->tip.right_margin; XtHeight(info->tip) = height + info->tip->tip.top_margin + info->tip->tip.bottom_margin; } #define DEFAULT_TIP_Y_OFFSET 12 static void TipPosition(XawTipInfo *info) { Window r, c; int rx, ry, wx, wy; unsigned mask; Position x, y; XQueryPointer(XtDisplay((Widget)info->tip), XtWindow((Widget)info->tip), &r, &c, &rx, &ry, &wx, &wy, &mask); x = rx - (XtWidth(info->tip) >> 1); y = ry + DEFAULT_TIP_Y_OFFSET; if (x >= 0) { int scr_width = WidthOfScreen(XtScreen(info->tip)); if (x + XtWidth(info->tip) + XtBorderWidth(info->tip) > scr_width) x = scr_width - XtWidth(info->tip) - XtBorderWidth(info->tip); } if (x < 0) x = 0; if (y >= 0) { int scr_height = HeightOfScreen(XtScreen(info->tip)); if (y + XtHeight(info->tip) + XtBorderWidth(info->tip) > scr_height) y -= XtHeight(info->tip) + XtBorderWidth(info->tip) + (DEFAULT_TIP_Y_OFFSET << 1); } if (y < 0) y = 0; XMoveResizeWindow(XtDisplay(info->tip), XtWindow(info->tip), (int)(XtX(info->tip) = x), (int)(XtY(info->tip) = y), (unsigned)XtWidth(info->tip), (unsigned)XtHeight(info->tip)); } static XawTipInfo * CreateTipInfo(Widget w) { XawTipInfo *info = XtNew(XawTipInfo); Widget shell = w; info->screen = XtScreen(w); while (XtParent(shell)) shell = XtParent(shell); info->tip = (TipWidget)XtCreateWidget("tip", tipWidgetClass, shell, NULL, 0); XtRealizeWidget((Widget)info->tip); info->widget = NULL; info->mapped = False; info->next = NULL; XtAddEventHandler(shell, KeyPressMask, False, TipShellEventHandler, (XtPointer)NULL); return (info); } static XawTipInfo * FindTipInfo(Widget w) { XawTipInfo *ptip, *tip = first_tip; Screen *screen = XtScreenOfObject(w); if (tip == NULL) return (first_tip = tip = CreateTipInfo(w)); for (ptip = tip; tip; ptip = tip, tip = tip->next) if (tip->screen == screen) return (tip); return (ptip->next = CreateTipInfo(w)); } static void ResetTip(XawTipInfo *info, Bool add_timeout) { if (info->tip->tip.timer) { XtRemoveTimeOut(info->tip->tip.timer); info->tip->tip.timer = 0; } if (info->mapped) { XtRemoveGrab(XtParent((Widget)info->tip)); XUnmapWindow(XtDisplay((Widget)info->tip), XtWindow((Widget)info->tip)); info->mapped = False; } if (add_timeout) { info->tip->tip.timer = XtAppAddTimeOut(XtWidgetToApplicationContext((Widget)info->tip), info->tip->tip.timeout, TipTimeoutCallback, (XtPointer)info); } } static void TipTimeoutCallback(XtPointer closure, XtIntervalId *id) { XawTipInfo *info = (XawTipInfo*)closure; Arg args[3]; info->tip->tip.label = NULL; info->tip->tip.international = False; info->tip->tip.encoding = 0; XtSetArg(args[0], XtNtip, &info->tip->tip.label); XtSetArg(args[1], XtNinternational, &info->tip->tip.international); XtSetArg(args[2], XtNencoding, &info->tip->tip.encoding); XtGetValues(info->widget, args, 3); if (info->tip->tip.label) { TipLayout(info); TipPosition(info); XMapRaised(XtDisplay((Widget)info->tip), XtWindow((Widget)info->tip)); XtAddGrab(XtParent((Widget)info->tip), True, True); info->mapped = True; } } /*ARGSUSED*/ static void TipShellEventHandler(Widget w, XtPointer client_data, XEvent *event, Boolean *continue_to_dispatch) { ResetTip(FindTipInfo(w), False); } /*ARGSUSED*/ static void TipEventHandler(Widget w, XtPointer client_data, XEvent *event, Boolean *continue_to_dispatch) { XawTipInfo *info = FindTipInfo(w); Boolean add_timeout; if (info->widget != w) { ResetTip(info, False); info->widget = w; } switch (event->type) { case EnterNotify: add_timeout = True; break; case MotionNotify: /* If any button is pressed, timer is 0 */ if (info->mapped) return; add_timeout = info->tip->tip.timer != 0; break; default: add_timeout = False; break; } ResetTip(info, add_timeout); } /* * Public routines */ void XawTipEnable(Widget w) { XtAddEventHandler(w, TIP_EVENT_MASK, False, TipEventHandler, (XtPointer)NULL); } void XawTipDisable(Widget w) { XawTipInfo *info = FindTipInfo(w); XtRemoveEventHandler(w, TIP_EVENT_MASK, False, TipEventHandler, (XtPointer)NULL); if (info->widget == w) ResetTip(info, False); }