/* * 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 */ /* * Portions Copyright (c) 2003 David J. Hawkey Jr. * Rights, permissions, and disclaimer per the above XFree86 license. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #define TIP_EVENT_MASK (ButtonPressMask | \ ButtonReleaseMask | \ PointerMotionMask | \ ButtonMotionMask | \ KeyPressMask | \ KeyReleaseMask | \ EnterWindowMask | \ LeaveWindowMask) /* * Types */ typedef struct _WidgetInfo { Widget widget; String label; struct _WidgetInfo *next; } WidgetInfo; typedef struct _XawTipInfo { Screen *screen; TipWidget tip; Bool mapped; WidgetInfo *widgets; struct _XawTipInfo *next; } XawTipInfo; typedef struct { XawTipInfo *info; WidgetInfo *winfo; } TimeoutInfo; /* * 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 WidgetInfo *CreateWidgetInfo(Widget); static WidgetInfo *FindWidgetInfo(XawTipInfo *, Widget); static XawTipInfo *CreateTipInfo(Widget); static XawTipInfo *FindTipInfo(Widget); static void ResetTip(XawTipInfo *, WidgetInfo *, 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}, #ifdef XAW_INTERNATIONALIZATION {XtNfontSet, XtCFontSet, XtRFontSet, sizeof(XFontSet), offset(fontset), XtRString, XtDefaultFontSet}, #endif {XtNlabel, XtCLabel, XtRString, sizeof(String), offset(label), XtRString, NULL}, {XtNencoding, XtCEncoding, XtRUnsignedChar, sizeof(unsigned char), offset(encoding), XtRImmediate, (XtPointer)XawTextEncoding8bit}, {XtNinternalHeight, XtCHeight, XtRDimension, sizeof(Dimension), offset(internal_height), XtRImmediate, (XtPointer)2}, {XtNinternalWidth, XtCWidth, XtRDimension, sizeof(Dimension), offset(internal_width), XtRImmediate, (XtPointer)2}, {XtNbackingStore, XtCBackingStore, XtRBackingStore, sizeof(int), offset(backing_store), XtRImmediate, (XtPointer)(Always + WhenMapped + NotUseful)}, {XtNtimeout, XtCTimeout, XtRInt, sizeof(int), offset(timeout), XtRImmediate, (XtPointer)500}, }; #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 *TipInfoList = NULL; static TimeoutInfo TimeoutData; /* * Implementation */ /* * XmuCvtBackingStoreToString() from XFree86's distribution, because * X.Org's distribution doesn't have it. */ /*ARGSUSED*/ static Boolean XawCvtBackingStoreToString(Display *dpy, XrmValuePtr args, Cardinal *num_args, XrmValuePtr fromVal, XrmValuePtr toVal, XtPointer *data) { static String buffer; Cardinal size; switch (*(int *)fromVal->addr) { case NotUseful: buffer = XtEnotUseful; break; case WhenMapped: buffer = XtEwhenMapped; break; case Always: buffer = XtEalways; break; case (Always + WhenMapped + NotUseful): buffer = XtEdefault; break; default: XtWarning("Cannot convert BackingStore to String"); toVal->addr = NULL; toVal->size = 0; return (False); } size = strlen(buffer) + 1; if (toVal->addr != NULL) { if (toVal->size < size) { toVal->size = size; return (False); } strcpy((char *)toVal->addr, buffer); } else toVal->addr = (XPointer)buffer; toVal->size = sizeof(String); return (True); } static void XawTipClassInitialize(void) { XawInitializeWidgetSet(); XtAddConverter(XtRString, XtRBackingStore, XmuCvtStringToBackingStore, NULL, 0); XtSetTypeConverter(XtRBackingStore, XtRString, XawCvtBackingStoreToString, NULL, 0, XtCacheNone, NULL); } /*ARGSUSED*/ static void XawTipInitialize(Widget req, Widget w, ArgList args, Cardinal *num_args) { TipWidget tip = (TipWidget)w; XGCValues values; 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); WidgetInfo *winfo; 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); while (info->widgets) { winfo = info->widgets->next; XtFree((char *)info->widgets->label); XtFree((char *)info->widgets); info->widgets = winfo; } if (info == TipInfoList) TipInfoList = TipInfoList->next; else { XawTipInfo *p = TipInfoList; 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.internal_height + tip->tip.font->max_bounds.ascent; int len; #ifdef XAW_INTERNATIONALIZATION if (tip->tip.international == True) { Position ksy = tip->tip.internal_height; XFontSetExtents *ext = XExtentsOfFontSet(tip->tip.fontset); ksy += abs(ext->max_ink_extent.y); while ((nl = index(label, '\n')) != NULL) { XmbDrawString(XtDisplay(w), XtWindow(w), tip->tip.fontset, gc, tip->tip.internal_width, 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.internal_width, ksy, label, len); } else #endif { while ((nl = index(label, '\n')) != NULL) { if (tip->tip.encoding) XDrawString16(XtDisplay(w), XtWindow(w), gc, tip->tip.internal_width, y, (XChar2b*)label, (int)(nl - label) >> 1); else XDrawString(XtDisplay(w), XtWindow(w), gc, tip->tip.internal_width, 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.internal_width, y, (XChar2b*)label, len >> 1); else XDrawString(XtDisplay(w), XtWindow(w), gc, tip->tip.internal_width, 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; } return (redisplay); } static void TipLayout(XawTipInfo *info) { XFontStruct *fs = info->tip->tip.font; int width = 0, height; char *nl, *label = info->tip->tip.label; #ifdef XAW_INTERNATIONALIZATION 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 #endif { 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.internal_width * 2; XtHeight(info->tip) = height + info->tip->tip.internal_height * 2; } #define DEFAULT_TIP_OFFSET 12 static void TipPosition(XawTipInfo *info) { Window r, c; int rx, ry, wx, wy; unsigned mask; Position x, y; int bw2 = XtBorderWidth(info->tip) * 2; int scr_width = WidthOfScreen(XtScreen(info->tip)); int scr_height = HeightOfScreen(XtScreen(info->tip)); int win_width = XtWidth(info->tip) + bw2; int win_height = XtHeight(info->tip) + bw2; XQueryPointer(XtDisplay((Widget)info->tip), XtWindow((Widget)info->tip), &r, &c, &rx, &ry, &wx, &wy, &mask); x = rx + DEFAULT_TIP_OFFSET; y = ry + DEFAULT_TIP_OFFSET; if (x + win_width > scr_width) x = scr_width - win_width; if (x < 0) x = 0; if (y + win_height > scr_height) y -= win_height + (DEFAULT_TIP_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 WidgetInfo * CreateWidgetInfo(Widget w) { WidgetInfo *winfo = XtNew(WidgetInfo); winfo->widget = w; winfo->label = NULL; winfo->next = NULL; return (winfo); } static WidgetInfo * FindWidgetInfo(XawTipInfo *info, Widget w) { WidgetInfo *winfo, *wlist = info->widgets; if (wlist == NULL) return (info->widgets = CreateWidgetInfo(w)); for (winfo = wlist; wlist; winfo = wlist, wlist = wlist->next) if (wlist->widget == w) return (wlist); return (winfo->next = CreateWidgetInfo(w)); } static XawTipInfo * CreateTipInfo(Widget w) { XawTipInfo *info = XtNew(XawTipInfo); Widget shell = w; while (XtParent(shell)) shell = XtParent(shell); info->tip = (TipWidget)XtCreateWidget("tip", tipWidgetClass, shell, NULL, 0); XtRealizeWidget((Widget)info->tip); info->screen = XtScreen(w); info->mapped = False; info->widgets = NULL; info->next = NULL; XtAddEventHandler(shell, KeyPressMask, False, TipShellEventHandler, (XtPointer)NULL); return (info); } static XawTipInfo * FindTipInfo(Widget w) { XawTipInfo *info, *list = TipInfoList; Screen *screen; if (list == NULL) return (TipInfoList = CreateTipInfo(w)); screen = XtScreen(w); for (info = list; list; info = list, list = list->next) if (list->screen == screen) return (list); return (info->next = CreateTipInfo(w)); } static void ResetTip(XawTipInfo *info, WidgetInfo *winfo, 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) { TimeoutData.info = info; TimeoutData.winfo = winfo; info->tip->tip.timer = XtAppAddTimeOut(XtWidgetToApplicationContext((Widget)info->tip), info->tip->tip.timeout, TipTimeoutCallback, (XtPointer)&TimeoutData); } } static void TipTimeoutCallback(XtPointer closure, XtIntervalId *id) { TimeoutInfo *cinfo = (TimeoutInfo *)closure; XawTipInfo *info = cinfo->info; WidgetInfo *winfo = cinfo->winfo; Arg args[2]; info->tip->tip.label = winfo->label; info->tip->tip.encoding = 0; XtSetArg(args[0], XtNencoding, &info->tip->tip.encoding); #ifdef XAW_INTERNATIONALIZATION info->tip->tip.international = False; XtSetArg(args[1], XtNinternational, &info->tip->tip.international); XtGetValues(winfo->widget, args, 2); #else XtGetValues(winfo->widget, args, 1); #endif 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) { XawTipInfo *info = FindTipInfo(w); ResetTip(info, FindWidgetInfo(info, w), False); } /*ARGSUSED*/ static void TipEventHandler(Widget w, XtPointer client_data, XEvent *event, Boolean *continue_to_dispatch) { XawTipInfo *info = FindTipInfo(w); Boolean add_timeout; 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, FindWidgetInfo(info, w), add_timeout); } /* * Public routines */ void XawTipEnable(Widget w, String label) { if (XtIsWidget(w) && label && *label) { XawTipInfo *info = FindTipInfo(w); WidgetInfo *winfo = FindWidgetInfo(info, w); if (winfo->label) XtFree((char *)winfo->label); winfo->label = XtNewString(label); XtAddEventHandler(w, TIP_EVENT_MASK, False, TipEventHandler, (XtPointer)NULL); } } void XawTipDisable(Widget w) { if (XtIsWidget(w)) { XawTipInfo *info = FindTipInfo(w); XtRemoveEventHandler(w, TIP_EVENT_MASK, False, TipEventHandler, (XtPointer)NULL); ResetTip(info, FindWidgetInfo(info, w), False); } }