blob: 0f08649f6a12ce724f2515b2087c92baa021c9fe [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{
Bram Moolenaar33570922005-01-25 22:26:29 +0000374 /* LINTED: avoid warning: dubious operation on enum */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000375 gtk_signal_disconnect_by_func((GtkObject*)(beval->target),
376 GTK_SIGNAL_FUNC(target_event_cb),
377 beval);
378
379 if (gtk_socket_id == 0 && gui.mainwin != NULL
380 && gtk_widget_is_ancestor(beval->target, gui.mainwin))
381 {
Bram Moolenaar33570922005-01-25 22:26:29 +0000382 /* LINTED: avoid warning: dubious operation on enum */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000383 gtk_signal_disconnect_by_func((GtkObject*)(gui.mainwin),
384 GTK_SIGNAL_FUNC(mainwin_event_cb),
385 beval);
386 }
387}
388
389 static gint
390target_event_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
391{
392 BalloonEval *beval = (BalloonEval *)data;
393
394 switch (event->type)
395 {
396 case GDK_ENTER_NOTIFY:
397 pointer_event(beval, (int)event->crossing.x,
398 (int)event->crossing.y,
399 event->crossing.state);
400 break;
401 case GDK_MOTION_NOTIFY:
402 if (event->motion.is_hint)
403 {
404 int x;
405 int y;
406 GdkModifierType state;
407 /*
408 * GDK_POINTER_MOTION_HINT_MASK is set, thus we cannot obtain
409 * the coordinates from the GdkEventMotion struct directly.
410 */
411 gdk_window_get_pointer(widget->window, &x, &y, &state);
412 pointer_event(beval, x, y, (unsigned int)state);
413 }
414 else
415 {
416 pointer_event(beval, (int)event->motion.x,
417 (int)event->motion.y,
418 event->motion.state);
419 }
420 break;
421 case GDK_LEAVE_NOTIFY:
422 /*
423 * Ignore LeaveNotify events that are not "normal".
424 * Apparently we also get it when somebody else grabs focus.
425 */
426 if (event->crossing.mode == GDK_CROSSING_NORMAL)
427 cancelBalloon(beval);
428 break;
429 case GDK_BUTTON_PRESS:
430# ifdef HAVE_GTK2
431 case GDK_SCROLL:
432# endif
433 cancelBalloon(beval);
434 break;
435 case GDK_KEY_PRESS:
436 key_event(beval, event->key.keyval, TRUE);
437 break;
438 case GDK_KEY_RELEASE:
439 key_event(beval, event->key.keyval, FALSE);
440 break;
441 default:
442 break;
443 }
444
445 return FALSE; /* continue emission */
446}
447
448/*ARGSUSED*/
449 static gint
450mainwin_event_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
451{
452 BalloonEval *beval = (BalloonEval *)data;
453
454 switch (event->type)
455 {
456 case GDK_KEY_PRESS:
457 key_event(beval, event->key.keyval, TRUE);
458 break;
459 case GDK_KEY_RELEASE:
460 key_event(beval, event->key.keyval, FALSE);
461 break;
462 default:
463 break;
464 }
465
466 return FALSE; /* continue emission */
467}
468
469 static void
470pointer_event(BalloonEval *beval, int x, int y, unsigned state)
471{
472 int distance;
473
474 distance = ABS(x - beval->x) + ABS(y - beval->y);
475
476 if (distance > 4)
477 {
478 /*
479 * Moved out of the balloon location: cancel it.
480 * Remember button state
481 */
482 beval->state = state;
483 cancelBalloon(beval);
484
485 /* Mouse buttons are pressed - no balloon now */
486 if (!(state & ((int)GDK_BUTTON1_MASK | (int)GDK_BUTTON2_MASK
487 | (int)GDK_BUTTON3_MASK)))
488 {
489 beval->x = x;
490 beval->y = y;
491
492 if (state & (int)GDK_MOD1_MASK)
493 {
494 /*
495 * Alt is pressed -- enter super-evaluate-mode,
496 * where there is no time delay
497 */
498 if (beval->msgCB != NULL)
499 {
500 beval->showState = ShS_PENDING;
501 (*beval->msgCB)(beval, state);
502 }
503 }
504 else
505 {
506 beval->timerID = gtk_timeout_add((guint32)p_bdlay,
507 &timeout_cb, beval);
508 }
509 }
510 }
511}
512
513 static void
514key_event(BalloonEval *beval, unsigned keyval, int is_keypress)
515{
516 if (beval->showState == ShS_SHOWING && beval->msgCB != NULL)
517 {
518 switch (keyval)
519 {
520 case GDK_Shift_L:
521 case GDK_Shift_R:
522 beval->showState = ShS_UPDATE_PENDING;
523 (*beval->msgCB)(beval, (is_keypress)
524 ? (int)GDK_SHIFT_MASK : 0);
525 break;
526 case GDK_Control_L:
527 case GDK_Control_R:
528 beval->showState = ShS_UPDATE_PENDING;
529 (*beval->msgCB)(beval, (is_keypress)
530 ? (int)GDK_CONTROL_MASK : 0);
531 break;
532 default:
533 cancelBalloon(beval);
534 break;
535 }
536 }
537 else
538 cancelBalloon(beval);
539}
540
541 static gint
542timeout_cb(gpointer data)
543{
544 BalloonEval *beval = (BalloonEval *)data;
545
546 beval->timerID = 0;
547 /*
548 * If the timer event happens then the mouse has stopped long enough for
549 * a request to be started. The request will only send to the debugger if
550 * there the mouse is pointing at real data.
551 */
552 requestBalloon(beval);
553
554 return FALSE; /* don't call me again */
555}
556
557/*ARGSUSED2*/
558 static gint
559balloon_expose_event_cb(GtkWidget *widget, GdkEventExpose *event, gpointer data)
560{
561 gtk_paint_flat_box(widget->style, widget->window,
562 GTK_STATE_NORMAL, GTK_SHADOW_OUT,
563 &event->area, widget, "tooltip",
564 0, 0, -1, -1);
565
566 return FALSE; /* continue emission */
567}
568
569# ifndef HAVE_GTK2
570/*ARGSUSED2*/
571 static void
572balloon_draw_cb(GtkWidget *widget, GdkRectangle *area, gpointer data)
573{
574 GtkWidget *child;
575 GdkRectangle child_area;
576
577 gtk_paint_flat_box(widget->style, widget->window,
578 GTK_STATE_NORMAL, GTK_SHADOW_OUT,
579 area, widget, "tooltip",
580 0, 0, -1, -1);
581
582 child = GTK_BIN(widget)->child;
583
584 if (gtk_widget_intersect(child, area, &child_area))
585 gtk_widget_draw(child, &child_area);
586}
587# endif
588
589#else /* !FEAT_GUI_GTK */
590
591 static void
592addEventHandler(target, beval)
593 Widget target;
594 BalloonEval *beval;
595{
596 XtAddEventHandler(target,
597 PointerMotionMask | EnterWindowMask |
598 LeaveWindowMask | ButtonPressMask | KeyPressMask |
599 KeyReleaseMask,
600 False,
601 pointerEventEH, (XtPointer)beval);
602}
603
604 static void
605removeEventHandler(beval)
606 BalloonEval *beval;
607{
608 XtRemoveEventHandler(beval->target,
609 PointerMotionMask | EnterWindowMask |
610 LeaveWindowMask | ButtonPressMask | KeyPressMask |
611 KeyReleaseMask,
612 False,
613 pointerEventEH, (XtPointer)beval);
614}
615
616
617/*
618 * The X event handler. All it does is call the real event handler.
619 */
620/*ARGSUSED*/
621 static void
622pointerEventEH(w, client_data, event, unused)
623 Widget w;
624 XtPointer client_data;
625 XEvent *event;
626 Boolean *unused;
627{
628 BalloonEval *beval = (BalloonEval *)client_data;
629 pointerEvent(beval, event);
630}
631
632
633/*
634 * The real event handler. Called by pointerEventEH() whenever an event we are
635 * interested in ocurrs.
636 */
637
638 static void
639pointerEvent(beval, event)
640 BalloonEval *beval;
641 XEvent *event;
642{
643 Position distance; /* a measure of how much the ponter moved */
644 Position delta; /* used to compute distance */
645
646 switch (event->type)
647 {
648 case EnterNotify:
649 case MotionNotify:
650 delta = event->xmotion.x - beval->x;
651 if (delta < 0)
652 delta = -delta;
653 distance = delta;
654 delta = event->xmotion.y - beval->y;
655 if (delta < 0)
656 delta = -delta;
657 distance += delta;
658 if (distance > 4)
659 {
660 /*
661 * Moved out of the balloon location: cancel it.
662 * Remember button state
663 */
664 beval->state = event->xmotion.state;
665 if (beval->state & (Button1Mask|Button2Mask|Button3Mask))
666 {
667 /* Mouse buttons are pressed - no balloon now */
668 cancelBalloon(beval);
669 }
670 else if (beval->state & (Mod1Mask|Mod2Mask|Mod3Mask))
671 {
672 /*
673 * Alt is pressed -- enter super-evaluate-mode,
674 * where there is no time delay
675 */
676 beval->x = event->xmotion.x;
677 beval->y = event->xmotion.y;
678 beval->x_root = event->xmotion.x_root;
679 beval->y_root = event->xmotion.y_root;
680 cancelBalloon(beval);
681 if (beval->msgCB != NULL)
682 {
683 beval->showState = ShS_PENDING;
684 (*beval->msgCB)(beval, beval->state);
685 }
686 }
687 else
688 {
689 beval->x = event->xmotion.x;
690 beval->y = event->xmotion.y;
691 beval->x_root = event->xmotion.x_root;
692 beval->y_root = event->xmotion.y_root;
693 cancelBalloon(beval);
694 beval->timerID = XtAppAddTimeOut( beval->appContext,
695 (long_u)p_bdlay, timerRoutine, beval);
696 }
697 }
698 break;
699
700 /*
701 * Motif and Athena version: Keystrokes will be caught by the
702 * "textArea" widget, and handled in gui_x11_key_hit_cb().
703 */
704 case KeyPress:
705 if (beval->showState == ShS_SHOWING && beval->msgCB != NULL)
706 {
707 Modifiers modifier;
708 KeySym keysym;
709
710 XtTranslateKeycode(gui.dpy,
711 event->xkey.keycode, event->xkey.state,
712 &modifier, &keysym);
713 if (keysym == XK_Shift_L || keysym == XK_Shift_R)
714 {
715 beval->showState = ShS_UPDATE_PENDING;
716 (*beval->msgCB)(beval, ShiftMask);
717 }
718 else if (keysym == XK_Control_L || keysym == XK_Control_R)
719 {
720 beval->showState = ShS_UPDATE_PENDING;
721 (*beval->msgCB)(beval, ControlMask);
722 }
723 else
724 cancelBalloon(beval);
725 }
726 else
727 cancelBalloon(beval);
728 break;
729
730 case KeyRelease:
731 if (beval->showState == ShS_SHOWING && beval->msgCB != NULL)
732 {
733 Modifiers modifier;
734 KeySym keysym;
735
736 XtTranslateKeycode(gui.dpy, event->xkey.keycode,
737 event->xkey.state, &modifier, &keysym);
738 if ((keysym == XK_Shift_L) || (keysym == XK_Shift_R)) {
739 beval->showState = ShS_UPDATE_PENDING;
740 (*beval->msgCB)(beval, 0);
741 }
742 else if ((keysym == XK_Control_L) || (keysym == XK_Control_R))
743 {
744 beval->showState = ShS_UPDATE_PENDING;
745 (*beval->msgCB)(beval, 0);
746 }
747 else
748 cancelBalloon(beval);
749 }
750 else
751 cancelBalloon(beval);
752 break;
753
754 case LeaveNotify:
755 /* Ignore LeaveNotify events that are not "normal".
756 * Apparently we also get it when somebody else grabs focus.
757 * Happens for me every two seconds (some clipboard tool?) */
758 if (event->xcrossing.mode == NotifyNormal)
759 cancelBalloon(beval);
760 break;
761
762 case ButtonPress:
763 cancelBalloon(beval);
764 break;
765
766 default:
767 break;
768 }
769}
770
771/*ARGSUSED*/
772 static void
773timerRoutine(dx, id)
774 XtPointer dx;
775 XtIntervalId *id;
776{
777 BalloonEval *beval = (BalloonEval *)dx;
778
779 beval->timerID = (XtIntervalId)NULL;
780
781 /*
782 * If the timer event happens then the mouse has stopped long enough for
783 * a request to be started. The request will only send to the debugger if
784 * there the mouse is pointing at real data.
785 */
786 requestBalloon(beval);
787}
788
789#endif /* !FEAT_GUI_GTK */
790
791 static void
792requestBalloon(beval)
793 BalloonEval *beval;
794{
795 if (beval->showState != ShS_PENDING)
796 {
797 /* Determine the beval to display */
798 if (beval->msgCB != NULL)
799 {
800 beval->showState = ShS_PENDING;
801 (*beval->msgCB)(beval, beval->state);
802 }
803 else if (beval->msg != NULL)
804 drawBalloon(beval);
805 }
806}
807
808#ifdef FEAT_GUI_GTK
809
810# ifdef HAVE_GTK2
811/*
812 * Convert the string to UTF-8 if 'encoding' is not "utf-8".
813 * Replace any non-printable characters and invalid bytes sequences with
814 * "^X" or "<xx>" escapes, and apply SpecialKey highlighting to them.
815 * TAB and NL are passed through unscathed.
816 */
817# define IS_NONPRINTABLE(c) (((c) < 0x20 && (c) != TAB && (c) != NL) \
818 || (c) == DEL)
819 static void
820set_printable_label_text(GtkLabel *label, char_u *msg)
821{
822 char_u *convbuf = NULL;
823 char_u *buf;
824 char_u *p;
825 char_u *pdest;
826 unsigned int len;
827 int charlen;
828 int uc;
829 PangoAttrList *attr_list;
830
831 /* Convert to UTF-8 if it isn't already */
832 if (output_conv.vc_type != CONV_NONE)
833 {
834 convbuf = string_convert(&output_conv, msg, NULL);
835 if (convbuf != NULL)
836 msg = convbuf;
837 }
838
839 /* First let's see how much we need to allocate */
840 len = 0;
841 for (p = msg; *p != NUL; p += charlen)
842 {
843 if ((*p & 0x80) == 0) /* be quick for ASCII */
844 {
845 charlen = 1;
846 len += IS_NONPRINTABLE(*p) ? 2 : 1; /* nonprintable: ^X */
847 }
848 else
849 {
850 charlen = utf_ptr2len_check(p);
851 uc = utf_ptr2char(p);
852
853 if (charlen != utf_char2len(uc))
854 charlen = 1; /* reject overlong sequences */
855
856 if (charlen == 1 || uc < 0xa0) /* illegal byte or */
857 len += 4; /* control char: <xx> */
858 else if (!utf_printable(uc))
859 /* Note: we assume here that utf_printable() doesn't
860 * care about characters outside the BMP. */
861 len += 6; /* nonprintable: <xxxx> */
862 else
863 len += charlen;
864 }
865 }
866
867 attr_list = pango_attr_list_new();
868 buf = alloc(len + 1);
869
870 /* Now go for the real work */
871 if (buf != NULL)
872 {
873 attrentry_T *aep;
874 PangoAttribute *attr;
875 guicolor_T pixel;
876 GdkColor color = { 0, 0, 0, 0 };
877
878 /* Look up the RGB values of the SpecialKey foreground color. */
879 aep = syn_gui_attr2entry(hl_attr(HLF_8));
880 pixel = (aep != NULL) ? aep->ae_u.gui.fg_color : INVALCOLOR;
881 if (pixel != INVALCOLOR)
882 gdk_colormap_query_color(gtk_widget_get_colormap(gui.drawarea),
883 (unsigned long)pixel, &color);
884
885 pdest = buf;
886 p = msg;
887 while (*p != NUL)
888 {
889 /* Be quick for ASCII */
890 if ((*p & 0x80) == 0 && !IS_NONPRINTABLE(*p))
891 {
892 *pdest++ = *p++;
893 }
894 else
895 {
896 charlen = utf_ptr2len_check(p);
897 uc = utf_ptr2char(p);
898
899 if (charlen != utf_char2len(uc))
900 charlen = 1; /* reject overlong sequences */
901
902 if (charlen == 1 || uc < 0xa0 || !utf_printable(uc))
903 {
904 int outlen;
905
906 /* Careful: we can't just use transchar_byte() here,
907 * since 'encoding' is not necessarily set to "utf-8". */
908 if (*p & 0x80 && charlen == 1)
909 {
910 transchar_hex(pdest, *p); /* <xx> */
911 outlen = 4;
912 }
913 else if (uc >= 0x80)
914 {
915 /* Note: we assume here that utf_printable() doesn't
916 * care about characters outside the BMP. */
917 transchar_hex(pdest, uc); /* <xx> or <xxxx> */
918 outlen = (uc < 0x100) ? 4 : 6;
919 }
920 else
921 {
922 transchar_nonprint(pdest, *p); /* ^X */
923 outlen = 2;
924 }
925 if (pixel != INVALCOLOR)
926 {
927 attr = pango_attr_foreground_new(
928 color.red, color.green, color.blue);
929 attr->start_index = pdest - buf;
930 attr->end_index = pdest - buf + outlen;
931 pango_attr_list_insert(attr_list, attr);
932 }
933 pdest += outlen;
934 p += charlen;
935 }
936 else
937 {
938 do
939 *pdest++ = *p++;
940 while (--charlen != 0);
941 }
942 }
943 }
944 *pdest = NUL;
945 }
946
947 vim_free(convbuf);
948
949 gtk_label_set_text(label, (const char *)buf);
950 vim_free(buf);
951
952 gtk_label_set_attributes(label, attr_list);
953 pango_attr_list_unref(attr_list);
954}
955# undef IS_NONPRINTABLE
956# endif /* HAVE_GTK2 */
957
958/*
959 * Draw a balloon.
960 */
961 static void
962drawBalloon(BalloonEval *beval)
963{
964 if (beval->msg != NULL)
965 {
966 GtkRequisition requisition;
967 int screen_w;
968 int screen_h;
969 int x;
970 int y;
971 int x_offset = EVAL_OFFSET_X;
972 int y_offset = EVAL_OFFSET_Y;
973# ifdef HAVE_GTK2
974 PangoLayout *layout;
975# endif
976# ifdef HAVE_GTK_MULTIHEAD
977 GdkScreen *screen;
978
979 screen = gtk_widget_get_screen(beval->target);
980 gtk_window_set_screen(GTK_WINDOW(beval->balloonShell), screen);
981 screen_w = gdk_screen_get_width(screen);
982 screen_h = gdk_screen_get_height(screen);
983# else
984 screen_w = gdk_screen_width();
985 screen_h = gdk_screen_height();
986# endif
987 gtk_widget_ensure_style(beval->balloonShell);
988 gtk_widget_ensure_style(beval->balloonLabel);
989
990# ifdef HAVE_GTK2
991 set_printable_label_text(GTK_LABEL(beval->balloonLabel), beval->msg);
992 /*
993 * Dirty trick: Enable wrapping mode on the label's layout behind its
994 * back. This way GtkLabel won't try to constrain the wrap width to a
995 * builtin maximum value of about 65 Latin characters.
996 */
997 layout = gtk_label_get_layout(GTK_LABEL(beval->balloonLabel));
998# ifdef PANGO_WRAP_WORD_CHAR
999 pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
1000# else
1001 pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
1002# endif
1003 pango_layout_set_width(layout,
1004 /* try to come up with some reasonable width */
1005 PANGO_SCALE * CLAMP(gui.num_cols * gui.char_width,
1006 screen_w / 2,
1007 MAX(20, screen_w - 20)));
1008
1009 /* Calculate the balloon's width and height. */
1010 gtk_widget_size_request(beval->balloonShell, &requisition);
1011# else
1012 gtk_label_set_line_wrap(GTK_LABEL(beval->balloonLabel), FALSE);
1013 gtk_label_set_text(GTK_LABEL(beval->balloonLabel),
1014 (const char *)beval->msg);
1015
1016 /* Calculate the balloon's width and height. */
1017 gtk_widget_size_request(beval->balloonShell, &requisition);
1018 /*
1019 * Unfortunately, the dirty trick used above to get around the builtin
1020 * maximum wrap width of GtkLabel doesn't work with GTK+ 1. Thus if
1021 * and only if it's absolutely necessary to avoid drawing off-screen,
1022 * do enable wrapping now and recalculate the size request.
1023 */
1024 if (requisition.width > screen_w)
1025 {
1026 gtk_label_set_line_wrap(GTK_LABEL(beval->balloonLabel), TRUE);
1027 gtk_widget_size_request(beval->balloonShell, &requisition);
1028 }
1029# endif
1030
1031 /* Compute position of the balloon area */
1032 gdk_window_get_origin(beval->target->window, &x, &y);
1033 x += beval->x;
1034 y += beval->y;
1035
1036 /* Get out of the way of the mouse pointer */
1037 if (x + x_offset + requisition.width > screen_w)
1038 y_offset += 15;
1039 if (y + y_offset + requisition.height > screen_h)
1040 y_offset = -requisition.height - EVAL_OFFSET_Y;
1041
1042 /* Sanitize values */
1043 x = CLAMP(x + x_offset, 0, MAX(0, screen_w - requisition.width));
1044 y = CLAMP(y + y_offset, 0, MAX(0, screen_h - requisition.height));
1045
1046 /* Show the balloon */
1047 gtk_widget_set_uposition(beval->balloonShell, x, y);
1048 gtk_widget_show(beval->balloonShell);
1049
1050 beval->showState = ShS_SHOWING;
1051 }
1052}
1053
1054/*
1055 * Undraw a balloon.
1056 */
1057 static void
1058undrawBalloon(BalloonEval *beval)
1059{
1060 if (beval->balloonShell != NULL)
1061 gtk_widget_hide(beval->balloonShell);
1062 beval->showState = ShS_NEUTRAL;
1063}
1064
1065 static void
1066cancelBalloon(BalloonEval *beval)
1067{
1068 if (beval->showState == ShS_SHOWING
1069 || beval->showState == ShS_UPDATE_PENDING)
1070 undrawBalloon(beval);
1071
1072 if (beval->timerID != 0)
1073 {
1074 gtk_timeout_remove(beval->timerID);
1075 beval->timerID = 0;
1076 }
1077 beval->showState = ShS_NEUTRAL;
1078}
1079
1080 static void
1081createBalloonEvalWindow(BalloonEval *beval)
1082{
1083 beval->balloonShell = gtk_window_new(GTK_WINDOW_POPUP);
1084
1085 gtk_widget_set_app_paintable(beval->balloonShell, TRUE);
1086 gtk_window_set_policy(GTK_WINDOW(beval->balloonShell), FALSE, FALSE, TRUE);
1087 gtk_widget_set_name(beval->balloonShell, "gtk-tooltips");
1088 gtk_container_border_width(GTK_CONTAINER(beval->balloonShell), 4);
1089
1090 gtk_signal_connect((GtkObject*)(beval->balloonShell), "expose_event",
1091 GTK_SIGNAL_FUNC(balloon_expose_event_cb), NULL);
1092# ifndef HAVE_GTK2
1093 gtk_signal_connect((GtkObject*)(beval->balloonShell), "draw",
1094 GTK_SIGNAL_FUNC(balloon_draw_cb), NULL);
1095# endif
1096 beval->balloonLabel = gtk_label_new(NULL);
1097
1098 gtk_label_set_line_wrap(GTK_LABEL(beval->balloonLabel), FALSE);
1099 gtk_label_set_justify(GTK_LABEL(beval->balloonLabel), GTK_JUSTIFY_LEFT);
1100 gtk_misc_set_alignment(GTK_MISC(beval->balloonLabel), 0.5f, 0.5f);
1101 gtk_widget_set_name(beval->balloonLabel, "vim-balloon-label");
1102 gtk_widget_show(beval->balloonLabel);
1103
1104 gtk_container_add(GTK_CONTAINER(beval->balloonShell), beval->balloonLabel);
1105}
1106
1107#else /* !FEAT_GUI_GTK */
1108
1109/*
1110 * Draw a balloon.
1111 */
1112 static void
1113drawBalloon(beval)
1114 BalloonEval *beval;
1115{
1116 Dimension w;
1117 Dimension h;
1118 Position tx;
1119 Position ty;
1120
1121 if (beval->msg != NULL)
1122 {
1123 /* Show the Balloon */
1124
1125 /* Calculate the label's width and height */
1126#ifdef FEAT_GUI_MOTIF
1127 XmString s;
1128
1129 /* For the callback function we parse NL characters to create a
1130 * multi-line label. This doesn't work for all languages, but
1131 * XmStringCreateLocalized() doesn't do multi-line labels... */
1132 if (beval->msgCB != NULL)
1133 s = XmStringCreateLtoR((char *)beval->msg, XmFONTLIST_DEFAULT_TAG);
1134 else
1135 s = XmStringCreateLocalized((char *)beval->msg);
1136 {
1137 XmFontList fl;
1138
1139 fl = gui_motif_fontset2fontlist(&gui.tooltip_fontset);
1140 if (fl != NULL)
1141 {
1142 XmStringExtent(fl, s, &w, &h);
1143 XmFontListFree(fl);
1144 }
1145 }
1146 w += gui.border_offset << 1;
1147 h += gui.border_offset << 1;
1148 XtVaSetValues(beval->balloonLabel, XmNlabelString, s, NULL);
1149 XmStringFree(s);
1150#else /* Athena */
1151 /* Assume XtNinternational == True */
1152 XFontSet fset;
1153 XFontSetExtents *ext;
1154
1155 XtVaGetValues(beval->balloonLabel, XtNfontSet, &fset, NULL);
1156 ext = XExtentsOfFontSet(fset);
1157 h = ext->max_ink_extent.height;
1158 w = XmbTextEscapement(fset,
1159 (char *)beval->msg,
1160 (int)STRLEN(beval->msg));
1161 w += gui.border_offset << 1;
1162 h += gui.border_offset << 1;
1163 XtVaSetValues(beval->balloonLabel, XtNlabel, beval->msg, NULL);
1164#endif
1165
1166 /* Compute position of the balloon area */
1167 tx = beval->x_root + EVAL_OFFSET_X;
1168 ty = beval->y_root + EVAL_OFFSET_Y;
1169 if ((tx + w) > beval->screen_width)
1170 tx = beval->screen_width - w;
1171 if ((ty + h) > beval->screen_height)
1172 ty = beval->screen_height - h;
1173#ifdef FEAT_GUI_MOTIF
1174 XtVaSetValues(beval->balloonShell,
1175 XmNx, tx,
1176 XmNy, ty,
1177 NULL);
1178#else
1179 /* Athena */
1180 XtVaSetValues(beval->balloonShell,
1181 XtNx, tx,
1182 XtNy, ty,
1183 NULL);
1184#endif
1185
1186 XtPopup(beval->balloonShell, XtGrabNone);
1187
1188 beval->showState = ShS_SHOWING;
1189
1190 current_beval = beval;
1191 }
1192}
1193
1194/*
1195 * Undraw a balloon.
1196 */
1197 static void
1198undrawBalloon(beval)
1199 BalloonEval *beval;
1200{
1201 if (beval->balloonShell != (Widget)0)
1202 XtPopdown(beval->balloonShell);
1203 beval->showState = ShS_NEUTRAL;
1204
1205 current_beval = NULL;
1206}
1207
1208 static void
1209cancelBalloon(beval)
1210 BalloonEval *beval;
1211{
1212 if (beval->showState == ShS_SHOWING
1213 || beval->showState == ShS_UPDATE_PENDING)
1214 undrawBalloon(beval);
1215
1216 if (beval->timerID != (XtIntervalId)NULL)
1217 {
1218 XtRemoveTimeOut(beval->timerID);
1219 beval->timerID = (XtIntervalId)NULL;
1220 }
1221 beval->showState = ShS_NEUTRAL;
1222}
1223
1224
1225 static void
1226createBalloonEvalWindow(beval)
1227 BalloonEval *beval;
1228{
1229 Arg args[12];
1230 int n;
1231
1232 n = 0;
1233#ifdef FEAT_GUI_MOTIF
1234 XtSetArg(args[n], XmNallowShellResize, True); n++;
1235 beval->balloonShell = XtAppCreateShell("balloonEval", "BalloonEval",
1236 overrideShellWidgetClass, gui.dpy, args, n);
1237#else
1238 /* Athena */
1239 XtSetArg(args[n], XtNallowShellResize, True); n++;
1240 beval->balloonShell = XtAppCreateShell("balloonEval", "BalloonEval",
1241 overrideShellWidgetClass, gui.dpy, args, n);
1242#endif
1243
1244 n = 0;
1245#ifdef FEAT_GUI_MOTIF
1246 {
1247 XmFontList fl;
1248
1249 fl = gui_motif_fontset2fontlist(&gui.tooltip_fontset);
1250 XtSetArg(args[n], XmNforeground, gui.tooltip_fg_pixel); n++;
1251 XtSetArg(args[n], XmNbackground, gui.tooltip_bg_pixel); n++;
1252 XtSetArg(args[n], XmNfontList, fl); n++;
1253 XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
1254 beval->balloonLabel = XtCreateManagedWidget("balloonLabel",
1255 xmLabelWidgetClass, beval->balloonShell, args, n);
1256 }
1257#else /* FEAT_GUI_ATHENA */
1258 XtSetArg(args[n], XtNforeground, gui.tooltip_fg_pixel); n++;
1259 XtSetArg(args[n], XtNbackground, gui.tooltip_bg_pixel); n++;
1260 XtSetArg(args[n], XtNinternational, True); n++;
1261 XtSetArg(args[n], XtNfontSet, gui.tooltip_fontset); n++;
1262 beval->balloonLabel = XtCreateManagedWidget("balloonLabel",
1263 labelWidgetClass, beval->balloonShell, args, n);
1264#endif
1265}
1266
1267#endif /* !FEAT_GUI_GTK */
1268#endif /* !FEAT_GUI_W32 */
1269
1270#endif /* FEAT_BEVAL */