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