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