(mm) Balloon help in Motif application
Home | Software | Count
  ansi xterm
  web counter
  bangla font
MS Windows
Balloon Help in Motif

This article is about writing balloon help for X/Motif applications under X Window System. In many graphical applications you probably noticed, when the mouse pointer is moved over a graphic button and if the pointer remains on the button for a short period of time, a small rectangular window pops up with a one line help message. This kind of help is known as balloon help, bubble help or quick tip. Quick tip is particularly helpful for push buttons with graphic bitmaps, especially if the bitmaps are not very obvious. If you are a X/Motif programmer, you may have thought about adding this kind of help to your applications but was not quite sure how to do that. In this article, I will describe how to write balloon help for X/Motif applications. A C routine Qtip() (stands for Quick tip) with source code, documentation and examples is also supplied. This routine can be used to add quick tip to any X/Motif application.

Writing balloon help
As mentioned in the introduction, balloon help is useful for push buttons with graphic bitmaps. In order to display quick tip, when a mouse pointer is moved over the widget, we want to wait for a short amount of time, then pop-up the tip window. The pop-up delay is important because it is not desirable to pop windows when someone just tracks the mouse over the push buttons. So, how do we detect that the mouse pointer is moved over a widget or it is moved out from a widget? We can detect it by registering an X toolkit event handler for the widget. Note we also want to make sure that the quick tip is displayed only if the mouse pointer stays over the widget for a short period of time. We can do this by registering an X toolkit timeout routine for the widget.

We will write the Qtip library in C. Let declare an internal data structure with two members, an object of type Widget and a pointer to char:

    typedef struct _QtipData 
        Widget widget;  /* widget to bind help for */
        char   *text;   /* help text, can be NULL  */ 
    } QtipData;
Here, text is the help text (can be NULL) and widget is the push button or any other type of widget to bind the help for. Let's write the Qtip() function now. Note, you do not need to type the code described below, a library with source code (commented) and documentation is supplied.
  1	void Qtip(Widget widget,char *text)
  2	{
  3	    QtipData *data=(QtipData *)
  4	        XtNew(QtipData);
  6	    if (text != (char *) NULL)
  7	        data->text=XtNewString(text);
  8	    else
  9	        data->text=(char *) NULL;
  11	    data->widget=widget;
  13	    /*
  14	    ** check if the widget is a subclass of Core Widget class
  15	    */
  16        if (XtIsWidget(widget) == False)
  17        {
  18            return;
  19        }
  21        XtAddEventHandler(widget,
  22            EnterWindowMask | LeaveWindowMask,
  23            False,
  24            (XtEventHandler) QtipCb,
  25            (XtPointer) data);
  26    }
At line 3, we allocate enough memory for the structure. From line 6 to 11, we filled the members of the structure QtipData.

At line 16, we make sure that the widget passed to the function is not a gadget. If it is a gadget, the function will return silently. We can not use gadgets, because among other limitations, gadgets can not have event handlers.

At line 21, we registered an event handler for the widget. This event handler detects when the mouse pointer is moved in and out of the widget.

At line 22, EnterWindowMask | LeaveWindowMask indicates that the function QtipCb() (line 24) will be called when the mouse pointer enters or leave the widget area.

Notice (XtPointer) data at line 25, here we are passing our own data structure to the event handler routine QtipCb(). If you look at the prototype of the X toolkit function XtAddEventHandler(), you will notice that the 5th argument takes a data type of XtPointer. XtPointer is nothing but a void *, therefore, any type of data can be passed to the event handler routine through this argument. Now let's look at the event handler routine QtipCb().

1 static void QtipCb(Widget widget,XtPointer client_data,XEvent *event)
2 {
3    switch(event->type)
4    {
5       case EnterNotify:
6       {
7           if (!s_timerid)
8           {
9               timeout=1;
10              s_timerid=XtAppAddTimeOut(
11                   XtWidgetToApplicationContext(widget),
12                    QTIP_HELP_DELAY,
13                    (XtTimerCallbackProc) PopQtip,
14                    client_data);
15          }
16                break;
17      }
19      case LeaveNotify:
20      {
21          if (timeout == 1)
22          {
23              timeout=0;
24              XtRemoveTimeOut(s_timerid);
25              s_timerid=0L;
26          }
27          if (ShelpShellW != (Widget) NULL)
28          {
29             if (XtIsManaged(ShelpShellW))
30                  XtUnmanageChild(ShelpShellW);
31          }
32                break;
33      }
34   }
At line 3, we check for the type of events received. When the mouse pointer enters the widget, the code segment from line 5-17 will be executed.

At line 10, we register a timeout routine,that is a routine will be called after a certain time is elapsed.

At line 12, QTIP_HELP_DELAY indicates the delay in milli seconds (it is defined in qtip.h as 500 milli seconds or half a second in our case).

At line 13, PopQip is the function which is called as soon as the timeout (500 ms in our case) is reached. At line 14, we are again passing our own data structure to the function PopQtip().

The code segment from line 19-33 gets executed when the mouse pointer is moved out of the widget. At line 24, if timeout is still not reached, we remove the timeout. At line 29, if the tip window is up, we bring it down.

Now let us look at some segments of the function PopQtip(). This function does all the work of creating the help window and placing it properly.

  1   static void PopQtip(XtPointer client_data)
  2   {
  3       static XmFontList
  4           flist= NULL;
  6       XmString
  8           lab_str;
  10       Dimension
  11           lab_w,lab_h,w,h;
  13       Position
  14           rx,ry;
  16       QtipData
  17           *data;
  19       data=(QtipData *) client_data;
  20       s_timerid=0L;
  21       timeout=0;
  23       if (ShelpShellW != (Widget) NULL)
  24       {
  25           if (XtIsManaged(ShelpShellW))
  26               XtUnmanageChild(ShelpShellW);
  27       }
  29       /*
  30       ** first time
  31       */
  32       if (ShelpShellW == (Widget) NULL)
  33       {
  34           /*
  35           ** create the shell, set the XmNoverrideRedirect to True, so that
  36           ** the window manger won't intervene
  37           */
  38           ShelpShellW=XtVaCreatePopupShell("tipShell",
  39               topLevelShellWidgetClass,GetTopShell(data->widget),
  40               XmNoverrideRedirect, True,
  41               XmNresizePolicy,XmRESIZE_ANY,
  42               NULL);
  44           /*
  45           ** create the label
  46           ** we must make the label widget static.
  47           */
  48           SlabelW=XtVaCreateManagedWidget("tipLabel",
  49               xmLabelWidgetClass, ShelpShellW,
  50               NULL);
  51       }
  53       if (flist == NULL)
  54       {
  55           XtVaGetValues(SlabelW,
  56               XmNfontList,&flist,
  57               NULL);
  58       }
  60       if (data->text != (char *) NULL)
  61       {
  62           lab_str=XmStringCreateSimple(data->text);
  63       }
  64       else
  65           XtVaGetValues(data->widget,XmNlabelString,&lab_str,NULL);
  68       if (flist)
  69       {
  70           lab_w=XmStringWidth(flist,lab_str)+6;
  71           lab_h=XmStringHeight(flist,lab_str)+6;
  72       }
  74       /*
  75       ** determine widgets width & height
  76       */
  77       XtVaGetValues(data->widget,
  78           XmNwidth,&w,
  79           XmNheight,&h,
  80           NULL);
  82       /*
  83       ** we will pop the qtip window 2 pixels below the widget's edge and
  84       ** halfway from the widget's left edge. So, we will translate these
  85       ** coordinate from widget coordinates to root window coordinates
  86       */
  87       XtTranslateCoords(data->widget,
  88           (Position) (w/2),
  89           (Position) (h+2),
  90           &rx,&ry);
  92       XtVaSetValues(ShelpShellW,
  93           XmNx,rx,
  94           XmNy,ry,
  95           XmNwidth,lab_w,
  96           XmNheight,lab_h,
  97           NULL);
  98       XtVaSetValues(SlabelW,
  99           XmNlabelString,lab_str,
  100           NULL);
  102       /*
  103       ** don't need the string, free it
  104       */
  105       XmStringFree(lab_str);
  107       if (ShelpShellW != (Widget) NULL)
  108           XtManageChild(ShelpShellW);
  109   }
At line 38, we create a top level shell. Please look carefully at line 40, there, we set the resource XmNoverrideRedirect to True. This resource instructs the window manager not to intervene. If we do not set this resource to True, the window manager will put the title bar and frame around this shell (the whole process is called reparenting).

At line 48, we create a label widget on the shell widget.

At line 60, we check if a help text is passed, if so, create a motif compound string out of it (line 62). If no help text is passed, we use the push button's label text as the help message (line 65).

At line 70 and 71, we determine the width and height of the help message. Note the variable flist, it is the XmFontList of the label widget we created at line 48. Therefore, the font used in the label will be used for the help message as well. We will talk later how to specify a font for the label via a X resource.

Figure 1: Widget coordinates

At line 78 and 79, we obtain the width (w) and height (h) of the push button widget respectively. Please refer to Figure 1. In Figure 1, the window with message Open File is a tip window.

I decided to position the tip window at half way (w/2) from widget's left edget and 2 pixels below (h+2) the widget's bottom edge. At line 87, the Xt function XtTranslateCoords() is called to translate the widget coordinate (w/2,h+2) to root coordinate (rx,ry). We will use (rx,ry) to position the tip window.

At line 92, we call XtVaSetValue() to place the tip shell at the coordinate (rx,ry) and set the width and height of the tip shell as well.

At line 98, we set the help text to the label widget. If you remember, we created the label on top of the tip shell. At line 105, we free the help text (compound string), because motif library already made a copy of it when we set the text to the label at line 98. If we do not free it, it will create a "memory leak".

And finally we manage the tip shell widget at line 108, so that it shows up on our screen.

Qtip library
All the code described above is used in the Qtip library. It is written in in C and interfaces to Motif library. You will need a C compiler (ANSI or non-ANSI), X11 and at least Motif 1.1 in order to compile. The library has only one function call Qtip(). You can use the C file qtip.c directly but I think it is cleaner to use it as a library. You must include the header file qtip.h in your code before calling the function Qtip(). The synopsis of the routine is shown below:

    #include <qtip.h>
    void Qtip(Widget widget,char *text)

        widget    specifies the widget to bind help for
        text      specifies the quick tip text (can be NULL)

If text is passed as NULL, the library will try to use the widget's label text as help message. If help text is passed to the function, it can not be changed, therefore, it is better to pass NULL. This way, the help message can be changed any time just by changing the X resources.

Obtaining source
* Download

File: qtip1.1.tar.gz
Size: 27349 bytes
MD5 Checksum: 995d54367de5f0c257dd18883490f02c
Last updated: ?

qtip1.1.tar.gz is gzip compressed tar file. If you do not have gzip, it is available at ftp://prep.ai.mit.edu/pub/gnu/gzip-1.2.4.tar

Steps to compile
After down-loading the archive, at the shell prompt, type:

    guzip < qtip1.1.tar.gz | tar xvf -
To compile the library, at the shell prompt, type:
    cd qtip1.1
To compile the demo program, type:
    cd demo
To run the demo program, type:
Figure 2: Screen shot of qtip_demo

Here is an example using the Qtip library. It creates one push button, sets pixmap to it and adds the balloon help.

  ** an example of using Qtip() function call to add balloon help
  ** to compile:
  ** cc qtip_simple.c -o qtip_simple -I.. -L.. -lQtip -lXm -lXt -lX11

  #include <qtip.h>

  /* bitmap for push button */
  #define open_width 20
  #define open_height 20
  static char open_bits[] = {
     0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x30, 0x0a, 0xfc, 0x00, 0x0a,
     0xfc, 0x10, 0x0c, 0xff, 0x7f, 0x0f, 0xff, 0x7f, 0x00, 0xff, 0x7f, 0x00,
     0xff, 0x7f, 0x00, 0xff, 0xff, 0x0f, 0xbf, 0xaa, 0x0a, 0x3f, 0x55, 0x05,
     0xdf, 0xaa, 0x06, 0x4f, 0x55, 0x03, 0xaf, 0xaa, 0x02, 0xa7, 0xaa, 0x01,
     0x57, 0x55, 0x01, 0xab, 0xaa, 0x00, 0x55, 0xd5, 0x00, 0xff, 0x7f, 0x00};

  /* fallback resources */
  static char *app_defaults[]=
  "Foo*background: gray",
  "Foo*keyboardFocusPolicy: pointer",
  "Foo*tipLabel*fontList: -*-helvetica-medium-r-*-*-*-120-*-*-*-*-iso8859-*",
  "Foo*tipShell.borderWidth: 1",
  "Foo*tipShell.borderColor: red",
  "Foo*tipLabel.background: #FFFFCC",
  "Foo*tipLabel.foreground: black",

  int main(int argc,char **argv)




      unsigned int




      ** create toplevel shell, note the class of the app is Foo


      /* create the push button widget (not gadget) */
      pushbW=XtVaCreateManagedWidget("Open File",

      /* get the background and foreground color */

      /* create pixmap from X11 bitmap */
          (unsigned long) fg,
          (unsigned long) bg,

      /* set the pixmap to the button */
      if (pixmap != (Pixmap) NULL)
      ** now call Qtip() to bind balloon help.
      ** Note, we'r passing the text argument as NULL, so the
      ** function will use the label text of the button as 
      ** help message
      Qtip(pushbW, (char *) NULL);


      return (0); /* won't be here*/
X Resources
The appearance of the quick tip window can be controlled via several X resources. If no X resource is set, the appearance will be inherited from the application's top level shell and XmLabel.

Here is an example of an X resources file of an application whose class name is Foo.

  Foo*tipLabel*fontList: -*-helvetica-medium-r-*-*-*-120-*-*-*-*-iso8859-*
  Foo*tipShell.borderWidth: 1
  Foo*tipShell.borderColor: red
  Foo*tipLabel.background: #FFFFCC
  Foo*tipLabel.foreground: black

License - BSD

URL of this page: http://www.muquit.com/muquit/software/qtip/qtip.html

back Page updated: Sun Mar 31 01:59:56 2013 GMT   Copyright © 2013 muquit@muquit.com.