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