^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
BE ENGINEERING INSIGHTS: Tool Tips
By Robert Polic -- <robert@be.com>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Windows has Tool Tips, Macintosh has Balloon Help, and the
BeOS has, er, well, up to this point, nothing, in the way of
context-sensitive help. To address this shortcoming I've
written a ToolTip class that's easily integrated into a Be
application. The Be version of tool tips behaves more like a
Windows Tool Tip than a Macintosh Balloon. In other words,
as you drag the cursor across different areas of a window, a
small window pops up -- after a settable delay -- below and
to the right of the cursor with a one-line description of
the UI element. The tip is displayed until the cursor is
moved again, the window or application is no longer active,
or the settable hold-time is reached.

Source for the TToolTip class and demo application can be
found at
<ftp://ftp.be.com/pub/samples/interface_kit/ToolTips.zip>.

Integrating this class with your application is fairly
straightforward. You'll first need to instantiate a TToolTip
object. In the demo application provided, you do this in the
constructor of the application and the pointer to the object
is cached:

 ToolTipDemoApp::ToolTipDemoApp()
                :BApplication("application/x-vnd.Be-Help"),
                 fPrefWindow(NULL)
   {
        // set up a rectangle, instantiate and show a new
        window (new ToolTipDemoWindow(BRect(100, 80, 300,
        152)))->Show();
        // instantiate the tooltip window but don't show it
        fToolTip = new TToolTip();
    }

The next step is to override the MessageReceived method of
the application object to forward any B_SOME_APP_ACTIVATED,
eToolTipStart or eToolTipStop messages to the TToolTip class:

    void ToolTipDemoApp::MessageReceived(BMessage *msg)
    {
        switch (msg->what) {
            // these messages need to be forwarded to the
               TToolTip
                        class
            case B_SOME_APP_ACTIVATED:
            case eToolTipStart:
            case eToolTipStop:
                fToolTip->PostMessage(msg);
                break;
        }
    }

The last step is to replace any BControl objects (BButtons,
BColorControl, BSlider, etc.) with the TToolTipControls
equivalents. These objects look identical to the base class
objects with the exception of taking a "tip" parameter after
all required parameters:

    view->AddChild(new TToolTipButton(BRect(10, 44, 90, 44),
"tool_tip_button",
        "Quit", new BMessage(B_QUIT_REQUESTED), "Click this
button to quit"));
    view->AddChild(new TToolTipButton(BRect(110, 44, 190,
44),
"tool_tip_button",
        "Preferences", new BMessage('PREF'), "Click this
button to set ToolTip preferences"));

Views other than BControls (BBox, BMenuField, BListView,
BView, etc.) can also take advantage of the TToolTip class.
To do this you'll need to sub-class the view you're
interested in and override the MouseMoved method. In the
constructor of the view, cache the tip string:

    TToolTipButton::TToolTipButton(BRect rect,
                           const char *name,
                           const char *label,
                           BMessage *msg,
                           const char *tip,
                           uint32 resize_mask,
                           uint32 flags)
               :BButton(rect, name, label, msg, resize_mask,
flags),
                fIn(false)
    {
        // cache the ToolTip string
        fTip = (char *)malloc(strlen(tip) + 1);
        strcpy(fTip, tip);
    }

In the view destructor, stop displaying the tip if it's
currently being displayed and free the cached tip string:

    TToolTipButton::~TToolTipButton()
    {
        // kill off any current ToolTip
        if (fIn)
            be_app->PostMessage(eToolTipStop);
        // free cached ToolTip
        free(fTip);
    }

In the MouseMoved method, determine whether the cursor is
moving into or out of the view. If it's moving in, build a
message that contains the start point and bounds of the view
(both in screen coordinates) and the tip string, and post it
to the application:

// if mouse has moved into our view, our window is active
// and it previously wasn't in, send a message to start the
   ToolTip if ((Bounds().Contains(where)) &&
(Window()->IsActive()))
{
		if (!fIn) {
			BMessage	msg(eToolTipStart);

			msg.AddPoint("start", ConvertToScreen(where));
			msg.AddRect("bounds",
ConvertToScreen(Bounds()));
			msg.AddString("string", fTip);
			be_app->PostMessage(&msg);
			fIn = true;
		}
	}

If the mouse is moving out of the view, post an eToolTipStop
message to the application:


    // otherwise stop the message
    else if (fIn) {
        be_app->PostMessage(eToolTipStop);
        fIn = false;
    }p);
        fIn = false;
    }

The TToollTip class is fully customizable as far as the font
(and font size) used, the delay before the tip is displayed,
the duration the tip is displayed, and most important,
whether tool tips are enabled or not. To get the current
settings:

    tool_tip_settings   settings;

    fToolTip->GetSettings(&settings);

To apply the new settings:

    fToolTip->SetSettings(settings);

tool_tip_settings is the following structure:

    struct tool_tip_settings {
        bool        enabled;       // flag whether
tips are enables or not
        bool        one_time_only; // flag to only display
the tip once per time in view
        bigtime_t   delay;         // delay before tip is
shown in microseconds
        bigtime_t   hold;          // amount of time tip
is displayed in microseconds
        BFont       font;          // font tip is drawn in
    };

Oh yeah, if you're going to add tool-tips to your
application, please add a setting in your preference panel
to enable or disable them.
