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