xref: /vim-8.2.3635/src/gui_beval.c (revision cf2d8dee)
1 /* vi:set ts=8 sts=4 sw=4:
2  *
3  * VIM - Vi IMproved	by Bram Moolenaar
4  *			Visual Workshop integration by Gordon Prieur
5  *
6  * Do ":help uganda"  in Vim to read copying and usage conditions.
7  * Do ":help credits" in Vim to see a list of people who contributed.
8  * See README.txt for an overview of the Vim source code.
9  */
10 
11 #include "vim.h"
12 
13 #if defined(FEAT_BEVAL) || defined(PROTO)
14 
15 /*
16  * Common code, invoked when the mouse is resting for a moment.
17  */
18     void
19 general_beval_cb(BalloonEval *beval, int state UNUSED)
20 {
21 #ifdef FEAT_EVAL
22     win_T	*wp;
23     int		col;
24     int		use_sandbox;
25     linenr_T	lnum;
26     char_u	*text;
27     static char_u  *result = NULL;
28     long	winnr = 0;
29     char_u	*bexpr;
30     buf_T	*save_curbuf;
31     size_t	len;
32 # ifdef FEAT_WINDOWS
33     win_T	*cw;
34 # endif
35 #endif
36     static int	recursive = FALSE;
37 
38     /* Don't do anything when 'ballooneval' is off, messages scrolled the
39      * windows up or we have no beval area. */
40     if (!p_beval || balloonEval == NULL || msg_scrolled > 0)
41 	return;
42 
43     /* Don't do this recursively.  Happens when the expression evaluation
44      * takes a long time and invokes something that checks for CTRL-C typed. */
45     if (recursive)
46 	return;
47     recursive = TRUE;
48 
49 #ifdef FEAT_EVAL
50     if (get_beval_info(balloonEval, TRUE, &wp, &lnum, &text, &col) == OK)
51     {
52 	bexpr = (*wp->w_buffer->b_p_bexpr == NUL) ? p_bexpr
53 						    : wp->w_buffer->b_p_bexpr;
54 	if (*bexpr != NUL)
55 	{
56 # ifdef FEAT_WINDOWS
57 	    /* Convert window pointer to number. */
58 	    for (cw = firstwin; cw != wp; cw = cw->w_next)
59 		++winnr;
60 # endif
61 
62 	    set_vim_var_nr(VV_BEVAL_BUFNR, (long)wp->w_buffer->b_fnum);
63 	    set_vim_var_nr(VV_BEVAL_WINNR, winnr);
64 	    set_vim_var_nr(VV_BEVAL_LNUM, (long)lnum);
65 	    set_vim_var_nr(VV_BEVAL_COL, (long)(col + 1));
66 	    set_vim_var_string(VV_BEVAL_TEXT, text, -1);
67 	    vim_free(text);
68 
69 	    /*
70 	     * Temporarily change the curbuf, so that we can determine whether
71 	     * the buffer-local balloonexpr option was set insecurely.
72 	     */
73 	    save_curbuf = curbuf;
74 	    curbuf = wp->w_buffer;
75 	    use_sandbox = was_set_insecurely((char_u *)"balloonexpr",
76 				 *curbuf->b_p_bexpr == NUL ? 0 : OPT_LOCAL);
77 	    curbuf = save_curbuf;
78 	    if (use_sandbox)
79 		++sandbox;
80 	    ++textlock;
81 
82 	    vim_free(result);
83 	    result = eval_to_string(bexpr, NULL, TRUE);
84 
85 	    /* Remove one trailing newline, it is added when the result was a
86 	     * list and it's hardly every useful.  If the user really wants a
87 	     * trailing newline he can add two and one remains. */
88 	    if (result != NULL)
89 	    {
90 		len = STRLEN(result);
91 		if (len > 0 && result[len - 1] == NL)
92 		    result[len - 1] = NUL;
93 	    }
94 
95 	    if (use_sandbox)
96 		--sandbox;
97 	    --textlock;
98 
99 	    set_vim_var_string(VV_BEVAL_TEXT, NULL, -1);
100 	    if (result != NULL && result[0] != NUL)
101 	    {
102 		gui_mch_post_balloon(beval, result);
103 		recursive = FALSE;
104 		return;
105 	    }
106 	}
107     }
108 #endif
109 #ifdef FEAT_NETBEANS_INTG
110     if (bevalServers & BEVAL_NETBEANS)
111 	netbeans_beval_cb(beval, state);
112 #endif
113 #ifdef FEAT_SUN_WORKSHOP
114     if (bevalServers & BEVAL_WORKSHOP)
115 	workshop_beval_cb(beval, state);
116 #endif
117 
118     recursive = FALSE;
119 }
120 
121 /* on Win32 only get_beval_info() is required */
122 #if !defined(FEAT_GUI_W32) || defined(PROTO)
123 
124 #ifdef FEAT_GUI_GTK
125 # if GTK_CHECK_VERSION(3,0,0)
126 #  include <gdk/gdkkeysyms-compat.h>
127 # else
128 #  include <gdk/gdkkeysyms.h>
129 # endif
130 # include <gtk/gtk.h>
131 #else
132 # include <X11/keysym.h>
133 # ifdef FEAT_GUI_MOTIF
134 #  include <Xm/PushB.h>
135 #  include <Xm/Separator.h>
136 #  include <Xm/List.h>
137 #  include <Xm/Label.h>
138 #  include <Xm/AtomMgr.h>
139 #  include <Xm/Protocols.h>
140 # else
141    /* Assume Athena */
142 #  include <X11/Shell.h>
143 #  ifdef FEAT_GUI_NEXTAW
144 #   include <X11/neXtaw/Label.h>
145 #  else
146 #   include <X11/Xaw/Label.h>
147 #  endif
148 # endif
149 #endif
150 
151 #include "gui_beval.h"
152 
153 #ifndef FEAT_GUI_GTK
154 extern Widget vimShell;
155 
156 /*
157  * Currently, we assume that there can be only one BalloonEval showing
158  * on-screen at any given moment.  This variable will hold the currently
159  * showing BalloonEval or NULL if none is showing.
160  */
161 static BalloonEval *current_beval = NULL;
162 #endif
163 
164 #ifdef FEAT_GUI_GTK
165 static void addEventHandler(GtkWidget *, BalloonEval *);
166 static void removeEventHandler(BalloonEval *);
167 static gint target_event_cb(GtkWidget *, GdkEvent *, gpointer);
168 static gint mainwin_event_cb(GtkWidget *, GdkEvent *, gpointer);
169 static void pointer_event(BalloonEval *, int, int, unsigned);
170 static void key_event(BalloonEval *, unsigned, int);
171 # if GTK_CHECK_VERSION(3,0,0)
172 static gboolean timeout_cb(gpointer);
173 # else
174 static gint timeout_cb(gpointer);
175 # endif
176 # if GTK_CHECK_VERSION(3,0,0)
177 static gboolean balloon_draw_event_cb (GtkWidget *, cairo_t *, gpointer);
178 # else
179 static gint balloon_expose_event_cb (GtkWidget *, GdkEventExpose *, gpointer);
180 # endif
181 #else
182 static void addEventHandler(Widget, BalloonEval *);
183 static void removeEventHandler(BalloonEval *);
184 static void pointerEventEH(Widget, XtPointer, XEvent *, Boolean *);
185 static void pointerEvent(BalloonEval *, XEvent *);
186 static void timerRoutine(XtPointer, XtIntervalId *);
187 #endif
188 static void cancelBalloon(BalloonEval *);
189 static void requestBalloon(BalloonEval *);
190 static void drawBalloon(BalloonEval *);
191 static void undrawBalloon(BalloonEval *beval);
192 static void createBalloonEvalWindow(BalloonEval *);
193 
194 
195 
196 /*
197  * Create a balloon-evaluation area for a Widget.
198  * There can be either a "mesg" for a fixed string or "mesgCB" to generate a
199  * message by calling this callback function.
200  * When "mesg" is not NULL it must remain valid for as long as the balloon is
201  * used.  It is not freed here.
202  * Returns a pointer to the resulting object (NULL when out of memory).
203  */
204     BalloonEval *
205 gui_mch_create_beval_area(
206     void	*target,
207     char_u	*mesg,
208     void	(*mesgCB)(BalloonEval *, int),
209     void	*clientData)
210 {
211 #ifndef FEAT_GUI_GTK
212     char	*display_name;	    /* get from gui.dpy */
213     int		screen_num;
214     char	*p;
215 #endif
216     BalloonEval	*beval;
217 
218     if (mesg != NULL && mesgCB != NULL)
219     {
220 	EMSG(_("E232: Cannot create BalloonEval with both message and callback"));
221 	return NULL;
222     }
223 
224     beval = (BalloonEval *)alloc(sizeof(BalloonEval));
225     if (beval != NULL)
226     {
227 #ifdef FEAT_GUI_GTK
228 	beval->target = GTK_WIDGET(target);
229 	beval->balloonShell = NULL;
230 	beval->timerID = 0;
231 #else
232 	beval->target = (Widget)target;
233 	beval->balloonShell = NULL;
234 	beval->timerID = (XtIntervalId)NULL;
235 	beval->appContext = XtWidgetToApplicationContext((Widget)target);
236 #endif
237 	beval->showState = ShS_NEUTRAL;
238 	beval->x = 0;
239 	beval->y = 0;
240 	beval->msg = mesg;
241 	beval->msgCB = mesgCB;
242 	beval->clientData = clientData;
243 
244 	/*
245 	 * Set up event handler which will keep its eyes on the pointer,
246 	 * and when the pointer rests in a certain spot for a given time
247 	 * interval, show the beval.
248 	 */
249 	addEventHandler(beval->target, beval);
250 	createBalloonEvalWindow(beval);
251 
252 #ifndef FEAT_GUI_GTK
253 	/*
254 	 * Now create and save the screen width and height. Used in drawing.
255 	 */
256 	display_name = DisplayString(gui.dpy);
257 	p = strrchr(display_name, '.');
258 	if (p != NULL)
259 	    screen_num = atoi(++p);
260 	else
261 	    screen_num = 0;
262 	beval->screen_width = DisplayWidth(gui.dpy, screen_num);
263 	beval->screen_height = DisplayHeight(gui.dpy, screen_num);
264 #endif
265     }
266 
267     return beval;
268 }
269 
270 #if defined(FEAT_BEVAL_TIP) || defined(PROTO)
271 /*
272  * Destroy a balloon-eval and free its associated memory.
273  */
274     void
275 gui_mch_destroy_beval_area(BalloonEval *beval)
276 {
277     cancelBalloon(beval);
278     removeEventHandler(beval);
279     /* Children will automatically be destroyed */
280 # ifdef FEAT_GUI_GTK
281     gtk_widget_destroy(beval->balloonShell);
282 # else
283     XtDestroyWidget(beval->balloonShell);
284 # endif
285     vim_free(beval);
286 }
287 #endif
288 
289     void
290 gui_mch_enable_beval_area(BalloonEval *beval)
291 {
292     if (beval != NULL)
293 	addEventHandler(beval->target, beval);
294 }
295 
296     void
297 gui_mch_disable_beval_area(BalloonEval *beval)
298 {
299     if (beval != NULL)
300 	removeEventHandler(beval);
301 }
302 
303 #if defined(FEAT_BEVAL_TIP) || defined(PROTO)
304 /*
305  * This function returns the BalloonEval * associated with the currently
306  * displayed tooltip.  Returns NULL if there is no tooltip currently showing.
307  *
308  * Assumption: Only one tooltip can be shown at a time.
309  */
310     BalloonEval *
311 gui_mch_currently_showing_beval(void)
312 {
313     return current_beval;
314 }
315 #endif
316 #endif /* !FEAT_GUI_W32 */
317 
318 #if defined(FEAT_SUN_WORKSHOP) || defined(FEAT_NETBEANS_INTG) \
319     || defined(FEAT_EVAL) || defined(PROTO)
320 /*
321  * Get the text and position to be evaluated for "beval".
322  * If "getword" is true the returned text is not the whole line but the
323  * relevant word in allocated memory.
324  * Returns OK or FAIL.
325  */
326     int
327 get_beval_info(
328     BalloonEval	*beval,
329     int		getword,
330     win_T	**winp,
331     linenr_T	*lnump,
332     char_u	**textp,
333     int		*colp)
334 {
335     win_T	*wp;
336     int		row, col;
337     char_u	*lbuf;
338     linenr_T	lnum;
339 
340     *textp = NULL;
341     row = Y_2_ROW(beval->y);
342     col = X_2_COL(beval->x);
343 #ifdef FEAT_WINDOWS
344     wp = mouse_find_win(&row, &col);
345 #else
346     wp = firstwin;
347 #endif
348     if (wp != NULL && row < wp->w_height && col < W_WIDTH(wp))
349     {
350 	/* Found a window and the cursor is in the text.  Now find the line
351 	 * number. */
352 	if (!mouse_comp_pos(wp, &row, &col, &lnum))
353 	{
354 	    /* Not past end of the file. */
355 	    lbuf = ml_get_buf(wp->w_buffer, lnum, FALSE);
356 	    if (col <= win_linetabsize(wp, lbuf, (colnr_T)MAXCOL))
357 	    {
358 		/* Not past end of line. */
359 		if (getword)
360 		{
361 		    /* For Netbeans we get the relevant part of the line
362 		     * instead of the whole line. */
363 		    int		len;
364 		    pos_T	*spos = NULL, *epos = NULL;
365 
366 		    if (VIsual_active)
367 		    {
368 			if (lt(VIsual, curwin->w_cursor))
369 			{
370 			    spos = &VIsual;
371 			    epos = &curwin->w_cursor;
372 			}
373 			else
374 			{
375 			    spos = &curwin->w_cursor;
376 			    epos = &VIsual;
377 			}
378 		    }
379 
380 		    col = vcol2col(wp, lnum, col);
381 
382 		    if (VIsual_active
383 			    && wp->w_buffer == curwin->w_buffer
384 			    && (lnum == spos->lnum
385 				? col >= (int)spos->col
386 				: lnum > spos->lnum)
387 			    && (lnum == epos->lnum
388 				? col <= (int)epos->col
389 				: lnum < epos->lnum))
390 		    {
391 			/* Visual mode and pointing to the line with the
392 			 * Visual selection: return selected text, with a
393 			 * maximum of one line. */
394 			if (spos->lnum != epos->lnum || spos->col == epos->col)
395 			    return FAIL;
396 
397 			lbuf = ml_get_buf(curwin->w_buffer, VIsual.lnum, FALSE);
398 			len = epos->col - spos->col;
399 			if (*p_sel != 'e')
400 			    len += MB_PTR2LEN(lbuf + epos->col);
401 			lbuf = vim_strnsave(lbuf + spos->col, len);
402 			lnum = spos->lnum;
403 			col = spos->col;
404 		    }
405 		    else
406 		    {
407 			/* Find the word under the cursor. */
408 			++emsg_off;
409 			len = find_ident_at_pos(wp, lnum, (colnr_T)col, &lbuf,
410 					FIND_IDENT + FIND_STRING + FIND_EVAL);
411 			--emsg_off;
412 			if (len == 0)
413 			    return FAIL;
414 			lbuf = vim_strnsave(lbuf, len);
415 		    }
416 		}
417 
418 		*winp = wp;
419 		*lnump = lnum;
420 		*textp = lbuf;
421 		*colp = col;
422 		beval->ts = wp->w_buffer->b_p_ts;
423 		return OK;
424 	    }
425 	}
426     }
427 
428     return FAIL;
429 }
430 
431 # if !defined(FEAT_GUI_W32) || defined(PROTO)
432 
433 /*
434  * Show a balloon with "mesg".
435  */
436     void
437 gui_mch_post_balloon(BalloonEval *beval, char_u *mesg)
438 {
439     beval->msg = mesg;
440     if (mesg != NULL)
441 	drawBalloon(beval);
442     else
443 	undrawBalloon(beval);
444 }
445 # endif /* FEAT_GUI_W32 */
446 #endif /* FEAT_SUN_WORKSHOP || FEAT_NETBEANS_INTG || PROTO */
447 
448 #if !defined(FEAT_GUI_W32) || defined(PROTO)
449 #if defined(FEAT_BEVAL_TIP) || defined(PROTO)
450 /*
451  * Hide the given balloon.
452  */
453     void
454 gui_mch_unpost_balloon(BalloonEval *beval)
455 {
456     undrawBalloon(beval);
457 }
458 #endif
459 
460 #ifdef FEAT_GUI_GTK
461 /*
462  * We can unconditionally use ANSI-style prototypes here since
463  * GTK+ requires an ANSI C compiler anyway.
464  */
465     static void
466 addEventHandler(GtkWidget *target, BalloonEval *beval)
467 {
468     /*
469      * Connect to the generic "event" signal instead of the individual
470      * signals for each event type, because the former is emitted earlier.
471      * This allows us to catch events independently of the signal handlers
472      * in gui_gtk_x11.c.
473      */
474 # if GTK_CHECK_VERSION(3,0,0)
475     g_signal_connect(G_OBJECT(target), "event",
476 		     G_CALLBACK(target_event_cb),
477 		     beval);
478 # else
479     /* Should use GTK_OBJECT() here, but that causes a lint warning... */
480     gtk_signal_connect((GtkObject*)(target), "event",
481 		       GTK_SIGNAL_FUNC(target_event_cb),
482 		       beval);
483 # endif
484     /*
485      * Nasty:  Key press events go to the main window thus the drawing area
486      * will never see them.  This means we have to connect to the main window
487      * as well in order to catch those events.
488      */
489     if (gtk_socket_id == 0 && gui.mainwin != NULL
490 	    && gtk_widget_is_ancestor(target, gui.mainwin))
491     {
492 # if GTK_CHECK_VERSION(3,0,0)
493 	g_signal_connect(G_OBJECT(gui.mainwin), "event",
494 			 G_CALLBACK(mainwin_event_cb),
495 			 beval);
496 # else
497 	gtk_signal_connect((GtkObject*)(gui.mainwin), "event",
498 			   GTK_SIGNAL_FUNC(mainwin_event_cb),
499 			   beval);
500 # endif
501     }
502 }
503 
504     static void
505 removeEventHandler(BalloonEval *beval)
506 {
507     /* LINTED: avoid warning: dubious operation on enum */
508 # if GTK_CHECK_VERSION(3,0,0)
509     g_signal_handlers_disconnect_by_func(G_OBJECT(beval->target),
510 					 G_CALLBACK(target_event_cb),
511 					 beval);
512 # else
513     gtk_signal_disconnect_by_func((GtkObject*)(beval->target),
514 				  GTK_SIGNAL_FUNC(target_event_cb),
515 				  beval);
516 # endif
517 
518     if (gtk_socket_id == 0 && gui.mainwin != NULL
519 	    && gtk_widget_is_ancestor(beval->target, gui.mainwin))
520     {
521 	/* LINTED: avoid warning: dubious operation on enum */
522 # if GTK_CHECK_VERSION(3,0,0)
523 	g_signal_handlers_disconnect_by_func(G_OBJECT(gui.mainwin),
524 					     G_CALLBACK(mainwin_event_cb),
525 					     beval);
526 # else
527 	gtk_signal_disconnect_by_func((GtkObject*)(gui.mainwin),
528 				      GTK_SIGNAL_FUNC(mainwin_event_cb),
529 				      beval);
530 # endif
531     }
532 }
533 
534     static gint
535 target_event_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
536 {
537     BalloonEval *beval = (BalloonEval *)data;
538 
539     switch (event->type)
540     {
541 	case GDK_ENTER_NOTIFY:
542 	    pointer_event(beval, (int)event->crossing.x,
543 				 (int)event->crossing.y,
544 				 event->crossing.state);
545 	    break;
546 	case GDK_MOTION_NOTIFY:
547 	    if (event->motion.is_hint)
548 	    {
549 		int		x;
550 		int		y;
551 		GdkModifierType	state;
552 		/*
553 		 * GDK_POINTER_MOTION_HINT_MASK is set, thus we cannot obtain
554 		 * the coordinates from the GdkEventMotion struct directly.
555 		 */
556 # if GTK_CHECK_VERSION(3,0,0)
557 		{
558 		    GdkWindow * const win = gtk_widget_get_window(widget);
559 		    GdkDisplay * const dpy = gdk_window_get_display(win);
560 		    GdkDeviceManager * const mngr = gdk_display_get_device_manager(dpy);
561 		    GdkDevice * const dev = gdk_device_manager_get_client_pointer(mngr);
562 		    gdk_window_get_device_position(win, dev , &x, &y, &state);
563 		}
564 # else
565 		gdk_window_get_pointer(widget->window, &x, &y, &state);
566 # endif
567 		pointer_event(beval, x, y, (unsigned int)state);
568 	    }
569 	    else
570 	    {
571 		pointer_event(beval, (int)event->motion.x,
572 				     (int)event->motion.y,
573 				     event->motion.state);
574 	    }
575 	    break;
576 	case GDK_LEAVE_NOTIFY:
577 	    /*
578 	     * Ignore LeaveNotify events that are not "normal".
579 	     * Apparently we also get it when somebody else grabs focus.
580 	     */
581 	    if (event->crossing.mode == GDK_CROSSING_NORMAL)
582 		cancelBalloon(beval);
583 	    break;
584 	case GDK_BUTTON_PRESS:
585 	case GDK_SCROLL:
586 	    cancelBalloon(beval);
587 	    break;
588 	case GDK_KEY_PRESS:
589 	    key_event(beval, event->key.keyval, TRUE);
590 	    break;
591 	case GDK_KEY_RELEASE:
592 	    key_event(beval, event->key.keyval, FALSE);
593 	    break;
594 	default:
595 	    break;
596     }
597 
598     return FALSE; /* continue emission */
599 }
600 
601     static gint
602 mainwin_event_cb(GtkWidget *widget UNUSED, GdkEvent *event, gpointer data)
603 {
604     BalloonEval *beval = (BalloonEval *)data;
605 
606     switch (event->type)
607     {
608 	case GDK_KEY_PRESS:
609 	    key_event(beval, event->key.keyval, TRUE);
610 	    break;
611 	case GDK_KEY_RELEASE:
612 	    key_event(beval, event->key.keyval, FALSE);
613 	    break;
614 	default:
615 	    break;
616     }
617 
618     return FALSE; /* continue emission */
619 }
620 
621     static void
622 pointer_event(BalloonEval *beval, int x, int y, unsigned state)
623 {
624     int distance;
625 
626     distance = ABS(x - beval->x) + ABS(y - beval->y);
627 
628     if (distance > 4)
629     {
630 	/*
631 	 * Moved out of the balloon location: cancel it.
632 	 * Remember button state
633 	 */
634 	beval->state = state;
635 	cancelBalloon(beval);
636 
637 	/* Mouse buttons are pressed - no balloon now */
638 	if (!(state & ((int)GDK_BUTTON1_MASK | (int)GDK_BUTTON2_MASK
639 						    | (int)GDK_BUTTON3_MASK)))
640 	{
641 	    beval->x = x;
642 	    beval->y = y;
643 
644 	    if (state & (int)GDK_MOD1_MASK)
645 	    {
646 		/*
647 		 * Alt is pressed -- enter super-evaluate-mode,
648 		 * where there is no time delay
649 		 */
650 		if (beval->msgCB != NULL)
651 		{
652 		    beval->showState = ShS_PENDING;
653 		    (*beval->msgCB)(beval, state);
654 		}
655 	    }
656 	    else
657 	    {
658 # if GTK_CHECK_VERSION(3,0,0)
659 		beval->timerID = g_timeout_add((guint)p_bdlay,
660 					       &timeout_cb, beval);
661 # else
662 		beval->timerID = gtk_timeout_add((guint32)p_bdlay,
663 						 &timeout_cb, beval);
664 # endif
665 	    }
666 	}
667     }
668 }
669 
670     static void
671 key_event(BalloonEval *beval, unsigned keyval, int is_keypress)
672 {
673     if (beval->showState == ShS_SHOWING && beval->msgCB != NULL)
674     {
675 	switch (keyval)
676 	{
677 	    case GDK_Shift_L:
678 	    case GDK_Shift_R:
679 		beval->showState = ShS_UPDATE_PENDING;
680 		(*beval->msgCB)(beval, (is_keypress)
681 						   ? (int)GDK_SHIFT_MASK : 0);
682 		break;
683 	    case GDK_Control_L:
684 	    case GDK_Control_R:
685 		beval->showState = ShS_UPDATE_PENDING;
686 		(*beval->msgCB)(beval, (is_keypress)
687 						 ? (int)GDK_CONTROL_MASK : 0);
688 		break;
689 	    default:
690 		/* Don't do this for key release, we apparently get these with
691 		 * focus changes in some GTK version. */
692 		if (is_keypress)
693 		    cancelBalloon(beval);
694 		break;
695 	}
696     }
697     else
698 	cancelBalloon(beval);
699 }
700 
701 # if GTK_CHECK_VERSION(3,0,0)
702     static gboolean
703 # else
704     static gint
705 # endif
706 timeout_cb(gpointer data)
707 {
708     BalloonEval *beval = (BalloonEval *)data;
709 
710     beval->timerID = 0;
711     /*
712      * If the timer event happens then the mouse has stopped long enough for
713      * a request to be started. The request will only send to the debugger if
714      * there the mouse is pointing at real data.
715      */
716     requestBalloon(beval);
717 
718     return FALSE; /* don't call me again */
719 }
720 
721 # if GTK_CHECK_VERSION(3,0,0)
722     static gboolean
723 balloon_draw_event_cb(GtkWidget *widget,
724 		      cairo_t	*cr,
725 		      gpointer	 data UNUSED)
726 {
727     GtkStyleContext *context = NULL;
728     gint width = -1, height = -1;
729 
730     if (widget == NULL)
731 	return TRUE;
732 
733     context = gtk_widget_get_style_context(widget);
734     width = gtk_widget_get_allocated_width(widget);
735     height = gtk_widget_get_allocated_height(widget);
736 
737     gtk_style_context_save(context);
738 
739     gtk_style_context_add_class(context, "tooltip");
740     gtk_style_context_set_state(context, GTK_STATE_FLAG_NORMAL);
741 
742     cairo_save(cr);
743     gtk_render_frame(context, cr, 0, 0, width, height);
744     gtk_render_background(context, cr, 0, 0, width, height);
745     cairo_restore(cr);
746 
747     gtk_style_context_restore(context);
748 
749     return FALSE;
750 }
751 # else
752     static gint
753 balloon_expose_event_cb(GtkWidget *widget,
754 			GdkEventExpose *event,
755 			gpointer data UNUSED)
756 {
757     gtk_paint_flat_box(widget->style, widget->window,
758 		       GTK_STATE_NORMAL, GTK_SHADOW_OUT,
759 		       &event->area, widget, "tooltip",
760 		       0, 0, -1, -1);
761 
762     return FALSE; /* continue emission */
763 }
764 # endif /* !GTK_CHECK_VERSION(3,0,0) */
765 
766 #else /* !FEAT_GUI_GTK */
767 
768     static void
769 addEventHandler(Widget target, BalloonEval *beval)
770 {
771     XtAddEventHandler(target,
772 			PointerMotionMask | EnterWindowMask |
773 			LeaveWindowMask | ButtonPressMask | KeyPressMask |
774 			KeyReleaseMask,
775 			False,
776 			pointerEventEH, (XtPointer)beval);
777 }
778 
779     static void
780 removeEventHandler(BalloonEval *beval)
781 {
782     XtRemoveEventHandler(beval->target,
783 			PointerMotionMask | EnterWindowMask |
784 			LeaveWindowMask | ButtonPressMask | KeyPressMask |
785 			KeyReleaseMask,
786 			False,
787 			pointerEventEH, (XtPointer)beval);
788 }
789 
790 
791 /*
792  * The X event handler. All it does is call the real event handler.
793  */
794     static void
795 pointerEventEH(
796     Widget	w UNUSED,
797     XtPointer	client_data,
798     XEvent	*event,
799     Boolean	*unused UNUSED)
800 {
801     BalloonEval *beval = (BalloonEval *)client_data;
802     pointerEvent(beval, event);
803 }
804 
805 
806 /*
807  * The real event handler. Called by pointerEventEH() whenever an event we are
808  * interested in occurs.
809  */
810 
811     static void
812 pointerEvent(BalloonEval *beval, XEvent *event)
813 {
814     Position	distance;	    /* a measure of how much the pointer moved */
815     Position	delta;		    /* used to compute distance */
816 
817     switch (event->type)
818     {
819 	case EnterNotify:
820 	case MotionNotify:
821 	    delta = event->xmotion.x - beval->x;
822 	    if (delta < 0)
823 		delta = -delta;
824 	    distance = delta;
825 	    delta = event->xmotion.y - beval->y;
826 	    if (delta < 0)
827 		delta = -delta;
828 	    distance += delta;
829 	    if (distance > 4)
830 	    {
831 		/*
832 		 * Moved out of the balloon location: cancel it.
833 		 * Remember button state
834 		 */
835 		beval->state = event->xmotion.state;
836 		if (beval->state & (Button1Mask|Button2Mask|Button3Mask))
837 		{
838 		    /* Mouse buttons are pressed - no balloon now */
839 		    cancelBalloon(beval);
840 		}
841 		else if (beval->state & (Mod1Mask|Mod2Mask|Mod3Mask))
842 		{
843 		    /*
844 		     * Alt is pressed -- enter super-evaluate-mode,
845 		     * where there is no time delay
846 		     */
847 		    beval->x = event->xmotion.x;
848 		    beval->y = event->xmotion.y;
849 		    beval->x_root = event->xmotion.x_root;
850 		    beval->y_root = event->xmotion.y_root;
851 		    cancelBalloon(beval);
852 		    if (beval->msgCB != NULL)
853 		    {
854 			beval->showState = ShS_PENDING;
855 			(*beval->msgCB)(beval, beval->state);
856 		    }
857 		}
858 		else
859 		{
860 		    beval->x = event->xmotion.x;
861 		    beval->y = event->xmotion.y;
862 		    beval->x_root = event->xmotion.x_root;
863 		    beval->y_root = event->xmotion.y_root;
864 		    cancelBalloon(beval);
865 		    beval->timerID = XtAppAddTimeOut( beval->appContext,
866 					(long_u)p_bdlay, timerRoutine, beval);
867 		}
868 	    }
869 	    break;
870 
871 	/*
872 	 * Motif and Athena version: Keystrokes will be caught by the
873 	 * "textArea" widget, and handled in gui_x11_key_hit_cb().
874 	 */
875 	case KeyPress:
876 	    if (beval->showState == ShS_SHOWING && beval->msgCB != NULL)
877 	    {
878 		Modifiers   modifier;
879 		KeySym	    keysym;
880 
881 		XtTranslateKeycode(gui.dpy,
882 				       event->xkey.keycode, event->xkey.state,
883 				       &modifier, &keysym);
884 		if (keysym == XK_Shift_L || keysym == XK_Shift_R)
885 		{
886 		    beval->showState = ShS_UPDATE_PENDING;
887 		    (*beval->msgCB)(beval, ShiftMask);
888 		}
889 		else if (keysym == XK_Control_L || keysym == XK_Control_R)
890 		{
891 		    beval->showState = ShS_UPDATE_PENDING;
892 		    (*beval->msgCB)(beval, ControlMask);
893 		}
894 		else
895 		    cancelBalloon(beval);
896 	    }
897 	    else
898 		cancelBalloon(beval);
899 	    break;
900 
901 	case KeyRelease:
902 	    if (beval->showState == ShS_SHOWING && beval->msgCB != NULL)
903 	    {
904 		Modifiers modifier;
905 		KeySym keysym;
906 
907 		XtTranslateKeycode(gui.dpy, event->xkey.keycode,
908 				event->xkey.state, &modifier, &keysym);
909 		if ((keysym == XK_Shift_L) || (keysym == XK_Shift_R)) {
910 		    beval->showState = ShS_UPDATE_PENDING;
911 		    (*beval->msgCB)(beval, 0);
912 		}
913 		else if ((keysym == XK_Control_L) || (keysym == XK_Control_R))
914 		{
915 		    beval->showState = ShS_UPDATE_PENDING;
916 		    (*beval->msgCB)(beval, 0);
917 		}
918 		else
919 		    cancelBalloon(beval);
920 	    }
921 	    else
922 		cancelBalloon(beval);
923 	    break;
924 
925 	case LeaveNotify:
926 		/* Ignore LeaveNotify events that are not "normal".
927 		 * Apparently we also get it when somebody else grabs focus.
928 		 * Happens for me every two seconds (some clipboard tool?) */
929 		if (event->xcrossing.mode == NotifyNormal)
930 		    cancelBalloon(beval);
931 		break;
932 
933 	case ButtonPress:
934 		cancelBalloon(beval);
935 		break;
936 
937 	default:
938 	    break;
939     }
940 }
941 
942     static void
943 timerRoutine(XtPointer dx, XtIntervalId *id UNUSED)
944 {
945     BalloonEval *beval = (BalloonEval *)dx;
946 
947     beval->timerID = (XtIntervalId)NULL;
948 
949     /*
950      * If the timer event happens then the mouse has stopped long enough for
951      * a request to be started. The request will only send to the debugger if
952      * there the mouse is pointing at real data.
953      */
954     requestBalloon(beval);
955 }
956 
957 #endif /* !FEAT_GUI_GTK */
958 
959     static void
960 requestBalloon(BalloonEval *beval)
961 {
962     if (beval->showState != ShS_PENDING)
963     {
964 	/* Determine the beval to display */
965 	if (beval->msgCB != NULL)
966 	{
967 	    beval->showState = ShS_PENDING;
968 	    (*beval->msgCB)(beval, beval->state);
969 	}
970 	else if (beval->msg != NULL)
971 	    drawBalloon(beval);
972     }
973 }
974 
975 #ifdef FEAT_GUI_GTK
976 /*
977  * Convert the string to UTF-8 if 'encoding' is not "utf-8".
978  * Replace any non-printable characters and invalid bytes sequences with
979  * "^X" or "<xx>" escapes, and apply SpecialKey highlighting to them.
980  * TAB and NL are passed through unscathed.
981  */
982 # define IS_NONPRINTABLE(c) (((c) < 0x20 && (c) != TAB && (c) != NL) \
983 			      || (c) == DEL)
984     static void
985 set_printable_label_text(GtkLabel *label, char_u *text)
986 {
987     char_u	    *convbuf = NULL;
988     char_u	    *buf;
989     char_u	    *p;
990     char_u	    *pdest;
991     unsigned int    len;
992     int		    charlen;
993     int		    uc;
994     PangoAttrList   *attr_list;
995 
996     /* Convert to UTF-8 if it isn't already */
997     if (output_conv.vc_type != CONV_NONE)
998     {
999 	convbuf = string_convert(&output_conv, text, NULL);
1000 	if (convbuf != NULL)
1001 	    text = convbuf;
1002     }
1003 
1004     /* First let's see how much we need to allocate */
1005     len = 0;
1006     for (p = text; *p != NUL; p += charlen)
1007     {
1008 	if ((*p & 0x80) == 0)	/* be quick for ASCII */
1009 	{
1010 	    charlen = 1;
1011 	    len += IS_NONPRINTABLE(*p) ? 2 : 1;	/* nonprintable: ^X */
1012 	}
1013 	else
1014 	{
1015 	    charlen = utf_ptr2len(p);
1016 	    uc = utf_ptr2char(p);
1017 
1018 	    if (charlen != utf_char2len(uc))
1019 		charlen = 1; /* reject overlong sequences */
1020 
1021 	    if (charlen == 1 || uc < 0xa0)	/* illegal byte or    */
1022 		len += 4;			/* control char: <xx> */
1023 	    else if (!utf_printable(uc))
1024 		/* Note: we assume here that utf_printable() doesn't
1025 		 * care about characters outside the BMP. */
1026 		len += 6;			/* nonprintable: <xxxx> */
1027 	    else
1028 		len += charlen;
1029 	}
1030     }
1031 
1032     attr_list = pango_attr_list_new();
1033     buf = alloc(len + 1);
1034 
1035     /* Now go for the real work */
1036     if (buf != NULL)
1037     {
1038 	attrentry_T	*aep;
1039 	PangoAttribute	*attr;
1040 	guicolor_T	pixel;
1041 	GdkColor	color = { 0, 0, 0, 0 };
1042 
1043 	/* Look up the RGB values of the SpecialKey foreground color. */
1044 	aep = syn_gui_attr2entry(hl_attr(HLF_8));
1045 	pixel = (aep != NULL) ? aep->ae_u.gui.fg_color : INVALCOLOR;
1046 	if (pixel != INVALCOLOR)
1047 # if GTK_CHECK_VERSION(3,0,0)
1048 	{
1049 	    GdkVisual * const visual = gtk_widget_get_visual(gui.drawarea);
1050 
1051 	    if (visual == NULL)
1052 	    {
1053 		color.red = 0;
1054 		color.green = 0;
1055 		color.blue = 0;
1056 	    }
1057 	    else
1058 	    {
1059 		guint32 r_mask, g_mask, b_mask;
1060 		gint r_shift, g_shift, b_shift;
1061 
1062 		gdk_visual_get_red_pixel_details(visual, &r_mask, &r_shift,
1063 						 NULL);
1064 		gdk_visual_get_green_pixel_details(visual, &g_mask, &g_shift,
1065 						   NULL);
1066 		gdk_visual_get_blue_pixel_details(visual, &b_mask, &b_shift,
1067 						  NULL);
1068 
1069 		color.red = ((pixel & r_mask) >> r_shift) / 255.0 * 65535;
1070 		color.green = ((pixel & g_mask) >> g_shift) / 255.0 * 65535;
1071 		color.blue = ((pixel & b_mask) >> b_shift) / 255.0 * 65535;
1072 	    }
1073 	}
1074 # else
1075 	    gdk_colormap_query_color(gtk_widget_get_colormap(gui.drawarea),
1076 				     (unsigned long)pixel, &color);
1077 # endif
1078 
1079 	pdest = buf;
1080 	p = text;
1081 	while (*p != NUL)
1082 	{
1083 	    /* Be quick for ASCII */
1084 	    if ((*p & 0x80) == 0 && !IS_NONPRINTABLE(*p))
1085 	    {
1086 		*pdest++ = *p++;
1087 	    }
1088 	    else
1089 	    {
1090 		charlen = utf_ptr2len(p);
1091 		uc = utf_ptr2char(p);
1092 
1093 		if (charlen != utf_char2len(uc))
1094 		    charlen = 1; /* reject overlong sequences */
1095 
1096 		if (charlen == 1 || uc < 0xa0 || !utf_printable(uc))
1097 		{
1098 		    int	outlen;
1099 
1100 		    /* Careful: we can't just use transchar_byte() here,
1101 		     * since 'encoding' is not necessarily set to "utf-8". */
1102 		    if (*p & 0x80 && charlen == 1)
1103 		    {
1104 			transchar_hex(pdest, *p);	/* <xx> */
1105 			outlen = 4;
1106 		    }
1107 		    else if (uc >= 0x80)
1108 		    {
1109 			/* Note: we assume here that utf_printable() doesn't
1110 			 * care about characters outside the BMP. */
1111 			transchar_hex(pdest, uc);	/* <xx> or <xxxx> */
1112 			outlen = (uc < 0x100) ? 4 : 6;
1113 		    }
1114 		    else
1115 		    {
1116 			transchar_nonprint(pdest, *p);	/* ^X */
1117 			outlen = 2;
1118 		    }
1119 		    if (pixel != INVALCOLOR)
1120 		    {
1121 			attr = pango_attr_foreground_new(
1122 				color.red, color.green, color.blue);
1123 			attr->start_index = pdest - buf;
1124 			attr->end_index   = pdest - buf + outlen;
1125 			pango_attr_list_insert(attr_list, attr);
1126 		    }
1127 		    pdest += outlen;
1128 		    p += charlen;
1129 		}
1130 		else
1131 		{
1132 		    do
1133 			*pdest++ = *p++;
1134 		    while (--charlen != 0);
1135 		}
1136 	    }
1137 	}
1138 	*pdest = NUL;
1139     }
1140 
1141     vim_free(convbuf);
1142 
1143     gtk_label_set_text(label, (const char *)buf);
1144     vim_free(buf);
1145 
1146     gtk_label_set_attributes(label, attr_list);
1147     pango_attr_list_unref(attr_list);
1148 }
1149 # undef IS_NONPRINTABLE
1150 
1151 /*
1152  * Draw a balloon.
1153  */
1154     static void
1155 drawBalloon(BalloonEval *beval)
1156 {
1157     if (beval->msg != NULL)
1158     {
1159 	GtkRequisition	requisition;
1160 	int		screen_w;
1161 	int		screen_h;
1162 	int		x;
1163 	int		y;
1164 	int		x_offset = EVAL_OFFSET_X;
1165 	int		y_offset = EVAL_OFFSET_Y;
1166 	PangoLayout	*layout;
1167 # ifdef HAVE_GTK_MULTIHEAD
1168 	GdkScreen	*screen;
1169 
1170 	screen = gtk_widget_get_screen(beval->target);
1171 	gtk_window_set_screen(GTK_WINDOW(beval->balloonShell), screen);
1172 	screen_w = gdk_screen_get_width(screen);
1173 	screen_h = gdk_screen_get_height(screen);
1174 # else
1175 	screen_w = gdk_screen_width();
1176 	screen_h = gdk_screen_height();
1177 # endif
1178 # if !GTK_CHECK_VERSION(3,0,0)
1179 	gtk_widget_ensure_style(beval->balloonShell);
1180 	gtk_widget_ensure_style(beval->balloonLabel);
1181 # endif
1182 
1183 	set_printable_label_text(GTK_LABEL(beval->balloonLabel), beval->msg);
1184 	/*
1185 	 * Dirty trick:  Enable wrapping mode on the label's layout behind its
1186 	 * back.  This way GtkLabel won't try to constrain the wrap width to a
1187 	 * builtin maximum value of about 65 Latin characters.
1188 	 */
1189 	layout = gtk_label_get_layout(GTK_LABEL(beval->balloonLabel));
1190 # ifdef PANGO_WRAP_WORD_CHAR
1191 	pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
1192 # else
1193 	pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
1194 # endif
1195 	pango_layout_set_width(layout,
1196 		/* try to come up with some reasonable width */
1197 		PANGO_SCALE * CLAMP(gui.num_cols * gui.char_width,
1198 				    screen_w / 2,
1199 				    MAX(20, screen_w - 20)));
1200 
1201 	/* Calculate the balloon's width and height. */
1202 # if GTK_CHECK_VERSION(3,0,0)
1203 	gtk_widget_get_preferred_size(beval->balloonShell, &requisition, NULL);
1204 # else
1205 	gtk_widget_size_request(beval->balloonShell, &requisition);
1206 # endif
1207 
1208 	/* Compute position of the balloon area */
1209 # if GTK_CHECK_VERSION(3,0,0)
1210 	gdk_window_get_origin(gtk_widget_get_window(beval->target), &x, &y);
1211 # else
1212 	gdk_window_get_origin(beval->target->window, &x, &y);
1213 # endif
1214 	x += beval->x;
1215 	y += beval->y;
1216 
1217 	/* Get out of the way of the mouse pointer */
1218 	if (x + x_offset + requisition.width > screen_w)
1219 	    y_offset += 15;
1220 	if (y + y_offset + requisition.height > screen_h)
1221 	    y_offset = -requisition.height - EVAL_OFFSET_Y;
1222 
1223 	/* Sanitize values */
1224 	x = CLAMP(x + x_offset, 0, MAX(0, screen_w - requisition.width));
1225 	y = CLAMP(y + y_offset, 0, MAX(0, screen_h - requisition.height));
1226 
1227 	/* Show the balloon */
1228 # if GTK_CHECK_VERSION(3,0,0)
1229 	gtk_window_move(GTK_WINDOW(beval->balloonShell), x, y);
1230 # else
1231 	gtk_widget_set_uposition(beval->balloonShell, x, y);
1232 # endif
1233 	gtk_widget_show(beval->balloonShell);
1234 
1235 	beval->showState = ShS_SHOWING;
1236     }
1237 }
1238 
1239 /*
1240  * Undraw a balloon.
1241  */
1242     static void
1243 undrawBalloon(BalloonEval *beval)
1244 {
1245     if (beval->balloonShell != NULL)
1246 	gtk_widget_hide(beval->balloonShell);
1247     beval->showState = ShS_NEUTRAL;
1248 }
1249 
1250     static void
1251 cancelBalloon(BalloonEval *beval)
1252 {
1253     if (beval->showState == ShS_SHOWING
1254 	    || beval->showState == ShS_UPDATE_PENDING)
1255 	undrawBalloon(beval);
1256 
1257     if (beval->timerID != 0)
1258     {
1259 # if GTK_CHECK_VERSION(3,0,0)
1260 	g_source_remove(beval->timerID);
1261 # else
1262 	gtk_timeout_remove(beval->timerID);
1263 # endif
1264 	beval->timerID = 0;
1265     }
1266     beval->showState = ShS_NEUTRAL;
1267 }
1268 
1269     static void
1270 createBalloonEvalWindow(BalloonEval *beval)
1271 {
1272     beval->balloonShell = gtk_window_new(GTK_WINDOW_POPUP);
1273 
1274     gtk_widget_set_app_paintable(beval->balloonShell, TRUE);
1275 # if GTK_CHECK_VERSION(3,0,0)
1276     gtk_window_set_resizable(GTK_WINDOW(beval->balloonShell), FALSE);
1277 # else
1278     gtk_window_set_policy(GTK_WINDOW(beval->balloonShell), FALSE, FALSE, TRUE);
1279 # endif
1280     gtk_widget_set_name(beval->balloonShell, "gtk-tooltips");
1281 # if GTK_CHECK_VERSION(3,0,0)
1282     gtk_container_set_border_width(GTK_CONTAINER(beval->balloonShell), 4);
1283 # else
1284     gtk_container_border_width(GTK_CONTAINER(beval->balloonShell), 4);
1285 # endif
1286 
1287 # if GTK_CHECK_VERSION(3,0,0)
1288     g_signal_connect(G_OBJECT(beval->balloonShell), "draw",
1289 		     G_CALLBACK(balloon_draw_event_cb), NULL);
1290 # else
1291     gtk_signal_connect((GtkObject*)(beval->balloonShell), "expose_event",
1292 		       GTK_SIGNAL_FUNC(balloon_expose_event_cb), NULL);
1293 # endif
1294     beval->balloonLabel = gtk_label_new(NULL);
1295 
1296     gtk_label_set_line_wrap(GTK_LABEL(beval->balloonLabel), FALSE);
1297     gtk_label_set_justify(GTK_LABEL(beval->balloonLabel), GTK_JUSTIFY_LEFT);
1298 # if GTK_CHECK_VERSION(3,16,0)
1299     gtk_label_set_xalign(GTK_LABEL(beval->balloonLabel), 0.5);
1300     gtk_label_set_yalign(GTK_LABEL(beval->balloonLabel), 0.5);
1301 # elif GTK_CHECK_VERSION(3,14,0)
1302     GValue align_val = G_VALUE_INIT;
1303     g_value_init(&align_val, G_TYPE_FLOAT);
1304     g_value_set_float(&align_val, 0.5);
1305     g_object_set_property(G_OBJECT(beval->balloonLabel), "xalign", &align_val);
1306     g_object_set_property(G_OBJECT(beval->balloonLabel), "yalign", &align_val);
1307     g_value_unset(&align_val);
1308 # else
1309     gtk_misc_set_alignment(GTK_MISC(beval->balloonLabel), 0.5f, 0.5f);
1310 # endif
1311     gtk_widget_set_name(beval->balloonLabel, "vim-balloon-label");
1312     gtk_widget_show(beval->balloonLabel);
1313 
1314     gtk_container_add(GTK_CONTAINER(beval->balloonShell), beval->balloonLabel);
1315 }
1316 
1317 #else /* !FEAT_GUI_GTK */
1318 
1319 /*
1320  * Draw a balloon.
1321  */
1322     static void
1323 drawBalloon(BalloonEval *beval)
1324 {
1325     Dimension	w;
1326     Dimension	h;
1327     Position tx;
1328     Position ty;
1329 
1330     if (beval->msg != NULL)
1331     {
1332 	/* Show the Balloon */
1333 
1334 	/* Calculate the label's width and height */
1335 #ifdef FEAT_GUI_MOTIF
1336 	XmString s;
1337 
1338 	/* For the callback function we parse NL characters to create a
1339 	 * multi-line label.  This doesn't work for all languages, but
1340 	 * XmStringCreateLocalized() doesn't do multi-line labels... */
1341 	if (beval->msgCB != NULL)
1342 	    s = XmStringCreateLtoR((char *)beval->msg, XmFONTLIST_DEFAULT_TAG);
1343 	else
1344 	    s = XmStringCreateLocalized((char *)beval->msg);
1345 	{
1346 	    XmFontList fl;
1347 
1348 	    fl = gui_motif_fontset2fontlist(&gui.tooltip_fontset);
1349 	    if (fl == NULL)
1350 	    {
1351 		XmStringFree(s);
1352 		return;
1353 	    }
1354 	    XmStringExtent(fl, s, &w, &h);
1355 	    XmFontListFree(fl);
1356 	}
1357 	w += gui.border_offset << 1;
1358 	h += gui.border_offset << 1;
1359 	XtVaSetValues(beval->balloonLabel, XmNlabelString, s, NULL);
1360 	XmStringFree(s);
1361 #else /* Athena */
1362 	/* Assume XtNinternational == True */
1363 	XFontSet	fset;
1364 	XFontSetExtents *ext;
1365 
1366 	XtVaGetValues(beval->balloonLabel, XtNfontSet, &fset, NULL);
1367 	ext = XExtentsOfFontSet(fset);
1368 	h = ext->max_ink_extent.height;
1369 	w = XmbTextEscapement(fset,
1370 			      (char *)beval->msg,
1371 			      (int)STRLEN(beval->msg));
1372 	w += gui.border_offset << 1;
1373 	h += gui.border_offset << 1;
1374 	XtVaSetValues(beval->balloonLabel, XtNlabel, beval->msg, NULL);
1375 #endif
1376 
1377 	/* Compute position of the balloon area */
1378 	tx = beval->x_root + EVAL_OFFSET_X;
1379 	ty = beval->y_root + EVAL_OFFSET_Y;
1380 	if ((tx + w) > beval->screen_width)
1381 	    tx = beval->screen_width - w;
1382 	if ((ty + h) > beval->screen_height)
1383 	    ty = beval->screen_height - h;
1384 #ifdef FEAT_GUI_MOTIF
1385 	XtVaSetValues(beval->balloonShell,
1386 		XmNx, tx,
1387 		XmNy, ty,
1388 		NULL);
1389 #else
1390 	/* Athena */
1391 	XtVaSetValues(beval->balloonShell,
1392 		XtNx, tx,
1393 		XtNy, ty,
1394 		NULL);
1395 #endif
1396 	/* Set tooltip colors */
1397 	{
1398 	    Arg args[2];
1399 
1400 #ifdef FEAT_GUI_MOTIF
1401 	    args[0].name = XmNbackground;
1402 	    args[0].value = gui.tooltip_bg_pixel;
1403 	    args[1].name = XmNforeground;
1404 	    args[1].value = gui.tooltip_fg_pixel;
1405 #else /* Athena */
1406 	    args[0].name = XtNbackground;
1407 	    args[0].value = gui.tooltip_bg_pixel;
1408 	    args[1].name = XtNforeground;
1409 	    args[1].value = gui.tooltip_fg_pixel;
1410 #endif
1411 	    XtSetValues(beval->balloonLabel, &args[0], XtNumber(args));
1412 	}
1413 
1414 	XtPopup(beval->balloonShell, XtGrabNone);
1415 
1416 	beval->showState = ShS_SHOWING;
1417 
1418 	current_beval = beval;
1419     }
1420 }
1421 
1422 /*
1423  * Undraw a balloon.
1424  */
1425     static void
1426 undrawBalloon(BalloonEval *beval)
1427 {
1428     if (beval->balloonShell != (Widget)0)
1429 	XtPopdown(beval->balloonShell);
1430     beval->showState = ShS_NEUTRAL;
1431 
1432     current_beval = NULL;
1433 }
1434 
1435     static void
1436 cancelBalloon(BalloonEval *beval)
1437 {
1438     if (beval->showState == ShS_SHOWING
1439 	    || beval->showState == ShS_UPDATE_PENDING)
1440 	undrawBalloon(beval);
1441 
1442     if (beval->timerID != (XtIntervalId)NULL)
1443     {
1444 	XtRemoveTimeOut(beval->timerID);
1445 	beval->timerID = (XtIntervalId)NULL;
1446     }
1447     beval->showState = ShS_NEUTRAL;
1448 }
1449 
1450 
1451     static void
1452 createBalloonEvalWindow(BalloonEval *beval)
1453 {
1454     Arg		args[12];
1455     int		n;
1456 
1457     n = 0;
1458 #ifdef FEAT_GUI_MOTIF
1459     XtSetArg(args[n], XmNallowShellResize, True); n++;
1460     beval->balloonShell = XtAppCreateShell("balloonEval", "BalloonEval",
1461 		    overrideShellWidgetClass, gui.dpy, args, n);
1462 #else
1463     /* Athena */
1464     XtSetArg(args[n], XtNallowShellResize, True); n++;
1465     beval->balloonShell = XtAppCreateShell("balloonEval", "BalloonEval",
1466 		    overrideShellWidgetClass, gui.dpy, args, n);
1467 #endif
1468 
1469     n = 0;
1470 #ifdef FEAT_GUI_MOTIF
1471     {
1472 	XmFontList fl;
1473 
1474 	fl = gui_motif_fontset2fontlist(&gui.tooltip_fontset);
1475 	XtSetArg(args[n], XmNforeground, gui.tooltip_fg_pixel); n++;
1476 	XtSetArg(args[n], XmNbackground, gui.tooltip_bg_pixel); n++;
1477 	XtSetArg(args[n], XmNfontList, fl); n++;
1478 	XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
1479 	beval->balloonLabel = XtCreateManagedWidget("balloonLabel",
1480 			xmLabelWidgetClass, beval->balloonShell, args, n);
1481     }
1482 #else /* FEAT_GUI_ATHENA */
1483     XtSetArg(args[n], XtNforeground, gui.tooltip_fg_pixel); n++;
1484     XtSetArg(args[n], XtNbackground, gui.tooltip_bg_pixel); n++;
1485     XtSetArg(args[n], XtNinternational, True); n++;
1486     XtSetArg(args[n], XtNfontSet, gui.tooltip_fontset); n++;
1487     beval->balloonLabel = XtCreateManagedWidget("balloonLabel",
1488 		    labelWidgetClass, beval->balloonShell, args, n);
1489 #endif
1490 }
1491 
1492 #endif /* !FEAT_GUI_GTK */
1493 #endif /* !FEAT_GUI_W32 */
1494 
1495 #endif /* FEAT_BEVAL */
1496