2
* Copyright (c) 2001-2004 Marcin Dalecki and others
4
* Permission is hereby granted, free of charge, to any person obtaining a copy
5
* of this software and associated documentation files (the "Software"), to
6
* deal in the Software without restriction, including without limitation the
7
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8
* sell copies of the Software, and to permit persons to whom the Software is
9
* furnished to do so, subject to the following conditions:
11
* The above copyright notice and this permission notice shall be included in
12
* all copies or substantial portions of the Software.
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
* PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM,
18
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
19
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
20
* OTHER DEALINGS IN THE SOFTWARE.
24
#include "xdvi-config.h"
28
#include "xdvi-debug.h"
31
#ifdef MOTIF /* needed for `make depend' */
34
#define UNUSED(x) ((void)(x))
41
#include <X11/IntrinsicP.h>
42
#include <X11/StringDefs.h>
46
#include <X11/ShellP.h>
48
#define TIP_NUM 1024 /* FIXME: remove this hard-coded value */
54
/* Full class record declaration */
55
typedef struct _TipClassRec {
56
CoreClassPart core_class;
57
CompositeClassPart composite_class;
58
ShellClassPart shell_class;
59
OverrideShellClassPart override_shell_class;
60
TipClassPart tip_class;
63
/* keep information about each widget we are keeping track of */
65
Widget watched; /* the widget we are watching */
66
Window window; /* Window of the object we are monitoring */
67
TipWidget tw; /* pointer back to the tip widget */
68
Position abs_x, abs_y;
69
Boolean active; /* if False, tip is suppressed */
70
char *text; /* text to display */
71
short size; /* its size */
74
/* New fields for the widget record */
78
XFontSet fontset; /* the font for text in box */
79
int waitPeriod; /* the delay resource - pointer must be
80
* in watched widget this long before
81
* help is popped up - in millisecs
83
unsigned int cwp; /* after help is popped down - normal
84
* wait period is cancelled for this
85
* period - in millisecs
89
struct tip_context twl[TIP_NUM]; /* list of widgets we are liteClue-ing */
90
Cardinal nr_twl; /* number of widgets we have attached */
91
Dimension font_width; /* width of '1' character */
92
Dimension font_height; /* height of font, rows are spaced using this */
93
Dimension font_baseline; /* relative displacement to baseline from top */
94
GC text_GC; /* for drawing text */
95
XtIntervalId tid; /* holds timer id */
96
Widget isup; /* the help popup is up on this widget */
97
Time HelpPopDownTime; /* the time at which help popup
102
* Full instance record declaration
104
typedef struct _TipRec {
106
CompositePart composite;
108
OverrideShellPart override;
112
#define CheckWidgetClass(routine) \
113
if (XtClass(w) != tipWidgetClass) \
114
wrong_widget(routine)
116
static void initialize(Widget, Widget, ArgList, Cardinal *);
117
static Boolean set_values(Widget, Widget, Widget, ArgList, Cardinal *);
118
static void destroy(Widget);
121
* Widget resources: eg to set tip box background: *tipShell.background: yellow.
124
#define offset(field) XtOffsetOf(TipRec, field)
125
static XtResource resources[] = {
126
{XtNforeground, XtCForeground,
127
XtRPixel, sizeof(Pixel), offset(tip.foreground),
129
{XtNfontSet, XtCFontSet,
130
XtRFontSet, sizeof(XFontSet), offset(tip.fontset),
132
{XmNwaitPeriod, XmCWaitPeriod,
133
XtRInt, sizeof(int), offset(tip.waitPeriod),
134
XtRImmediate, (XtPointer) 800},
135
{XmNcancelWaitPeriod, XmCCancelWaitPeriod,
136
XtRInt, sizeof(int), offset(tip.cwp),
137
XtRImmediate, (XtPointer) 250},
141
TipClassRec tipClassRec = {
143
/* superclass */ (WidgetClass) & overrideShellClassRec,
144
/* class_name */ "Tip",
145
/* widget size */ (Cardinal) sizeof(TipRec),
146
/* class_init */ NULL,
147
/* class_part_init */ (XtWidgetClassProc) NULL,
148
/* class_inited */ (XtEnum) FALSE,
149
/* initialize */ (XtInitProc) initialize,
150
/* init_hook */ (XtArgsProc) NULL,
151
/* realize */ XtInheritRealize,
152
/* actions */ (XtActionList) 0,
153
/* num_actions */ (Cardinal) 0,
154
/* resources */ (XtResourceList) resources,
155
/* num_resources */ (Cardinal) XtNumber(resources),
156
/* xrm_class */ NULLQUARK,
157
/* compress_motion */ TRUE,
158
/* compress_exposur */ (XtEnum) FALSE,
159
/* compress enterleave */ TRUE,
160
/* visibility_interest */ FALSE,
161
/* destroy */ destroy,
162
/* resize */ XtInheritResize,
163
/* expose, */ XtInheritExpose,
164
/* set_values */ (XtSetValuesFunc) set_values,
165
/* set_values_hook */ (XtArgsFunc) NULL,
166
/* set_values_almost */ XtInheritSetValuesAlmost,
167
/* get_values_hook */ (XtArgsProc) NULL,
168
/* accept_focus */ XtInheritAcceptFocus,
169
/* version */ XtVersion,
170
/* callback_private */ (XtPointer) NULL,
171
/* translations */ XtInheritTranslations,
172
/* query_geometry */ XtInheritQueryGeometry,
173
/* display_accelerator */ XtInheritDisplayAccelerator,
174
/* extension */ (XtPointer) 0,
178
/* geometry_manager */ XtInheritGeometryManager,
179
/* change_managed */ XtInheritChangeManaged,
180
/* insert_child */ XtInheritInsertChild,
181
/* delete_child */ XtInheritDeleteChild,
198
WidgetClass tipWidgetClass = (WidgetClass) & tipClassRec;
201
* The font_information is derived.
203
static void compute_font_info(TipWidget cw)
208
if (!cw->tip.fontset)
210
XmbTextExtents(cw->tip.fontset, "1", 1, &ink, &logical);
212
cw->tip.font_baseline = -logical.y; /* y offset from top to baseline, don't
213
know why this is returned as
216
cw->tip.font_width = logical.width; /* the width and height of the object */
218
cw->tip.font_height = logical.height;
219
TRACE_GUI((stderr, "baseline: %d, width: %d, height: %d\n",
220
cw->tip.font_baseline, cw->tip.font_width, cw->tip.font_height));
224
* Creates the various graphic contexts we will need.
226
static void create_GC(TipWidget cw)
231
valuemask = GCForeground | GCBackground | GCFillStyle;
232
myXGCV.foreground = cw->tip.foreground;
233
myXGCV.background = cw->core.background_pixel;
234
myXGCV.fill_style = FillSolid;
237
XtReleaseGC((Widget) cw, cw->tip.text_GC);
238
cw->tip.text_GC = XtGetGC((Widget) cw, valuemask, &myXGCV);
242
* A routine to halt execution and force a core dump for debugging analysis
243
* when a public routine is called with the wrong class of widget.
246
static void wrong_widget(char *routine)
248
XDVI_ABORT((stderr, "Wrong class of widget passed to %s", routine));
252
* Global list of shells for tips that are in use.
255
static TipWidget *shells = NULL;
256
static int nr_shells = 0;
258
/****************************************************************************
262
static void initialize(Widget treq, Widget tnew, ArgList args,
265
TipWidget tw = (TipWidget) tnew;
271
tw->tip.text_GC = NULL;
273
tw->tip.HelpPopDownTime = 0;
274
tw->tip.tid = (XtIntervalId) 0;
276
compute_font_info(tw);
279
/* Add to our list of tip shells.
282
shells = (TipWidget *)XtMalloc(sizeof(TipWidget));
284
shells = (TipWidget *)XtRealloc((char *)shells,
285
sizeof(TipWidget) * (nr_shells + 1));
287
shells[nr_shells++] = tw;
290
static Boolean set_values(Widget _current, Widget _request, Widget _new,
291
ArgList args, Cardinal * nargs)
293
TipWidget cw_new = (TipWidget) _new;
294
TipWidget cw_cur = (TipWidget) _current;
300
/* values of cw_new->tip.cwp and
301
cw_new->tip.waitPeriod are accepted without checking */
303
if (cw_new->tip.foreground != cw_cur->tip.foreground
304
|| cw_new->core.background_pixel !=
305
cw_cur->core.background_pixel) {
311
static void destroy(Widget w)
313
TipWidget tw = (TipWidget) w;
315
Boolean copy = False;
317
/* Remove this tip shell from our global list.
319
for (i = 0; i < nr_shells; ++i) {
320
if (shells[i] == tw) {
324
if (copy && nr_shells)
325
shells[i] = shells[i + 1];
328
XtFree((char *) shells);
333
/****************************************************************************
337
/* callback to popup the tip window
339
static void timeout_event(XtPointer client_data, XtIntervalId *id)
343
struct tip_context *obj = (struct tip_context *) client_data;
344
TipWidget tw = obj->tw;
345
Position abs_x, abs_y;
350
Position w_height, w_width;
355
TRACE_GUI((stderr, "timeout called!"));
357
if (tw->tip.tid == (XtIntervalId) 0)
358
return; /* timeout was removed but callback happened
361
tw->tip.tid = (XtIntervalId) 0;
362
if (obj->active == False)
370
{ /* perform additional check that pointer is really still over the widget;
371
else, tooltips will sometimes pop up if window had received an Enter
372
event before (for some reason, not all Enters are followed by Leaves).
373
This is especially apparent when running xdvi from a remote display over
378
unsigned int keys_buttons;
379
if (!XQueryPointer(DISP, RootWindowOfScreen(SCRN), &root, &child,
380
&root_x, &root_y, &ptr_x, &ptr_y, &keys_buttons))
383
TRACE_GUI((stderr, "Pointerlocate: %d, %d", root_x, root_y));
385
XtVaGetValues(w, XtNheight, &w_height, XtNwidth, &w_width, NULL);
386
XtTranslateCoords(w, 0, 0, &abs_x, &abs_y);
388
TRACE_GUI((stderr, "Window: %d,%d - %d,%d",
389
abs_x, abs_y, abs_x + w_width, abs_y + w_height));
391
if (root_x < abs_x || root_x > abs_x + w_width
392
|| root_y < abs_y || root_y > abs_y + w_height) {
393
TRACE_GUI((stderr, "not really over toolbutton - returning!"));
398
/* position just below the pointer
399
* (NOT the widget, in case the widget is large!)
402
/* abs_x += w_width / 2; */
403
/* abs_y += w_height; */
405
XmbTextExtents(tw->tip.fontset, obj->text, obj->size, &ink, &logical);
407
XtRealizeWidget((Widget)tw); /* so that setting the size etc. works */
409
XtResizeWidget((Widget) tw,
410
2 * HBorderPix + logical.width,
411
2 * VBorderPix + tw->tip.font_height,
412
tw->core.border_width);
413
TRACE_GUI((stderr, "Popup size: %d x %d (hborder: %d, vborder: %d)\n",
414
2 * HBorderPix + logical.width, 2 * VBorderPix + tw->tip.font_height,
415
HBorderPix, VBorderPix));
416
XtMoveWidget((Widget)tw, ptr_x, ptr_y);
418
XtPopup((Widget) tw, XtGrabNone);
419
tw->tip.isup = obj->watched;
421
XmbDrawImageString(XtDisplay((Widget) tw),
422
XtWindow((Widget) tw),
426
VBorderPix + tw->tip.font_baseline,
427
obj->text, obj->size);
431
* Pointer enters watched widget, set a timer to popup the help.
433
static void enter(struct tip_context *obj, XEvent * xevent,
436
TipWidget tw = obj->tw;
437
XEnterWindowEvent *event = &xevent->xcrossing;
438
int current_waitPeriod;
440
/* this doesn't help against the Enter/Leave problem mentioned above,
441
so it's not related to Widget creation ... */
442
if (!XtIsManaged(obj->watched)) {
443
TRACE_GUI((stderr, "%s:%d: Not yet managed!", __FILE__, __LINE__));
447
TRACE_GUI((stderr, "%s:%d: Enter!", __FILE__, __LINE__));
449
if (obj->active == False)
452
/* check for two enters in a row - happens when widget is
453
exposed under a pop-up */
454
if (tw->tip.tid != (XtIntervalId) 0)
457
if (event->mode != NotifyNormal)
460
/* it seems that this makes the tooltips somewhat unpredictable (they
461
don't show when hovering fast over several buttons, then staying on
462
one button); disabled this for the time being. */
463
/* if ((event->time - tw->tip.HelpPopDownTime) > tw->tip.cwp) */
464
/* current_waitPeriod = tw->tip.waitPeriod; */
466
/* current_waitPeriod = 0; */
468
/* current_waitPeriod = tw->tip.waitPeriod; */
469
current_waitPeriod = resource.tooltips_wait_period;
470
if (current_waitPeriod >= 0) {
471
tw->tip.tid = XtAppAddTimeOut(app, current_waitPeriod, timeout_event,
477
* Remove timer if its pending. Then popdown help.
479
static void leave(struct tip_context *obj, XEvent * xevent)
481
TipWidget tw = obj->tw;
482
XEnterWindowEvent *event = &xevent->xcrossing;
484
TRACE_GUI((stderr, "%s:%d: Leave!", __FILE__, __LINE__));
486
if (tw->tip.tid != (XtIntervalId) 0) {
487
if (globals.debug & DBG_EVENT)
488
fprintf(stderr, "%s:%d: removing timeout %ld\n", __FILE__, __LINE__, tw->tip.tid);
489
XtRemoveTimeOut(tw->tip.tid);
490
tw->tip.tid = (XtIntervalId) 0;
493
if (obj->active == False)
497
XtPopdown((Widget) tw);
499
tw->tip.HelpPopDownTime = event->time;
503
/****************************************************************************
504
* Public interface implementation.
507
void TipAppHandle(XtAppContext app, XEvent *event)
511
if (!(event->type == EnterNotify
512
|| event->type == MotionNotify
513
|| event->type == LeaveNotify
514
|| event->type == ButtonPress)) {
518
for (i = 0; i < nr_shells; ++i) {
521
for (j = 0; j < shells[i]->tip.nr_twl; ++j) {
522
if (event->xany.window == shells[i]->tip.twl[j].window) {
523
if (event->type == EnterNotify)
524
enter(shells[i]->tip.twl + j, event, app);
525
if (event->xany.type == LeaveNotify
526
|| event->xany.type == MotionNotify /* FIXME: this doesn' work? */
527
/* might be useful to popdown tip when mouse is moved */
528
|| event->xany.type == ButtonPress) {
529
leave(shells[i]->tip.twl + j, event);
537
* This has to replace the XtAppMainLoop in the application using
540
void TipAppMainLoop(XtAppContext app)
545
XtAppNextEvent(app, &event);
546
TipAppHandle(app, &event);
547
XtDispatchEvent(&event);
552
* Add a widget to be watched for tooltips.
554
* This function must be called after the widget has been realized!
555
* Further on please make sure that this function will not be called twice
559
* watch - the widget to give tips for
560
* text - pointer to tip text
562
void TipAddWidget(Widget w, Widget watch, const String text)
564
#define ROUTINE "TipAddWidget"
565
TipWidget tw = (TipWidget) w;
568
CheckWidgetClass(ROUTINE); /* make sure we are called with a tip widget */
570
/* Make internal resource available via resource.tooltips_wait_period(_bak) and
571
* resource.show_tooltips.
573
resource.tooltips_wait_period_bak = resource.tooltips_wait_period = ABS(tw->tip.waitPeriod);
574
if (tw->tip.waitPeriod < 0) {
575
resource.show_tooltips = False;
577
else if (!resource.show_tooltips) {
578
if (resource.tooltips_wait_period == 0)
579
resource.tooltips_wait_period = -1;
581
resource.tooltips_wait_period = -resource.tooltips_wait_period;
584
for (i = 0; i < nr_shells; ++i)
585
if (shells[i] == tw) {
586
struct tip_context *obj;
588
if (tw->tip.nr_twl >= TIP_NUM) {
589
XDVI_FATAL((stderr, "Too many tip widgets, cannot add new tip"));
592
obj = tw->tip.twl + tw->tip.nr_twl;
593
obj->text = XtNewString(text);
594
obj->size = strlen(text);
595
obj->watched = watch;
596
obj->window = XtWindow(watch);
605
/* silence `empty compilation unit' warnings */
606
static void bar(void); static void foo() { bar(); } static void bar(void) { foo(); }