Back to index

tetex-bin  3.0
Tip.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2001-2004 Marcin Dalecki and others
00003  *
00004  * Permission is hereby granted, free of charge, to any person obtaining a copy
00005  * of this software and associated documentation files (the "Software"), to
00006  * deal in the Software without restriction, including without limitation the
00007  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
00008  * sell copies of the Software, and to permit persons to whom the Software is
00009  * furnished to do so, subject to the following conditions:
00010  *
00011  * The above copyright notice and this permission notice shall be included in
00012  * all copies or substantial portions of the Software.
00013  *
00014  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00015  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00016  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
00017  * PAUL VOJTA OR ANY OTHER AUTHOR OF THIS SOFTWARE BE LIABLE FOR ANY CLAIM,
00018  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
00019  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
00020  * OTHER DEALINGS IN THE SOFTWARE.
00021  *
00022  */
00023 
00024 #include "xdvi-config.h"
00025 #include "xdvi.h"
00026 
00027 #include "Tip.h"
00028 #include "xdvi-debug.h"
00029 #include "util.h"
00030 
00031 #ifdef MOTIF /* needed for `make depend' */
00032 
00033 #ifndef UNUSED
00034 #define UNUSED(x) ((void)(x))
00035 #endif
00036 
00037 #include <signal.h>
00038 #include <stdio.h>
00039 #include <stdlib.h>
00040 
00041 #include <X11/IntrinsicP.h>
00042 #include <X11/StringDefs.h>
00043 #include <Xm/XmP.h>
00044 
00045 #include <Xm/PushB.h>
00046 #include <X11/ShellP.h>
00047 
00048 #define TIP_NUM 1024 /* FIXME: remove this hard-coded value */
00049 
00050 typedef struct {
00051     int __empty;
00052 } TipClassPart;
00053 
00054 /* Full class record declaration */
00055 typedef struct _TipClassRec {
00056     CoreClassPart core_class;
00057     CompositeClassPart composite_class;
00058     ShellClassPart shell_class;
00059     OverrideShellClassPart override_shell_class;
00060     TipClassPart tip_class;
00061 } TipClassRec;
00062 
00063 /* keep information about each widget we are keeping track of */
00064 struct tip_context {
00065     Widget watched;         /* the widget we are watching */
00066     Window window;          /* Window of the object we are monitoring */
00067     TipWidget tw;           /* pointer back to the tip widget */
00068     Position abs_x, abs_y;
00069     Boolean active;         /* if False, tip is suppressed */
00070     char *text;                    /* text to display */
00071     short size;                    /* its size */
00072 };
00073 
00074 /* New fields for the widget record */
00075 typedef struct {
00076     /* resources */
00077     Pixel foreground;
00078     XFontSet fontset;              /* the font for text in box */
00079     int waitPeriod;         /* the delay resource - pointer must be
00080                              * in watched widget this long before
00081                              * help is popped up - in millisecs
00082                              */
00083     unsigned int cwp;              /* after help is popped down - normal
00084                              * wait period is cancelled for this
00085                              * period - in millisecs
00086                              */
00087 
00088     /* private state */
00089     struct tip_context twl[TIP_NUM];      /* list of widgets we are liteClue-ing */
00090     Cardinal nr_twl;        /* number of widgets we have attached */
00091     Dimension font_width;   /* width of '1' character */
00092     Dimension font_height;  /* height of font, rows are spaced using this */
00093     Dimension font_baseline;       /* relative displacement to baseline from top */
00094     GC text_GC;                    /* for drawing text */
00095     XtIntervalId tid;              /* holds timer id */
00096     Widget isup;            /* the help popup is up on this widget */
00097     Time HelpPopDownTime;   /* the time at which help popup
00098                                was popped down */
00099 } TipPart;
00100 
00101 /*
00102  * Full instance record declaration
00103  */
00104 typedef struct _TipRec {
00105     CorePart core;
00106     CompositePart composite;
00107     ShellPart shell;
00108     OverrideShellPart override;
00109     TipPart tip;
00110 } TipRec;
00111 
00112 #define CheckWidgetClass(routine) \
00113        if (XtClass(w) != tipWidgetClass) \
00114               wrong_widget(routine)
00115 
00116 static void initialize(Widget, Widget, ArgList, Cardinal *);
00117 static Boolean set_values(Widget, Widget, Widget, ArgList, Cardinal *);
00118 static void destroy(Widget);
00119 
00120 /*
00121  * Widget resources: eg to set tip box background: *tipShell.background: yellow.
00122  */
00123 
00124 #define offset(field) XtOffsetOf(TipRec, field)
00125 static XtResource resources[] = {
00126     {XtNforeground, XtCForeground,
00127      XtRPixel, sizeof(Pixel), offset(tip.foreground),
00128      XtRString, "black"},
00129     {XtNfontSet, XtCFontSet,
00130      XtRFontSet, sizeof(XFontSet), offset(tip.fontset),
00131      XtRString, "fixed"},
00132     {XmNwaitPeriod, XmCWaitPeriod,
00133      XtRInt, sizeof(int), offset(tip.waitPeriod),
00134      XtRImmediate, (XtPointer) 800},
00135     {XmNcancelWaitPeriod, XmCCancelWaitPeriod,
00136      XtRInt, sizeof(int), offset(tip.cwp),
00137      XtRImmediate, (XtPointer) 250},
00138 };
00139 #undef offset
00140 
00141 TipClassRec tipClassRec = {
00142     {
00143      /* superclass           */ (WidgetClass) & overrideShellClassRec,
00144      /* class_name           */ "Tip",
00145      /* widget size          */ (Cardinal) sizeof(TipRec),
00146      /* class_init           */ NULL,
00147      /* class_part_init      */ (XtWidgetClassProc) NULL,
00148      /* class_inited         */ (XtEnum) FALSE,
00149      /* initialize           */ (XtInitProc) initialize,
00150      /* init_hook            */ (XtArgsProc) NULL,
00151      /* realize              */ XtInheritRealize,
00152      /* actions              */ (XtActionList) 0,
00153      /* num_actions          */ (Cardinal) 0,
00154      /* resources            */ (XtResourceList) resources,
00155      /* num_resources        */ (Cardinal) XtNumber(resources),
00156      /* xrm_class            */ NULLQUARK,
00157      /* compress_motion      */ TRUE,
00158      /* compress_exposur     */ (XtEnum) FALSE,
00159      /* compress enterleave  */ TRUE,
00160      /* visibility_interest  */ FALSE,
00161      /* destroy              */ destroy,
00162      /* resize               */ XtInheritResize,
00163      /* expose,              */ XtInheritExpose,
00164      /* set_values           */ (XtSetValuesFunc) set_values,
00165      /* set_values_hook      */ (XtArgsFunc) NULL,
00166      /* set_values_almost    */ XtInheritSetValuesAlmost,
00167      /* get_values_hook      */ (XtArgsProc) NULL,
00168      /* accept_focus         */ XtInheritAcceptFocus,
00169      /* version              */ XtVersion,
00170      /* callback_private     */ (XtPointer) NULL,
00171      /* translations         */ XtInheritTranslations,
00172      /* query_geometry       */ XtInheritQueryGeometry,
00173      /* display_accelerator  */ XtInheritDisplayAccelerator,
00174      /* extension            */ (XtPointer) 0,
00175      },
00176     /* composite part */
00177     {
00178      /* geometry_manager     */ XtInheritGeometryManager,
00179      /* change_managed       */ XtInheritChangeManaged,
00180      /* insert_child         */ XtInheritInsertChild,
00181      /* delete_child         */ XtInheritDeleteChild,
00182      /* extension            */ NULL
00183      },
00184     /* Shell */
00185     {
00186      (XtPointer) NULL,
00187      },
00188     /* Override Shell */
00189     {
00190      0,
00191      },
00192     /* tip */
00193     {
00194      0,
00195      }
00196 };
00197 
00198 WidgetClass tipWidgetClass = (WidgetClass) & tipClassRec;
00199 
00200 /*
00201  * The font_information is derived.
00202  */
00203 static void compute_font_info(TipWidget cw)
00204 {
00205     XRectangle ink;
00206     XRectangle logical;
00207 
00208     if (!cw->tip.fontset)
00209        return;
00210     XmbTextExtents(cw->tip.fontset, "1", 1, &ink, &logical);
00211 
00212     cw->tip.font_baseline = -logical.y;   /* y offset from top to baseline, don't
00213                                       know why this is returned as
00214                                       negative */
00215 
00216     cw->tip.font_width = logical.width;   /* the width and height of the object */
00217 
00218     cw->tip.font_height = logical.height;
00219     TRACE_GUI((stderr, "baseline: %d, width: %d, height: %d\n",
00220               cw->tip.font_baseline, cw->tip.font_width, cw->tip.font_height));
00221 }
00222 
00223 /*
00224  * Creates the various graphic contexts we will need.
00225  */
00226 static void create_GC(TipWidget cw)
00227 {
00228     XtGCMask valuemask;
00229     XGCValues myXGCV;
00230 
00231     valuemask = GCForeground | GCBackground | GCFillStyle;
00232     myXGCV.foreground = cw->tip.foreground;
00233     myXGCV.background = cw->core.background_pixel;
00234     myXGCV.fill_style = FillSolid;
00235 
00236     if (cw->tip.text_GC)
00237        XtReleaseGC((Widget) cw, cw->tip.text_GC);
00238     cw->tip.text_GC = XtGetGC((Widget) cw, valuemask, &myXGCV);
00239 }
00240 
00241 /*
00242  * A routine to halt execution and force a core dump for debugging analysis
00243  * when a public routine is called with the wrong class of widget.
00244  */
00245 
00246 static void wrong_widget(char *routine)
00247 {
00248     XDVI_ABORT((stderr, "Wrong class of widget passed to %s", routine));
00249 }
00250 
00251 /*
00252  * Global list of shells for tips that are in use.
00253  */
00254 
00255 static TipWidget *shells = NULL;
00256 static int nr_shells = 0;
00257 
00258 /****************************************************************************
00259  * Widget Methods
00260  */
00261 
00262 static void initialize(Widget treq, Widget tnew, ArgList args,
00263                      Cardinal * nargs)
00264 {
00265     TipWidget tw = (TipWidget) tnew;
00266 
00267     UNUSED(treq);
00268     UNUSED(args);
00269     UNUSED(nargs);
00270     
00271     tw->tip.text_GC = NULL;
00272     tw->tip.isup = NULL;
00273     tw->tip.HelpPopDownTime = 0;
00274     tw->tip.tid = (XtIntervalId) 0;
00275     tw->tip.nr_twl = 0;
00276     compute_font_info(tw);
00277     create_GC(tw);
00278 
00279     /* Add to our list of tip shells.
00280      */
00281     if (!shells)
00282        shells = (TipWidget *)XtMalloc(sizeof(TipWidget));
00283     else
00284        shells = (TipWidget *)XtRealloc((char *)shells,
00285                                    sizeof(TipWidget) * (nr_shells + 1));
00286 
00287     shells[nr_shells++] = tw;
00288 }
00289 
00290 static Boolean set_values(Widget _current, Widget _request, Widget _new,
00291                        ArgList args, Cardinal * nargs)
00292 {
00293     TipWidget cw_new = (TipWidget) _new;
00294     TipWidget cw_cur = (TipWidget) _current;
00295 
00296     UNUSED(_request);
00297     UNUSED(args);
00298     UNUSED(nargs);
00299     
00300     /* values of cw_new->tip.cwp and
00301        cw_new->tip.waitPeriod are accepted without checking */
00302 
00303     if (cw_new->tip.foreground != cw_cur->tip.foreground
00304        || cw_new->core.background_pixel !=
00305        cw_cur->core.background_pixel) {
00306        create_GC(cw_new);
00307     }
00308     return FALSE;
00309 }
00310 
00311 static void destroy(Widget w)
00312 {
00313     TipWidget tw = (TipWidget) w;
00314     int i;
00315     Boolean copy = False;
00316 
00317     /* Remove this tip shell from our global list.
00318      */
00319     for (i = 0; i < nr_shells; ++i) {
00320        if (shells[i] == tw) {
00321            copy = True;
00322            --nr_shells;
00323        }
00324        if (copy && nr_shells)
00325            shells[i] = shells[i + 1];
00326     }
00327     if (!nr_shells) {
00328        XtFree((char *) shells);
00329        shells = NULL;
00330     }
00331 }
00332 
00333 /****************************************************************************
00334  * Event handlers
00335  */
00336 
00337 /* callback to popup the tip window
00338  */
00339 static void timeout_event(XtPointer client_data, XtIntervalId *id)
00340 {
00341 #define HBorderPix 3
00342 #define VBorderPix 3
00343     struct tip_context *obj = (struct tip_context *) client_data;
00344     TipWidget tw = obj->tw;
00345     Position abs_x, abs_y;
00346     int ptr_x, ptr_y;
00347 
00348     XRectangle ink;
00349     XRectangle logical;
00350     Position w_height, w_width;
00351     Widget w;
00352     
00353     UNUSED(id);
00354 
00355     TRACE_GUI((stderr, "timeout called!"));
00356     
00357     if (tw->tip.tid == (XtIntervalId) 0)
00358        return;                     /* timeout was removed but callback happened
00359                                anyway */
00360 
00361     tw->tip.tid = (XtIntervalId) 0;
00362     if (obj->active == False)
00363        return;
00364 
00365     w = obj->watched;
00366 
00367     if (!XtIsManaged(w))
00368        return;
00369 
00370     { /* perform additional check that pointer is really still over the widget;
00371         else, tooltips will sometimes pop up if window had received an Enter
00372         event before (for some reason, not all Enters are followed by Leaves).
00373         This is especially apparent when running xdvi from a remote display over
00374         a slow connection.
00375        */
00376        Window root, child;
00377        int root_x, root_y;
00378        unsigned int keys_buttons;
00379        if (!XQueryPointer(DISP, RootWindowOfScreen(SCRN), &root, &child,
00380                         &root_x, &root_y, &ptr_x, &ptr_y, &keys_buttons))
00381            return;
00382 
00383        TRACE_GUI((stderr, "Pointerlocate: %d, %d", root_x, root_y));
00384        
00385        XtVaGetValues(w, XtNheight, &w_height, XtNwidth, &w_width, NULL);
00386        XtTranslateCoords(w, 0, 0, &abs_x, &abs_y);
00387        
00388        TRACE_GUI((stderr, "Window: %d,%d - %d,%d",
00389                  abs_x, abs_y, abs_x + w_width, abs_y + w_height));
00390        
00391        if (root_x < abs_x || root_x > abs_x + w_width
00392            || root_y < abs_y || root_y > abs_y + w_height) {
00393            TRACE_GUI((stderr, "not really over toolbutton - returning!"));
00394            return;
00395        }
00396     }
00397 
00398     /* position just below the pointer
00399      * (NOT the widget, in case the widget is large!)
00400      */
00401     ptr_y += 20;
00402 /*     abs_x += w_width / 2; */
00403 /*     abs_y += w_height; */
00404     
00405     XmbTextExtents(tw->tip.fontset, obj->text, obj->size, &ink, &logical);
00406 
00407     XtRealizeWidget((Widget)tw); /* so that setting the size etc. works */
00408        
00409     XtResizeWidget((Widget) tw,
00410                  2 * HBorderPix + logical.width,
00411                  2 * VBorderPix + tw->tip.font_height,
00412                  tw->core.border_width);
00413     TRACE_GUI((stderr, "Popup size: %d x %d (hborder: %d, vborder: %d)\n",
00414               2 * HBorderPix + logical.width, 2 * VBorderPix + tw->tip.font_height,
00415               HBorderPix, VBorderPix));
00416     XtMoveWidget((Widget)tw, ptr_x, ptr_y);
00417 
00418     XtPopup((Widget) tw, XtGrabNone);
00419     tw->tip.isup = obj->watched;
00420 
00421     XmbDrawImageString(XtDisplay((Widget) tw),
00422                      XtWindow((Widget) tw),
00423                      tw->tip.fontset,
00424                      tw->tip.text_GC,
00425                      HBorderPix,
00426                      VBorderPix + tw->tip.font_baseline,
00427                      obj->text, obj->size);
00428 }
00429 
00430 /*
00431  * Pointer enters watched widget, set a timer to popup the help.
00432  */
00433 static void enter(struct tip_context *obj, XEvent * xevent,
00434                 XtAppContext app)
00435 {
00436     TipWidget tw = obj->tw;
00437     XEnterWindowEvent *event = &xevent->xcrossing;
00438     int current_waitPeriod;
00439 
00440     /* this doesn't help against the Enter/Leave problem mentioned above,
00441        so it's not related to Widget creation ... */
00442     if (!XtIsManaged(obj->watched)) {
00443        TRACE_GUI((stderr, "%s:%d: Not yet managed!", __FILE__, __LINE__));
00444        return;
00445     }
00446     
00447     TRACE_GUI((stderr, "%s:%d: Enter!", __FILE__, __LINE__));
00448     
00449     if (obj->active == False)
00450        return;
00451 
00452     /* check for two enters in a row - happens when widget is
00453        exposed under a pop-up */
00454     if (tw->tip.tid != (XtIntervalId) 0)
00455        return;
00456 
00457     if (event->mode != NotifyNormal)
00458        return;
00459 
00460     /* it seems that this makes the tooltips somewhat unpredictable (they
00461        don't show when hovering fast over several buttons, then staying on
00462        one button); disabled this for the time being. */
00463 /*     if ((event->time - tw->tip.HelpPopDownTime) > tw->tip.cwp) */
00464 /*     current_waitPeriod = tw->tip.waitPeriod; */
00465 /*     else */
00466 /*     current_waitPeriod = 0; */
00467 
00468 /*     current_waitPeriod = tw->tip.waitPeriod; */
00469     current_waitPeriod =  resource.tooltips_wait_period;
00470     if (current_waitPeriod >= 0) {
00471        tw->tip.tid = XtAppAddTimeOut(app, current_waitPeriod, timeout_event,
00472                                   (XtPointer) obj);
00473     }
00474 }
00475 
00476 /*
00477  * Remove timer if its pending. Then popdown help.
00478  */
00479 static void leave(struct tip_context *obj, XEvent * xevent)
00480 {
00481     TipWidget tw = obj->tw;
00482     XEnterWindowEvent *event = &xevent->xcrossing;
00483 
00484     TRACE_GUI((stderr, "%s:%d: Leave!", __FILE__, __LINE__));
00485     
00486     if (tw->tip.tid != (XtIntervalId) 0) {
00487        if (globals.debug & DBG_EVENT)
00488            fprintf(stderr, "%s:%d: removing timeout %ld\n", __FILE__, __LINE__, tw->tip.tid);
00489        XtRemoveTimeOut(tw->tip.tid);
00490        tw->tip.tid = (XtIntervalId) 0;
00491     }
00492 
00493     if (obj->active == False)
00494        return;
00495 
00496     if (tw->tip.isup) {
00497        XtPopdown((Widget) tw);
00498        tw->tip.isup = NULL;
00499        tw->tip.HelpPopDownTime = event->time;
00500     }
00501 }
00502 
00503 /****************************************************************************
00504  * Public interface implementation.
00505  */
00506 
00507 void TipAppHandle(XtAppContext app, XEvent *event)
00508 {
00509     int i;
00510 
00511     if (!(event->type == EnterNotify
00512          || event->type == MotionNotify
00513          || event->type == LeaveNotify
00514          || event->type == ButtonPress)) {
00515        return;
00516     }
00517 
00518     for (i = 0; i < nr_shells; ++i) {
00519        unsigned int j;
00520 
00521        for (j = 0; j < shells[i]->tip.nr_twl; ++j) {
00522            if (event->xany.window == shells[i]->tip.twl[j].window) {
00523               if (event->type == EnterNotify)
00524                   enter(shells[i]->tip.twl + j, event, app);
00525               if (event->xany.type == LeaveNotify
00526                   || event->xany.type == MotionNotify /* FIXME: this doesn' work? */
00527                   /* might be useful to popdown tip when mouse is moved */
00528                   || event->xany.type == ButtonPress) {
00529                   leave(shells[i]->tip.twl + j, event);
00530               }
00531            }
00532        }
00533     }
00534 }
00535 
00536 /*
00537  * This has to replace the XtAppMainLoop in the application using
00538  * tooltips.
00539  */
00540 void TipAppMainLoop(XtAppContext app)
00541 {
00542     XEvent event;
00543 
00544     for (;;) {
00545        XtAppNextEvent(app, &event);
00546        TipAppHandle(app, &event);
00547        XtDispatchEvent(&event);
00548     }
00549 }
00550 
00551 /*
00552  * Add a widget to be watched for tooltips.
00553  *
00554  * This function must be called after the widget has been realized!
00555  * Further on please make sure that this function will not be called twice
00556  * for one button!
00557  *
00558  * w            - tip widget
00559  * watch        - the widget to give tips for
00560  * text         - pointer to tip text
00561  */
00562 void TipAddWidget(Widget w, Widget watch, const String text)
00563 {
00564 #define ROUTINE "TipAddWidget"
00565     TipWidget tw = (TipWidget) w;
00566     int i;
00567 
00568     CheckWidgetClass(ROUTINE);     /* make sure we are called with a tip widget */
00569 
00570     /* Make internal resource available via resource.tooltips_wait_period(_bak) and
00571      * resource.show_tooltips.
00572      */
00573     resource.tooltips_wait_period_bak = resource.tooltips_wait_period = ABS(tw->tip.waitPeriod);
00574     if (tw->tip.waitPeriod < 0) {
00575        resource.show_tooltips = False;
00576     }
00577     else if (!resource.show_tooltips) {
00578        if (resource.tooltips_wait_period == 0)
00579            resource.tooltips_wait_period = -1;
00580        else
00581            resource.tooltips_wait_period = -resource.tooltips_wait_period;
00582     }
00583     
00584     for (i = 0; i < nr_shells; ++i)
00585        if (shells[i] == tw) {
00586            struct tip_context *obj;
00587 
00588            if (tw->tip.nr_twl >= TIP_NUM) {
00589               XDVI_FATAL((stderr, "Too many tip widgets, cannot add new tip"));
00590               return;
00591            }
00592            obj = tw->tip.twl + tw->tip.nr_twl;
00593            obj->text = XtNewString(text);
00594            obj->size = strlen(text);
00595            obj->watched = watch;
00596            obj->window = XtWindow(watch);
00597            obj->active = True;
00598            obj->tw = tw;
00599            tw->tip.nr_twl++;
00600        }
00601 #undef ROUTINE
00602 }
00603 
00604 #else
00605 /* silence `empty compilation unit' warnings */
00606 static void bar(void); static void foo() { bar(); } static void bar(void) { foo(); }
00607 #endif /* MOTIF */