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