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