home *** CD-ROM | disk | FTP | other *** search
/ vim.ftp.fu-berlin.de / 2015-02-03.vim.ftp.fu-berlin.de.tar / vim.ftp.fu-berlin.de / unix / vim-6.2.tar.bz2 / vim-6.2.tar / vim62 / src / gui_beval.c < prev    next >
Encoding:
C/C++ Source or Header  |  2003-05-31  |  31.1 KB  |  1,260 lines

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