blob: 8c1cd9696fd12c88b2f4f16290937a3bb9e27419 [file] [log] [blame]
Bram Moolenaaredf3f972016-08-29 22:49:24 +02001/* vi:set ts=8 sts=4 sw=4 noet:
Bram Moolenaar071d4272004-06-13 20:20:40 +00002 *
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
Bram Moolenaarc3719bd2017-11-18 22:13:31 +010013#if defined(FEAT_BEVAL_GUI) || defined(PROTO)
Bram Moolenaare2ac10d2005-03-07 23:26:06 +000014
15/* on Win32 only get_beval_info() is required */
Bram Moolenaar071d4272004-06-13 20:20:40 +000016#if !defined(FEAT_GUI_W32) || defined(PROTO)
17
18#ifdef FEAT_GUI_GTK
Bram Moolenaar98921892016-02-23 17:14:37 +010019# if GTK_CHECK_VERSION(3,0,0)
20# include <gdk/gdkkeysyms-compat.h>
21# else
22# include <gdk/gdkkeysyms.h>
23# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000024# 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>
Bram Moolenaar238a5642006-02-21 22:12:05 +000037# ifdef FEAT_GUI_NEXTAW
38# include <X11/neXtaw/Label.h>
39# else
40# include <X11/Xaw/Label.h>
41# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000042# endif
43#endif
44
Bram Moolenaar071d4272004-06-13 20:20:40 +000045#ifndef FEAT_GUI_GTK
46extern 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 */
53static BalloonEval *current_beval = NULL;
54#endif
55
56#ifdef FEAT_GUI_GTK
Bram Moolenaard25c16e2016-01-29 22:13:30 +010057static void addEventHandler(GtkWidget *, BalloonEval *);
58static void removeEventHandler(BalloonEval *);
59static gint target_event_cb(GtkWidget *, GdkEvent *, gpointer);
60static gint mainwin_event_cb(GtkWidget *, GdkEvent *, gpointer);
61static void pointer_event(BalloonEval *, int, int, unsigned);
62static void key_event(BalloonEval *, unsigned, int);
Bram Moolenaar98921892016-02-23 17:14:37 +010063# if GTK_CHECK_VERSION(3,0,0)
64static gboolean timeout_cb(gpointer);
65# else
Bram Moolenaard25c16e2016-01-29 22:13:30 +010066static gint timeout_cb(gpointer);
Bram Moolenaar98921892016-02-23 17:14:37 +010067# endif
68# if GTK_CHECK_VERSION(3,0,0)
69static gboolean balloon_draw_event_cb (GtkWidget *, cairo_t *, gpointer);
70# else
71static gint balloon_expose_event_cb (GtkWidget *, GdkEventExpose *, gpointer);
72# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +000073#else
Bram Moolenaard25c16e2016-01-29 22:13:30 +010074static void addEventHandler(Widget, BalloonEval *);
75static void removeEventHandler(BalloonEval *);
76static void pointerEventEH(Widget, XtPointer, XEvent *, Boolean *);
77static void pointerEvent(BalloonEval *, XEvent *);
78static void timerRoutine(XtPointer, XtIntervalId *);
Bram Moolenaar071d4272004-06-13 20:20:40 +000079#endif
Bram Moolenaard25c16e2016-01-29 22:13:30 +010080static void cancelBalloon(BalloonEval *);
81static void requestBalloon(BalloonEval *);
82static void drawBalloon(BalloonEval *);
83static void undrawBalloon(BalloonEval *beval);
84static void createBalloonEvalWindow(BalloonEval *);
Bram Moolenaar071d4272004-06-13 20:20:40 +000085
Bram Moolenaar071d4272004-06-13 20:20:40 +000086/*
87 * Create a balloon-evaluation area for a Widget.
88 * There can be either a "mesg" for a fixed string or "mesgCB" to generate a
89 * message by calling this callback function.
90 * When "mesg" is not NULL it must remain valid for as long as the balloon is
91 * used. It is not freed here.
92 * Returns a pointer to the resulting object (NULL when out of memory).
93 */
94 BalloonEval *
Bram Moolenaar66f948e2016-01-30 16:39:25 +010095gui_mch_create_beval_area(
96 void *target,
97 char_u *mesg,
98 void (*mesgCB)(BalloonEval *, int),
99 void *clientData)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000100{
101#ifndef FEAT_GUI_GTK
102 char *display_name; /* get from gui.dpy */
103 int screen_num;
104 char *p;
105#endif
106 BalloonEval *beval;
107
108 if (mesg != NULL && mesgCB != NULL)
109 {
Bram Moolenaar95f09602016-11-10 20:01:45 +0100110 IEMSG(_("E232: Cannot create BalloonEval with both message and callback"));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000111 return NULL;
112 }
113
114 beval = (BalloonEval *)alloc(sizeof(BalloonEval));
115 if (beval != NULL)
116 {
117#ifdef FEAT_GUI_GTK
118 beval->target = GTK_WIDGET(target);
119 beval->balloonShell = NULL;
120 beval->timerID = 0;
121#else
122 beval->target = (Widget)target;
123 beval->balloonShell = NULL;
124 beval->timerID = (XtIntervalId)NULL;
125 beval->appContext = XtWidgetToApplicationContext((Widget)target);
126#endif
127 beval->showState = ShS_NEUTRAL;
128 beval->x = 0;
129 beval->y = 0;
130 beval->msg = mesg;
131 beval->msgCB = mesgCB;
132 beval->clientData = clientData;
133
134 /*
135 * Set up event handler which will keep its eyes on the pointer,
136 * and when the pointer rests in a certain spot for a given time
137 * interval, show the beval.
138 */
139 addEventHandler(beval->target, beval);
140 createBalloonEvalWindow(beval);
141
142#ifndef FEAT_GUI_GTK
143 /*
144 * Now create and save the screen width and height. Used in drawing.
145 */
146 display_name = DisplayString(gui.dpy);
147 p = strrchr(display_name, '.');
148 if (p != NULL)
149 screen_num = atoi(++p);
150 else
151 screen_num = 0;
152 beval->screen_width = DisplayWidth(gui.dpy, screen_num);
153 beval->screen_height = DisplayHeight(gui.dpy, screen_num);
154#endif
155 }
156
157 return beval;
158}
159
160#if defined(FEAT_BEVAL_TIP) || defined(PROTO)
161/*
Bram Moolenaarbae0c162007-05-10 19:30:25 +0000162 * Destroy a balloon-eval and free its associated memory.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000163 */
164 void
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100165gui_mch_destroy_beval_area(BalloonEval *beval)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000166{
167 cancelBalloon(beval);
168 removeEventHandler(beval);
169 /* Children will automatically be destroyed */
170# ifdef FEAT_GUI_GTK
171 gtk_widget_destroy(beval->balloonShell);
172# else
173 XtDestroyWidget(beval->balloonShell);
174# endif
175 vim_free(beval);
176}
177#endif
178
179 void
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100180gui_mch_enable_beval_area(BalloonEval *beval)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000181{
182 if (beval != NULL)
183 addEventHandler(beval->target, beval);
184}
185
186 void
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100187gui_mch_disable_beval_area(BalloonEval *beval)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000188{
189 if (beval != NULL)
190 removeEventHandler(beval);
191}
192
193#if defined(FEAT_BEVAL_TIP) || defined(PROTO)
194/*
195 * This function returns the BalloonEval * associated with the currently
196 * displayed tooltip. Returns NULL if there is no tooltip currently showing.
197 *
198 * Assumption: Only one tooltip can be shown at a time.
199 */
200 BalloonEval *
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100201gui_mch_currently_showing_beval(void)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000202{
203 return current_beval;
204}
205#endif
206#endif /* !FEAT_GUI_W32 */
207
Bram Moolenaare2ac10d2005-03-07 23:26:06 +0000208#if defined(FEAT_SUN_WORKSHOP) || defined(FEAT_NETBEANS_INTG) \
209 || defined(FEAT_EVAL) || defined(PROTO)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000210# if !defined(FEAT_GUI_W32) || defined(PROTO)
211
212/*
213 * Show a balloon with "mesg".
214 */
215 void
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100216gui_mch_post_balloon(BalloonEval *beval, char_u *mesg)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000217{
218 beval->msg = mesg;
219 if (mesg != NULL)
220 drawBalloon(beval);
221 else
222 undrawBalloon(beval);
223}
Bram Moolenaarc3719bd2017-11-18 22:13:31 +0100224# endif /* !FEAT_GUI_W32 */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000225#endif /* FEAT_SUN_WORKSHOP || FEAT_NETBEANS_INTG || PROTO */
226
227#if !defined(FEAT_GUI_W32) || defined(PROTO)
228#if defined(FEAT_BEVAL_TIP) || defined(PROTO)
229/*
230 * Hide the given balloon.
231 */
232 void
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100233gui_mch_unpost_balloon(BalloonEval *beval)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000234{
235 undrawBalloon(beval);
236}
237#endif
238
239#ifdef FEAT_GUI_GTK
Bram Moolenaar071d4272004-06-13 20:20:40 +0000240 static void
241addEventHandler(GtkWidget *target, BalloonEval *beval)
242{
243 /*
244 * Connect to the generic "event" signal instead of the individual
245 * signals for each event type, because the former is emitted earlier.
246 * This allows us to catch events independently of the signal handlers
247 * in gui_gtk_x11.c.
248 */
Bram Moolenaar98921892016-02-23 17:14:37 +0100249# if GTK_CHECK_VERSION(3,0,0)
250 g_signal_connect(G_OBJECT(target), "event",
251 G_CALLBACK(target_event_cb),
252 beval);
253# else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000254 /* Should use GTK_OBJECT() here, but that causes a lint warning... */
255 gtk_signal_connect((GtkObject*)(target), "event",
256 GTK_SIGNAL_FUNC(target_event_cb),
257 beval);
Bram Moolenaar98921892016-02-23 17:14:37 +0100258# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000259 /*
260 * Nasty: Key press events go to the main window thus the drawing area
261 * will never see them. This means we have to connect to the main window
262 * as well in order to catch those events.
263 */
264 if (gtk_socket_id == 0 && gui.mainwin != NULL
265 && gtk_widget_is_ancestor(target, gui.mainwin))
266 {
Bram Moolenaar98921892016-02-23 17:14:37 +0100267# if GTK_CHECK_VERSION(3,0,0)
268 g_signal_connect(G_OBJECT(gui.mainwin), "event",
269 G_CALLBACK(mainwin_event_cb),
270 beval);
271# else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000272 gtk_signal_connect((GtkObject*)(gui.mainwin), "event",
273 GTK_SIGNAL_FUNC(mainwin_event_cb),
274 beval);
Bram Moolenaar98921892016-02-23 17:14:37 +0100275# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000276 }
277}
278
279 static void
280removeEventHandler(BalloonEval *beval)
281{
Bram Moolenaar33570922005-01-25 22:26:29 +0000282 /* LINTED: avoid warning: dubious operation on enum */
Bram Moolenaar98921892016-02-23 17:14:37 +0100283# if GTK_CHECK_VERSION(3,0,0)
284 g_signal_handlers_disconnect_by_func(G_OBJECT(beval->target),
Bram Moolenaard47d8372016-09-09 22:13:24 +0200285 FUNC2GENERIC(target_event_cb),
Bram Moolenaar98921892016-02-23 17:14:37 +0100286 beval);
287# else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000288 gtk_signal_disconnect_by_func((GtkObject*)(beval->target),
289 GTK_SIGNAL_FUNC(target_event_cb),
290 beval);
Bram Moolenaar98921892016-02-23 17:14:37 +0100291# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000292
293 if (gtk_socket_id == 0 && gui.mainwin != NULL
294 && gtk_widget_is_ancestor(beval->target, gui.mainwin))
295 {
Bram Moolenaar33570922005-01-25 22:26:29 +0000296 /* LINTED: avoid warning: dubious operation on enum */
Bram Moolenaar98921892016-02-23 17:14:37 +0100297# if GTK_CHECK_VERSION(3,0,0)
298 g_signal_handlers_disconnect_by_func(G_OBJECT(gui.mainwin),
Bram Moolenaard47d8372016-09-09 22:13:24 +0200299 FUNC2GENERIC(mainwin_event_cb),
Bram Moolenaar98921892016-02-23 17:14:37 +0100300 beval);
301# else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000302 gtk_signal_disconnect_by_func((GtkObject*)(gui.mainwin),
303 GTK_SIGNAL_FUNC(mainwin_event_cb),
304 beval);
Bram Moolenaar98921892016-02-23 17:14:37 +0100305# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000306 }
307}
308
309 static gint
310target_event_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
311{
312 BalloonEval *beval = (BalloonEval *)data;
313
314 switch (event->type)
315 {
316 case GDK_ENTER_NOTIFY:
317 pointer_event(beval, (int)event->crossing.x,
318 (int)event->crossing.y,
319 event->crossing.state);
320 break;
321 case GDK_MOTION_NOTIFY:
322 if (event->motion.is_hint)
323 {
324 int x;
325 int y;
326 GdkModifierType state;
327 /*
328 * GDK_POINTER_MOTION_HINT_MASK is set, thus we cannot obtain
329 * the coordinates from the GdkEventMotion struct directly.
330 */
Bram Moolenaar98921892016-02-23 17:14:37 +0100331# if GTK_CHECK_VERSION(3,0,0)
332 {
333 GdkWindow * const win = gtk_widget_get_window(widget);
334 GdkDisplay * const dpy = gdk_window_get_display(win);
Bram Moolenaar30e12d22016-04-17 20:49:53 +0200335# if GTK_CHECK_VERSION(3,20,0)
336 GdkSeat * const seat = gdk_display_get_default_seat(dpy);
337 GdkDevice * const dev = gdk_seat_get_pointer(seat);
338# else
Bram Moolenaar98921892016-02-23 17:14:37 +0100339 GdkDeviceManager * const mngr = gdk_display_get_device_manager(dpy);
340 GdkDevice * const dev = gdk_device_manager_get_client_pointer(mngr);
Bram Moolenaar30e12d22016-04-17 20:49:53 +0200341# endif
Bram Moolenaar98921892016-02-23 17:14:37 +0100342 gdk_window_get_device_position(win, dev , &x, &y, &state);
343 }
344# else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000345 gdk_window_get_pointer(widget->window, &x, &y, &state);
Bram Moolenaar98921892016-02-23 17:14:37 +0100346# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000347 pointer_event(beval, x, y, (unsigned int)state);
348 }
349 else
350 {
351 pointer_event(beval, (int)event->motion.x,
352 (int)event->motion.y,
353 event->motion.state);
354 }
355 break;
356 case GDK_LEAVE_NOTIFY:
357 /*
358 * Ignore LeaveNotify events that are not "normal".
359 * Apparently we also get it when somebody else grabs focus.
360 */
361 if (event->crossing.mode == GDK_CROSSING_NORMAL)
362 cancelBalloon(beval);
363 break;
364 case GDK_BUTTON_PRESS:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000365 case GDK_SCROLL:
Bram Moolenaar071d4272004-06-13 20:20:40 +0000366 cancelBalloon(beval);
367 break;
368 case GDK_KEY_PRESS:
369 key_event(beval, event->key.keyval, TRUE);
370 break;
371 case GDK_KEY_RELEASE:
372 key_event(beval, event->key.keyval, FALSE);
373 break;
374 default:
375 break;
376 }
377
378 return FALSE; /* continue emission */
379}
380
Bram Moolenaar071d4272004-06-13 20:20:40 +0000381 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000382mainwin_event_cb(GtkWidget *widget UNUSED, GdkEvent *event, gpointer data)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000383{
384 BalloonEval *beval = (BalloonEval *)data;
385
386 switch (event->type)
387 {
388 case GDK_KEY_PRESS:
389 key_event(beval, event->key.keyval, TRUE);
390 break;
391 case GDK_KEY_RELEASE:
392 key_event(beval, event->key.keyval, FALSE);
393 break;
394 default:
395 break;
396 }
397
398 return FALSE; /* continue emission */
399}
400
401 static void
402pointer_event(BalloonEval *beval, int x, int y, unsigned state)
403{
404 int distance;
405
406 distance = ABS(x - beval->x) + ABS(y - beval->y);
407
408 if (distance > 4)
409 {
410 /*
411 * Moved out of the balloon location: cancel it.
412 * Remember button state
413 */
414 beval->state = state;
415 cancelBalloon(beval);
416
417 /* Mouse buttons are pressed - no balloon now */
418 if (!(state & ((int)GDK_BUTTON1_MASK | (int)GDK_BUTTON2_MASK
419 | (int)GDK_BUTTON3_MASK)))
420 {
421 beval->x = x;
422 beval->y = y;
423
424 if (state & (int)GDK_MOD1_MASK)
425 {
426 /*
427 * Alt is pressed -- enter super-evaluate-mode,
428 * where there is no time delay
429 */
430 if (beval->msgCB != NULL)
431 {
432 beval->showState = ShS_PENDING;
433 (*beval->msgCB)(beval, state);
434 }
435 }
436 else
437 {
Bram Moolenaar98921892016-02-23 17:14:37 +0100438# if GTK_CHECK_VERSION(3,0,0)
439 beval->timerID = g_timeout_add((guint)p_bdlay,
440 &timeout_cb, beval);
441# else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000442 beval->timerID = gtk_timeout_add((guint32)p_bdlay,
443 &timeout_cb, beval);
Bram Moolenaar98921892016-02-23 17:14:37 +0100444# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000445 }
446 }
447 }
448}
449
450 static void
451key_event(BalloonEval *beval, unsigned keyval, int is_keypress)
452{
453 if (beval->showState == ShS_SHOWING && beval->msgCB != NULL)
454 {
455 switch (keyval)
456 {
457 case GDK_Shift_L:
458 case GDK_Shift_R:
459 beval->showState = ShS_UPDATE_PENDING;
460 (*beval->msgCB)(beval, (is_keypress)
461 ? (int)GDK_SHIFT_MASK : 0);
462 break;
463 case GDK_Control_L:
464 case GDK_Control_R:
465 beval->showState = ShS_UPDATE_PENDING;
466 (*beval->msgCB)(beval, (is_keypress)
467 ? (int)GDK_CONTROL_MASK : 0);
468 break;
469 default:
Bram Moolenaar1d2ba7f2006-02-14 22:29:30 +0000470 /* Don't do this for key release, we apparently get these with
471 * focus changes in some GTK version. */
472 if (is_keypress)
473 cancelBalloon(beval);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000474 break;
475 }
476 }
477 else
478 cancelBalloon(beval);
479}
480
Bram Moolenaar98921892016-02-23 17:14:37 +0100481# if GTK_CHECK_VERSION(3,0,0)
482 static gboolean
483# else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000484 static gint
Bram Moolenaar98921892016-02-23 17:14:37 +0100485# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000486timeout_cb(gpointer data)
487{
488 BalloonEval *beval = (BalloonEval *)data;
489
490 beval->timerID = 0;
491 /*
492 * If the timer event happens then the mouse has stopped long enough for
493 * a request to be started. The request will only send to the debugger if
494 * there the mouse is pointing at real data.
495 */
496 requestBalloon(beval);
497
498 return FALSE; /* don't call me again */
499}
500
Bram Moolenaar98921892016-02-23 17:14:37 +0100501# if GTK_CHECK_VERSION(3,0,0)
502 static gboolean
503balloon_draw_event_cb(GtkWidget *widget,
504 cairo_t *cr,
505 gpointer data UNUSED)
506{
507 GtkStyleContext *context = NULL;
508 gint width = -1, height = -1;
509
510 if (widget == NULL)
511 return TRUE;
512
513 context = gtk_widget_get_style_context(widget);
514 width = gtk_widget_get_allocated_width(widget);
515 height = gtk_widget_get_allocated_height(widget);
516
517 gtk_style_context_save(context);
518
519 gtk_style_context_add_class(context, "tooltip");
520 gtk_style_context_set_state(context, GTK_STATE_FLAG_NORMAL);
521
522 cairo_save(cr);
523 gtk_render_frame(context, cr, 0, 0, width, height);
524 gtk_render_background(context, cr, 0, 0, width, height);
525 cairo_restore(cr);
526
527 gtk_style_context_restore(context);
528
529 return FALSE;
530}
531# else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000532 static gint
Bram Moolenaarb85cb212009-05-17 14:24:23 +0000533balloon_expose_event_cb(GtkWidget *widget,
534 GdkEventExpose *event,
535 gpointer data UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000536{
537 gtk_paint_flat_box(widget->style, widget->window,
538 GTK_STATE_NORMAL, GTK_SHADOW_OUT,
539 &event->area, widget, "tooltip",
540 0, 0, -1, -1);
541
542 return FALSE; /* continue emission */
543}
Bram Moolenaar98921892016-02-23 17:14:37 +0100544# endif /* !GTK_CHECK_VERSION(3,0,0) */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000545
Bram Moolenaar071d4272004-06-13 20:20:40 +0000546#else /* !FEAT_GUI_GTK */
547
548 static void
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100549addEventHandler(Widget target, BalloonEval *beval)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000550{
551 XtAddEventHandler(target,
552 PointerMotionMask | EnterWindowMask |
553 LeaveWindowMask | ButtonPressMask | KeyPressMask |
554 KeyReleaseMask,
555 False,
556 pointerEventEH, (XtPointer)beval);
557}
558
559 static void
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100560removeEventHandler(BalloonEval *beval)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000561{
562 XtRemoveEventHandler(beval->target,
563 PointerMotionMask | EnterWindowMask |
564 LeaveWindowMask | ButtonPressMask | KeyPressMask |
565 KeyReleaseMask,
566 False,
567 pointerEventEH, (XtPointer)beval);
568}
569
570
571/*
572 * The X event handler. All it does is call the real event handler.
573 */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000574 static void
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100575pointerEventEH(
576 Widget w UNUSED,
577 XtPointer client_data,
578 XEvent *event,
579 Boolean *unused UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000580{
581 BalloonEval *beval = (BalloonEval *)client_data;
582 pointerEvent(beval, event);
583}
584
585
586/*
587 * The real event handler. Called by pointerEventEH() whenever an event we are
Bram Moolenaarbae0c162007-05-10 19:30:25 +0000588 * interested in occurs.
Bram Moolenaar071d4272004-06-13 20:20:40 +0000589 */
590
591 static void
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100592pointerEvent(BalloonEval *beval, XEvent *event)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000593{
Bram Moolenaar84a05ac2013-05-06 04:24:17 +0200594 Position distance; /* a measure of how much the pointer moved */
Bram Moolenaar071d4272004-06-13 20:20:40 +0000595 Position delta; /* used to compute distance */
596
597 switch (event->type)
598 {
599 case EnterNotify:
600 case MotionNotify:
601 delta = event->xmotion.x - beval->x;
602 if (delta < 0)
603 delta = -delta;
604 distance = delta;
605 delta = event->xmotion.y - beval->y;
606 if (delta < 0)
607 delta = -delta;
608 distance += delta;
609 if (distance > 4)
610 {
611 /*
612 * Moved out of the balloon location: cancel it.
613 * Remember button state
614 */
615 beval->state = event->xmotion.state;
616 if (beval->state & (Button1Mask|Button2Mask|Button3Mask))
617 {
618 /* Mouse buttons are pressed - no balloon now */
619 cancelBalloon(beval);
620 }
621 else if (beval->state & (Mod1Mask|Mod2Mask|Mod3Mask))
622 {
623 /*
624 * Alt is pressed -- enter super-evaluate-mode,
625 * where there is no time delay
626 */
627 beval->x = event->xmotion.x;
628 beval->y = event->xmotion.y;
629 beval->x_root = event->xmotion.x_root;
630 beval->y_root = event->xmotion.y_root;
631 cancelBalloon(beval);
632 if (beval->msgCB != NULL)
633 {
634 beval->showState = ShS_PENDING;
635 (*beval->msgCB)(beval, beval->state);
636 }
637 }
638 else
639 {
640 beval->x = event->xmotion.x;
641 beval->y = event->xmotion.y;
642 beval->x_root = event->xmotion.x_root;
643 beval->y_root = event->xmotion.y_root;
644 cancelBalloon(beval);
645 beval->timerID = XtAppAddTimeOut( beval->appContext,
646 (long_u)p_bdlay, timerRoutine, beval);
647 }
648 }
649 break;
650
651 /*
652 * Motif and Athena version: Keystrokes will be caught by the
653 * "textArea" widget, and handled in gui_x11_key_hit_cb().
654 */
655 case KeyPress:
656 if (beval->showState == ShS_SHOWING && beval->msgCB != NULL)
657 {
658 Modifiers modifier;
659 KeySym keysym;
660
661 XtTranslateKeycode(gui.dpy,
662 event->xkey.keycode, event->xkey.state,
663 &modifier, &keysym);
664 if (keysym == XK_Shift_L || keysym == XK_Shift_R)
665 {
666 beval->showState = ShS_UPDATE_PENDING;
667 (*beval->msgCB)(beval, ShiftMask);
668 }
669 else if (keysym == XK_Control_L || keysym == XK_Control_R)
670 {
671 beval->showState = ShS_UPDATE_PENDING;
672 (*beval->msgCB)(beval, ControlMask);
673 }
674 else
675 cancelBalloon(beval);
676 }
677 else
678 cancelBalloon(beval);
679 break;
680
681 case KeyRelease:
682 if (beval->showState == ShS_SHOWING && beval->msgCB != NULL)
683 {
684 Modifiers modifier;
685 KeySym keysym;
686
687 XtTranslateKeycode(gui.dpy, event->xkey.keycode,
688 event->xkey.state, &modifier, &keysym);
689 if ((keysym == XK_Shift_L) || (keysym == XK_Shift_R)) {
690 beval->showState = ShS_UPDATE_PENDING;
691 (*beval->msgCB)(beval, 0);
692 }
693 else if ((keysym == XK_Control_L) || (keysym == XK_Control_R))
694 {
695 beval->showState = ShS_UPDATE_PENDING;
696 (*beval->msgCB)(beval, 0);
697 }
698 else
699 cancelBalloon(beval);
700 }
701 else
702 cancelBalloon(beval);
703 break;
704
705 case LeaveNotify:
706 /* Ignore LeaveNotify events that are not "normal".
707 * Apparently we also get it when somebody else grabs focus.
708 * Happens for me every two seconds (some clipboard tool?) */
709 if (event->xcrossing.mode == NotifyNormal)
710 cancelBalloon(beval);
711 break;
712
713 case ButtonPress:
714 cancelBalloon(beval);
715 break;
716
717 default:
718 break;
719 }
720}
721
Bram Moolenaar071d4272004-06-13 20:20:40 +0000722 static void
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100723timerRoutine(XtPointer dx, XtIntervalId *id UNUSED)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000724{
725 BalloonEval *beval = (BalloonEval *)dx;
726
727 beval->timerID = (XtIntervalId)NULL;
728
729 /*
730 * If the timer event happens then the mouse has stopped long enough for
731 * a request to be started. The request will only send to the debugger if
732 * there the mouse is pointing at real data.
733 */
734 requestBalloon(beval);
735}
736
737#endif /* !FEAT_GUI_GTK */
738
739 static void
Bram Moolenaar66f948e2016-01-30 16:39:25 +0100740requestBalloon(BalloonEval *beval)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000741{
742 if (beval->showState != ShS_PENDING)
743 {
744 /* Determine the beval to display */
745 if (beval->msgCB != NULL)
746 {
747 beval->showState = ShS_PENDING;
748 (*beval->msgCB)(beval, beval->state);
749 }
750 else if (beval->msg != NULL)
751 drawBalloon(beval);
752 }
753}
754
755#ifdef FEAT_GUI_GTK
Bram Moolenaar071d4272004-06-13 20:20:40 +0000756/*
757 * Convert the string to UTF-8 if 'encoding' is not "utf-8".
758 * Replace any non-printable characters and invalid bytes sequences with
759 * "^X" or "<xx>" escapes, and apply SpecialKey highlighting to them.
760 * TAB and NL are passed through unscathed.
761 */
Bram Moolenaar182c5be2010-06-25 05:37:59 +0200762# define IS_NONPRINTABLE(c) (((c) < 0x20 && (c) != TAB && (c) != NL) \
Bram Moolenaar071d4272004-06-13 20:20:40 +0000763 || (c) == DEL)
764 static void
Bram Moolenaar89d40322006-08-29 15:30:07 +0000765set_printable_label_text(GtkLabel *label, char_u *text)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000766{
767 char_u *convbuf = NULL;
768 char_u *buf;
769 char_u *p;
770 char_u *pdest;
771 unsigned int len;
772 int charlen;
773 int uc;
774 PangoAttrList *attr_list;
775
776 /* Convert to UTF-8 if it isn't already */
777 if (output_conv.vc_type != CONV_NONE)
778 {
Bram Moolenaar89d40322006-08-29 15:30:07 +0000779 convbuf = string_convert(&output_conv, text, NULL);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000780 if (convbuf != NULL)
Bram Moolenaar89d40322006-08-29 15:30:07 +0000781 text = convbuf;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000782 }
783
784 /* First let's see how much we need to allocate */
785 len = 0;
Bram Moolenaar89d40322006-08-29 15:30:07 +0000786 for (p = text; *p != NUL; p += charlen)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000787 {
788 if ((*p & 0x80) == 0) /* be quick for ASCII */
789 {
790 charlen = 1;
791 len += IS_NONPRINTABLE(*p) ? 2 : 1; /* nonprintable: ^X */
792 }
793 else
794 {
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000795 charlen = utf_ptr2len(p);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000796 uc = utf_ptr2char(p);
797
798 if (charlen != utf_char2len(uc))
799 charlen = 1; /* reject overlong sequences */
800
801 if (charlen == 1 || uc < 0xa0) /* illegal byte or */
802 len += 4; /* control char: <xx> */
803 else if (!utf_printable(uc))
804 /* Note: we assume here that utf_printable() doesn't
805 * care about characters outside the BMP. */
806 len += 6; /* nonprintable: <xxxx> */
807 else
808 len += charlen;
809 }
810 }
811
812 attr_list = pango_attr_list_new();
813 buf = alloc(len + 1);
814
815 /* Now go for the real work */
816 if (buf != NULL)
817 {
818 attrentry_T *aep;
819 PangoAttribute *attr;
820 guicolor_T pixel;
Bram Moolenaar36edf062016-07-21 22:10:12 +0200821#if GTK_CHECK_VERSION(3,0,0)
822 GdkRGBA color = { 0.0, 0.0, 0.0, 1.0 };
Bram Moolenaar870b7492016-07-22 22:26:52 +0200823# if PANGO_VERSION_CHECK(1,38,0)
Bram Moolenaar36edf062016-07-21 22:10:12 +0200824 PangoAttribute *attr_alpha;
Bram Moolenaar870b7492016-07-22 22:26:52 +0200825# endif
Bram Moolenaar36edf062016-07-21 22:10:12 +0200826#else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000827 GdkColor color = { 0, 0, 0, 0 };
Bram Moolenaar36edf062016-07-21 22:10:12 +0200828#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000829
830 /* Look up the RGB values of the SpecialKey foreground color. */
Bram Moolenaar8820b482017-03-16 17:23:31 +0100831 aep = syn_gui_attr2entry(HL_ATTR(HLF_8));
Bram Moolenaar071d4272004-06-13 20:20:40 +0000832 pixel = (aep != NULL) ? aep->ae_u.gui.fg_color : INVALCOLOR;
833 if (pixel != INVALCOLOR)
Bram Moolenaar98921892016-02-23 17:14:37 +0100834# if GTK_CHECK_VERSION(3,0,0)
835 {
Bram Moolenaar36edf062016-07-21 22:10:12 +0200836 color.red = ((pixel & 0xff0000) >> 16) / 255.0;
837 color.green = ((pixel & 0xff00) >> 8) / 255.0;
838 color.blue = (pixel & 0xff) / 255.0;
839 color.alpha = 1.0;
Bram Moolenaar98921892016-02-23 17:14:37 +0100840 }
841# else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000842 gdk_colormap_query_color(gtk_widget_get_colormap(gui.drawarea),
843 (unsigned long)pixel, &color);
Bram Moolenaar98921892016-02-23 17:14:37 +0100844# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000845
846 pdest = buf;
Bram Moolenaar89d40322006-08-29 15:30:07 +0000847 p = text;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000848 while (*p != NUL)
849 {
850 /* Be quick for ASCII */
851 if ((*p & 0x80) == 0 && !IS_NONPRINTABLE(*p))
852 {
853 *pdest++ = *p++;
854 }
855 else
856 {
Bram Moolenaar0fa313a2005-08-10 21:07:57 +0000857 charlen = utf_ptr2len(p);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000858 uc = utf_ptr2char(p);
859
860 if (charlen != utf_char2len(uc))
861 charlen = 1; /* reject overlong sequences */
862
863 if (charlen == 1 || uc < 0xa0 || !utf_printable(uc))
864 {
865 int outlen;
866
867 /* Careful: we can't just use transchar_byte() here,
868 * since 'encoding' is not necessarily set to "utf-8". */
869 if (*p & 0x80 && charlen == 1)
870 {
871 transchar_hex(pdest, *p); /* <xx> */
872 outlen = 4;
873 }
874 else if (uc >= 0x80)
875 {
876 /* Note: we assume here that utf_printable() doesn't
877 * care about characters outside the BMP. */
878 transchar_hex(pdest, uc); /* <xx> or <xxxx> */
879 outlen = (uc < 0x100) ? 4 : 6;
880 }
881 else
882 {
883 transchar_nonprint(pdest, *p); /* ^X */
884 outlen = 2;
885 }
886 if (pixel != INVALCOLOR)
887 {
Bram Moolenaar36edf062016-07-21 22:10:12 +0200888#if GTK_CHECK_VERSION(3,0,0)
889# define DOUBLE2UINT16(val) ((guint16)((val) * 65535 + 0.5))
890 attr = pango_attr_foreground_new(
891 DOUBLE2UINT16(color.red),
892 DOUBLE2UINT16(color.green),
893 DOUBLE2UINT16(color.blue));
Bram Moolenaar870b7492016-07-22 22:26:52 +0200894# if PANGO_VERSION_CHECK(1,38,0)
Bram Moolenaar36edf062016-07-21 22:10:12 +0200895 attr_alpha = pango_attr_foreground_alpha_new(
896 DOUBLE2UINT16(color.alpha));
Bram Moolenaar870b7492016-07-22 22:26:52 +0200897# endif
Bram Moolenaar36edf062016-07-21 22:10:12 +0200898# undef DOUBLE2UINT16
899#else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000900 attr = pango_attr_foreground_new(
901 color.red, color.green, color.blue);
Bram Moolenaar36edf062016-07-21 22:10:12 +0200902#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000903 attr->start_index = pdest - buf;
904 attr->end_index = pdest - buf + outlen;
905 pango_attr_list_insert(attr_list, attr);
Bram Moolenaar36edf062016-07-21 22:10:12 +0200906#if GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar870b7492016-07-22 22:26:52 +0200907# if PANGO_VERSION_CHECK(1,38,0)
Bram Moolenaar36edf062016-07-21 22:10:12 +0200908 attr_alpha->start_index = pdest - buf;
909 attr_alpha->end_index = pdest - buf + outlen;
910 pango_attr_list_insert(attr_list, attr_alpha);
Bram Moolenaar870b7492016-07-22 22:26:52 +0200911# endif
Bram Moolenaar36edf062016-07-21 22:10:12 +0200912#endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000913 }
914 pdest += outlen;
915 p += charlen;
916 }
917 else
918 {
919 do
920 *pdest++ = *p++;
921 while (--charlen != 0);
922 }
923 }
924 }
925 *pdest = NUL;
926 }
927
928 vim_free(convbuf);
929
930 gtk_label_set_text(label, (const char *)buf);
931 vim_free(buf);
932
933 gtk_label_set_attributes(label, attr_list);
934 pango_attr_list_unref(attr_list);
935}
Bram Moolenaar182c5be2010-06-25 05:37:59 +0200936# undef IS_NONPRINTABLE
Bram Moolenaar071d4272004-06-13 20:20:40 +0000937
938/*
939 * Draw a balloon.
940 */
941 static void
942drawBalloon(BalloonEval *beval)
943{
944 if (beval->msg != NULL)
945 {
946 GtkRequisition requisition;
947 int screen_w;
948 int screen_h;
949 int x;
950 int y;
951 int x_offset = EVAL_OFFSET_X;
952 int y_offset = EVAL_OFFSET_Y;
Bram Moolenaar071d4272004-06-13 20:20:40 +0000953 PangoLayout *layout;
Bram Moolenaara859f042016-11-17 19:11:55 +0100954
Bram Moolenaar7be9b502017-09-09 18:45:26 +0200955# if !GTK_CHECK_VERSION(3,22,2)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000956 GdkScreen *screen;
957
958 screen = gtk_widget_get_screen(beval->target);
959 gtk_window_set_screen(GTK_WINDOW(beval->balloonShell), screen);
Bram Moolenaar071d4272004-06-13 20:20:40 +0000960# endif
Bram Moolenaar7be9b502017-09-09 18:45:26 +0200961 gui_gtk_get_screen_size_of_win(beval->balloonShell,
962 &screen_w, &screen_h);
Bram Moolenaar98921892016-02-23 17:14:37 +0100963# if !GTK_CHECK_VERSION(3,0,0)
Bram Moolenaar071d4272004-06-13 20:20:40 +0000964 gtk_widget_ensure_style(beval->balloonShell);
965 gtk_widget_ensure_style(beval->balloonLabel);
Bram Moolenaar98921892016-02-23 17:14:37 +0100966# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000967
Bram Moolenaar071d4272004-06-13 20:20:40 +0000968 set_printable_label_text(GTK_LABEL(beval->balloonLabel), beval->msg);
969 /*
970 * Dirty trick: Enable wrapping mode on the label's layout behind its
971 * back. This way GtkLabel won't try to constrain the wrap width to a
972 * builtin maximum value of about 65 Latin characters.
973 */
974 layout = gtk_label_get_layout(GTK_LABEL(beval->balloonLabel));
Bram Moolenaar182c5be2010-06-25 05:37:59 +0200975# ifdef PANGO_WRAP_WORD_CHAR
Bram Moolenaar071d4272004-06-13 20:20:40 +0000976 pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
Bram Moolenaar182c5be2010-06-25 05:37:59 +0200977# else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000978 pango_layout_set_wrap(layout, PANGO_WRAP_WORD);
Bram Moolenaar182c5be2010-06-25 05:37:59 +0200979# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000980 pango_layout_set_width(layout,
981 /* try to come up with some reasonable width */
982 PANGO_SCALE * CLAMP(gui.num_cols * gui.char_width,
983 screen_w / 2,
984 MAX(20, screen_w - 20)));
985
986 /* Calculate the balloon's width and height. */
Bram Moolenaar98921892016-02-23 17:14:37 +0100987# if GTK_CHECK_VERSION(3,0,0)
988 gtk_widget_get_preferred_size(beval->balloonShell, &requisition, NULL);
989# else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000990 gtk_widget_size_request(beval->balloonShell, &requisition);
Bram Moolenaar98921892016-02-23 17:14:37 +0100991# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000992
993 /* Compute position of the balloon area */
Bram Moolenaar98921892016-02-23 17:14:37 +0100994# if GTK_CHECK_VERSION(3,0,0)
995 gdk_window_get_origin(gtk_widget_get_window(beval->target), &x, &y);
996# else
Bram Moolenaar071d4272004-06-13 20:20:40 +0000997 gdk_window_get_origin(beval->target->window, &x, &y);
Bram Moolenaar98921892016-02-23 17:14:37 +0100998# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +0000999 x += beval->x;
1000 y += beval->y;
1001
1002 /* Get out of the way of the mouse pointer */
1003 if (x + x_offset + requisition.width > screen_w)
1004 y_offset += 15;
1005 if (y + y_offset + requisition.height > screen_h)
1006 y_offset = -requisition.height - EVAL_OFFSET_Y;
1007
1008 /* Sanitize values */
1009 x = CLAMP(x + x_offset, 0, MAX(0, screen_w - requisition.width));
1010 y = CLAMP(y + y_offset, 0, MAX(0, screen_h - requisition.height));
1011
1012 /* Show the balloon */
Bram Moolenaar98921892016-02-23 17:14:37 +01001013# if GTK_CHECK_VERSION(3,0,0)
1014 gtk_window_move(GTK_WINDOW(beval->balloonShell), x, y);
1015# else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001016 gtk_widget_set_uposition(beval->balloonShell, x, y);
Bram Moolenaar98921892016-02-23 17:14:37 +01001017# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001018 gtk_widget_show(beval->balloonShell);
1019
1020 beval->showState = ShS_SHOWING;
1021 }
1022}
1023
1024/*
1025 * Undraw a balloon.
1026 */
1027 static void
1028undrawBalloon(BalloonEval *beval)
1029{
1030 if (beval->balloonShell != NULL)
1031 gtk_widget_hide(beval->balloonShell);
1032 beval->showState = ShS_NEUTRAL;
1033}
1034
1035 static void
1036cancelBalloon(BalloonEval *beval)
1037{
1038 if (beval->showState == ShS_SHOWING
1039 || beval->showState == ShS_UPDATE_PENDING)
1040 undrawBalloon(beval);
1041
1042 if (beval->timerID != 0)
1043 {
Bram Moolenaar98921892016-02-23 17:14:37 +01001044# if GTK_CHECK_VERSION(3,0,0)
1045 g_source_remove(beval->timerID);
1046# else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001047 gtk_timeout_remove(beval->timerID);
Bram Moolenaar98921892016-02-23 17:14:37 +01001048# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001049 beval->timerID = 0;
1050 }
1051 beval->showState = ShS_NEUTRAL;
1052}
1053
1054 static void
1055createBalloonEvalWindow(BalloonEval *beval)
1056{
1057 beval->balloonShell = gtk_window_new(GTK_WINDOW_POPUP);
1058
1059 gtk_widget_set_app_paintable(beval->balloonShell, TRUE);
Bram Moolenaar98921892016-02-23 17:14:37 +01001060# if GTK_CHECK_VERSION(3,0,0)
1061 gtk_window_set_resizable(GTK_WINDOW(beval->balloonShell), FALSE);
1062# else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001063 gtk_window_set_policy(GTK_WINDOW(beval->balloonShell), FALSE, FALSE, TRUE);
Bram Moolenaar98921892016-02-23 17:14:37 +01001064# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001065 gtk_widget_set_name(beval->balloonShell, "gtk-tooltips");
Bram Moolenaar98921892016-02-23 17:14:37 +01001066# if GTK_CHECK_VERSION(3,0,0)
1067 gtk_container_set_border_width(GTK_CONTAINER(beval->balloonShell), 4);
1068# else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001069 gtk_container_border_width(GTK_CONTAINER(beval->balloonShell), 4);
Bram Moolenaar98921892016-02-23 17:14:37 +01001070# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001071
Bram Moolenaar98921892016-02-23 17:14:37 +01001072# if GTK_CHECK_VERSION(3,0,0)
1073 g_signal_connect(G_OBJECT(beval->balloonShell), "draw",
1074 G_CALLBACK(balloon_draw_event_cb), NULL);
1075# else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001076 gtk_signal_connect((GtkObject*)(beval->balloonShell), "expose_event",
1077 GTK_SIGNAL_FUNC(balloon_expose_event_cb), NULL);
Bram Moolenaar98921892016-02-23 17:14:37 +01001078# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001079 beval->balloonLabel = gtk_label_new(NULL);
1080
1081 gtk_label_set_line_wrap(GTK_LABEL(beval->balloonLabel), FALSE);
1082 gtk_label_set_justify(GTK_LABEL(beval->balloonLabel), GTK_JUSTIFY_LEFT);
Bram Moolenaar98921892016-02-23 17:14:37 +01001083# if GTK_CHECK_VERSION(3,16,0)
1084 gtk_label_set_xalign(GTK_LABEL(beval->balloonLabel), 0.5);
1085 gtk_label_set_yalign(GTK_LABEL(beval->balloonLabel), 0.5);
1086# elif GTK_CHECK_VERSION(3,14,0)
1087 GValue align_val = G_VALUE_INIT;
1088 g_value_init(&align_val, G_TYPE_FLOAT);
1089 g_value_set_float(&align_val, 0.5);
1090 g_object_set_property(G_OBJECT(beval->balloonLabel), "xalign", &align_val);
1091 g_object_set_property(G_OBJECT(beval->balloonLabel), "yalign", &align_val);
1092 g_value_unset(&align_val);
1093# else
Bram Moolenaar071d4272004-06-13 20:20:40 +00001094 gtk_misc_set_alignment(GTK_MISC(beval->balloonLabel), 0.5f, 0.5f);
Bram Moolenaar98921892016-02-23 17:14:37 +01001095# endif
Bram Moolenaar071d4272004-06-13 20:20:40 +00001096 gtk_widget_set_name(beval->balloonLabel, "vim-balloon-label");
1097 gtk_widget_show(beval->balloonLabel);
1098
1099 gtk_container_add(GTK_CONTAINER(beval->balloonShell), beval->balloonLabel);
1100}
1101
1102#else /* !FEAT_GUI_GTK */
1103
1104/*
1105 * Draw a balloon.
1106 */
1107 static void
Bram Moolenaar66f948e2016-01-30 16:39:25 +01001108drawBalloon(BalloonEval *beval)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001109{
1110 Dimension w;
1111 Dimension h;
1112 Position tx;
1113 Position ty;
1114
1115 if (beval->msg != NULL)
1116 {
1117 /* Show the Balloon */
1118
1119 /* Calculate the label's width and height */
1120#ifdef FEAT_GUI_MOTIF
1121 XmString s;
1122
1123 /* For the callback function we parse NL characters to create a
1124 * multi-line label. This doesn't work for all languages, but
1125 * XmStringCreateLocalized() doesn't do multi-line labels... */
1126 if (beval->msgCB != NULL)
1127 s = XmStringCreateLtoR((char *)beval->msg, XmFONTLIST_DEFAULT_TAG);
1128 else
1129 s = XmStringCreateLocalized((char *)beval->msg);
1130 {
1131 XmFontList fl;
1132
1133 fl = gui_motif_fontset2fontlist(&gui.tooltip_fontset);
Bram Moolenaardb5ffaa2014-06-25 17:44:49 +02001134 if (fl == NULL)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001135 {
Bram Moolenaardb5ffaa2014-06-25 17:44:49 +02001136 XmStringFree(s);
1137 return;
Bram Moolenaar071d4272004-06-13 20:20:40 +00001138 }
Bram Moolenaardb5ffaa2014-06-25 17:44:49 +02001139 XmStringExtent(fl, s, &w, &h);
1140 XmFontListFree(fl);
Bram Moolenaar071d4272004-06-13 20:20:40 +00001141 }
1142 w += gui.border_offset << 1;
1143 h += gui.border_offset << 1;
1144 XtVaSetValues(beval->balloonLabel, XmNlabelString, s, NULL);
1145 XmStringFree(s);
1146#else /* Athena */
1147 /* Assume XtNinternational == True */
1148 XFontSet fset;
1149 XFontSetExtents *ext;
1150
1151 XtVaGetValues(beval->balloonLabel, XtNfontSet, &fset, NULL);
1152 ext = XExtentsOfFontSet(fset);
1153 h = ext->max_ink_extent.height;
1154 w = XmbTextEscapement(fset,
1155 (char *)beval->msg,
1156 (int)STRLEN(beval->msg));
1157 w += gui.border_offset << 1;
1158 h += gui.border_offset << 1;
1159 XtVaSetValues(beval->balloonLabel, XtNlabel, beval->msg, NULL);
1160#endif
1161
1162 /* Compute position of the balloon area */
1163 tx = beval->x_root + EVAL_OFFSET_X;
1164 ty = beval->y_root + EVAL_OFFSET_Y;
1165 if ((tx + w) > beval->screen_width)
1166 tx = beval->screen_width - w;
1167 if ((ty + h) > beval->screen_height)
1168 ty = beval->screen_height - h;
1169#ifdef FEAT_GUI_MOTIF
1170 XtVaSetValues(beval->balloonShell,
1171 XmNx, tx,
1172 XmNy, ty,
1173 NULL);
1174#else
1175 /* Athena */
1176 XtVaSetValues(beval->balloonShell,
1177 XtNx, tx,
1178 XtNy, ty,
1179 NULL);
1180#endif
Bram Moolenaar8281f442009-03-18 11:22:25 +00001181 /* Set tooltip colors */
1182 {
1183 Arg args[2];
1184
1185#ifdef FEAT_GUI_MOTIF
1186 args[0].name = XmNbackground;
1187 args[0].value = gui.tooltip_bg_pixel;
1188 args[1].name = XmNforeground;
1189 args[1].value = gui.tooltip_fg_pixel;
1190#else /* Athena */
1191 args[0].name = XtNbackground;
1192 args[0].value = gui.tooltip_bg_pixel;
1193 args[1].name = XtNforeground;
1194 args[1].value = gui.tooltip_fg_pixel;
1195#endif
1196 XtSetValues(beval->balloonLabel, &args[0], XtNumber(args));
1197 }
Bram Moolenaar071d4272004-06-13 20:20:40 +00001198
1199 XtPopup(beval->balloonShell, XtGrabNone);
1200
1201 beval->showState = ShS_SHOWING;
1202
1203 current_beval = beval;
1204 }
1205}
1206
1207/*
1208 * Undraw a balloon.
1209 */
1210 static void
Bram Moolenaar66f948e2016-01-30 16:39:25 +01001211undrawBalloon(BalloonEval *beval)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001212{
1213 if (beval->balloonShell != (Widget)0)
1214 XtPopdown(beval->balloonShell);
1215 beval->showState = ShS_NEUTRAL;
1216
1217 current_beval = NULL;
1218}
1219
1220 static void
Bram Moolenaar66f948e2016-01-30 16:39:25 +01001221cancelBalloon(BalloonEval *beval)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001222{
1223 if (beval->showState == ShS_SHOWING
1224 || beval->showState == ShS_UPDATE_PENDING)
1225 undrawBalloon(beval);
1226
1227 if (beval->timerID != (XtIntervalId)NULL)
1228 {
1229 XtRemoveTimeOut(beval->timerID);
1230 beval->timerID = (XtIntervalId)NULL;
1231 }
1232 beval->showState = ShS_NEUTRAL;
1233}
1234
1235
1236 static void
Bram Moolenaar66f948e2016-01-30 16:39:25 +01001237createBalloonEvalWindow(BalloonEval *beval)
Bram Moolenaar071d4272004-06-13 20:20:40 +00001238{
1239 Arg args[12];
1240 int n;
1241
1242 n = 0;
1243#ifdef FEAT_GUI_MOTIF
1244 XtSetArg(args[n], XmNallowShellResize, True); n++;
1245 beval->balloonShell = XtAppCreateShell("balloonEval", "BalloonEval",
1246 overrideShellWidgetClass, gui.dpy, args, n);
1247#else
1248 /* Athena */
1249 XtSetArg(args[n], XtNallowShellResize, True); n++;
1250 beval->balloonShell = XtAppCreateShell("balloonEval", "BalloonEval",
1251 overrideShellWidgetClass, gui.dpy, args, n);
1252#endif
1253
1254 n = 0;
1255#ifdef FEAT_GUI_MOTIF
1256 {
1257 XmFontList fl;
1258
1259 fl = gui_motif_fontset2fontlist(&gui.tooltip_fontset);
1260 XtSetArg(args[n], XmNforeground, gui.tooltip_fg_pixel); n++;
1261 XtSetArg(args[n], XmNbackground, gui.tooltip_bg_pixel); n++;
1262 XtSetArg(args[n], XmNfontList, fl); n++;
1263 XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
1264 beval->balloonLabel = XtCreateManagedWidget("balloonLabel",
1265 xmLabelWidgetClass, beval->balloonShell, args, n);
1266 }
1267#else /* FEAT_GUI_ATHENA */
1268 XtSetArg(args[n], XtNforeground, gui.tooltip_fg_pixel); n++;
1269 XtSetArg(args[n], XtNbackground, gui.tooltip_bg_pixel); n++;
1270 XtSetArg(args[n], XtNinternational, True); n++;
1271 XtSetArg(args[n], XtNfontSet, gui.tooltip_fontset); n++;
1272 beval->balloonLabel = XtCreateManagedWidget("balloonLabel",
1273 labelWidgetClass, beval->balloonShell, args, n);
1274#endif
1275}
1276
1277#endif /* !FEAT_GUI_GTK */
1278#endif /* !FEAT_GUI_W32 */
1279
Bram Moolenaarc3719bd2017-11-18 22:13:31 +01001280#endif /* FEAT_BEVAL_GUI */