XEmacs 21.2-b1
[chise/xemacs-chise.git.1] / src / balloon_help.c
1 /* Balloon Help
2    Copyright (c) 1997 Douglas Keller
3
4 This file is part of XEmacs.
5
6 XEmacs is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
9 later version.
10
11 XEmacs is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with XEmacs; see the file COPYING.  If not, write to
18 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 Boston, MA 02111-1307, USA.  */
20
21 /* Synched up with: Not in FSF. */
22
23 /*
24  * Balloon Help
25  *
26  * Version: 1.337 (Sun Apr 13 04:52:10 1997)
27  *
28  * Written by Douglas Keller <dkeller@vnet.ibm.com>
29  *
30  *
31  */
32
33 #include <config.h>
34 #include <string.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <assert.h>
38
39 #include <X11/Xlib.h>
40 #include <X11/Xutil.h>
41 #include <X11/extensions/shape.h>
42
43 #include "xintrinsic.h"
44
45 #include "balloon_help.h"
46
47 #ifndef WINDOWSNT
48 #define max(x,y) (x>y?x:y)
49 #endif
50
51 #undef bool
52 #define bool int
53
54 #define MARGIN_WIDTH      4
55 #define POINTER_OFFSET    8
56 #define BORDER_WIDTH      2
57 #define BORDER_WIDTH_HALF 1
58
59 #define CONE_HEIGHT    20
60 #define CONE_WIDTH     50
61
62 #define SHAPE_CONE_TOP          (1<<0)
63 #define SHAPE_CONE_LEFT         (1<<1)
64 #define SHAPE_CONE_TOP_LEFT     (SHAPE_CONE_TOP | SHAPE_CONE_LEFT)
65 #define SHAPE_CONE_TOP_RIGHT    (SHAPE_CONE_TOP)
66 #define SHAPE_CONE_BOTTOM_LEFT  (SHAPE_CONE_LEFT)
67 #define SHAPE_CONE_BOTTOM_RIGHT (0)
68 #define SHAPE_CONE_FREE         (-1)
69
70
71 static Display* b_dpy;
72
73 static XFontStruct* b_fontStruct;
74 static GC b_gc;
75
76 static GC b_shineGC;
77 static GC b_shadowGC;
78
79 static Window b_win;
80 static bool   b_winMapped;
81
82 static Pixmap b_mask;
83 static int    b_maskWidth, b_maskHeight;
84 static GC     b_maskGC;
85
86 static CONST char* b_text;
87 static int b_width, b_height;
88
89 static int b_lastX, b_lastY;
90
91 static XtIntervalId b_timer;
92 static unsigned long b_delay;
93
94 static int b_screenWidth, b_screenHeight;
95
96 static int b_lastShape;
97
98 /*============================================================================
99
100 ============================================================================*/
101
102 static GC
103 create_gc (Display* dpy, Window win, unsigned long fg, unsigned long bg,
104            XFontStruct* fontStruct)
105 {
106   XGCValues gcv;
107   unsigned long mask;
108
109   gcv.foreground = fg;
110   gcv.background = bg;
111   gcv.font       = fontStruct->fid;
112   gcv.join_style = JoinMiter;
113   gcv.line_width = BORDER_WIDTH;
114
115   mask = GCFont | GCBackground | GCForeground | GCJoinStyle | GCLineWidth;
116
117   return XCreateGC (dpy, win, mask, &gcv);
118 }
119
120 static void
121 destroy_gc (Display* dpy, GC gc)
122 {
123   if (gc)
124     {
125       XFreeGC (dpy, gc);
126     }
127 }
128
129 /*============================================================================
130
131 ============================================================================*/
132
133 static Window
134 create_window (Display* dpy, unsigned long bg)
135 {
136   Window win;
137   XSetWindowAttributes attr;
138   unsigned long attr_mask;
139
140   attr_mask              = CWOverrideRedirect | CWBackPixel | CWSaveUnder;
141   attr.override_redirect = True;
142   attr.background_pixel  = bg;
143   attr.save_under        = True;
144
145   win =
146     XCreateWindow (dpy,
147                    DefaultRootWindow (dpy),
148                    0, 0, 1, 1,
149                    0,
150                    CopyFromParent, InputOutput, CopyFromParent,
151                    attr_mask, &attr);
152
153   XSelectInput (dpy, win,
154                 SubstructureRedirectMask |
155                 SubstructureNotifyMask |
156                 ExposureMask |
157                 EnterWindowMask |
158                 LeaveWindowMask);
159   return win;
160 }
161
162 static void
163 destroy_window (Display* dpy, Window win)
164 {
165   if (win)
166     {
167       XDestroyWindow (dpy, win);
168     }
169 }
170
171 /*============================================================================
172
173 ============================================================================*/
174
175 static void
176 get_pointer_xy (Display* dpy, int* x_return, int* y_return)
177 {
178   int dummy;
179   unsigned int mask;
180   Window dummy_win;
181
182   XQueryPointer (dpy, RootWindow(dpy, DefaultScreen(dpy)), &dummy_win, &dummy_win,
183                  x_return, y_return, &dummy, &dummy, &mask);
184 }
185
186 /*============================================================================
187
188 ============================================================================*/
189
190 static void
191 create_pixmap_mask (int width, int height)
192 {
193   b_maskWidth  = width;
194   b_maskHeight = height;
195   b_mask = XCreatePixmap (b_dpy, b_win, width, height, 1);
196 }
197
198 static void
199 destroy_pixmap_mask(void)
200 {
201   XFreePixmap (b_dpy, b_mask);
202 }
203
204 static void
205 grow_pixmap_mask (int width, int height)
206 {
207   if (width > b_maskWidth || height > b_maskHeight)
208     {
209       destroy_pixmap_mask ();
210       create_pixmap_mask (width, height);
211     }
212 }
213
214 /*============================================================================
215
216 ============================================================================*/
217
218 static void
219 text_extent (XFontStruct* fontStruct, CONST char* text, int len,
220              int* width, int* height)
221 {
222   XCharStruct extent;
223   int dummy;
224
225   XTextExtents (fontStruct, text, len, &dummy, &dummy, &dummy, &extent);
226
227   *width  = extent.width;
228   *height = fontStruct->ascent + fontStruct->descent;
229 }
230
231 static void
232 get_text_size (Display* dpy, XFontStruct* fontStruct, CONST char* text,
233                int* max_width, int* max_height)
234 {
235   int width;
236   int height;
237   CONST char* start;
238   CONST char* end;
239
240   *max_width = *max_height = 0;
241
242   start = text;
243   while ((end = strchr(start, '\n')))
244     {
245       text_extent (fontStruct, start, end - start, &width, &height);
246       *max_width  = max (width, *max_width);
247       *max_height += height;
248
249       start = end + 1;
250     }
251   text_extent (fontStruct, start, strlen (start), &width, &height);
252   *max_width  = max (width, *max_width);
253   *max_height += height;
254
255   /* Min width */
256   *max_width  = max (*max_width, CONE_WIDTH / 2 * 3);
257
258 }
259
260 static void
261 draw_text (Display* dpy, Window win, GC gc, XFontStruct* fontStruct,
262            int x, int y, CONST char* text)
263 {
264   CONST char* start;
265   CONST char* end;
266   int font_height;
267
268   y += fontStruct->ascent;
269
270   font_height = fontStruct->ascent + fontStruct->descent;
271
272   start = text;
273   while ((end = strchr(start, '\n')))
274     {
275       XDrawString (dpy, win, gc, x, y, start, end - start);
276
277       start = end + 1;
278       y += font_height;
279     }
280   XDrawString (dpy, win, gc, x, y, start, strlen (start));
281 }
282
283 /*============================================================================
284
285 ============================================================================*/
286
287 static int
288 get_shape (int last_shape, int x, int y, int width, int height,
289            int screen_width, int screen_height)
290 {
291   /* Can we use last_shape? */
292   if (((last_shape == SHAPE_CONE_TOP_LEFT) &&
293        (x + width < screen_width) && (y + height < screen_height)) ||
294       ((last_shape == SHAPE_CONE_TOP_RIGHT) &&
295        (x - width > 0) && (y + height < screen_height)) ||
296       ((last_shape == SHAPE_CONE_BOTTOM_LEFT) &&
297        (x + width < screen_width) && (y - height > 0)) ||
298       ((last_shape == SHAPE_CONE_BOTTOM_RIGHT) &&
299        (x - width > 0) && (y - height > 0)))
300     return last_shape;
301
302   /* Try to pick a shape that will not get changed,
303      e.g. if top left quadrant, top_left */
304   return (x < screen_width / 2) ?
305     (y < screen_height / 2 ? SHAPE_CONE_TOP_LEFT:  SHAPE_CONE_BOTTOM_LEFT) :
306     (y < screen_height / 2 ? SHAPE_CONE_TOP_RIGHT: SHAPE_CONE_BOTTOM_RIGHT);
307 }
308
309 static void
310 make_mask (int shape, int x, int y, int width, int height)
311 {
312   XPoint cone[ 3 ];
313
314   grow_pixmap_mask (width, height);
315
316   /* Clear mask */
317   XSetForeground (b_dpy, b_maskGC, 0);
318   XFillRectangle (b_dpy, b_mask, b_maskGC,
319                   0, 0, width, height);
320
321   /* Enable text area */
322   XSetForeground (b_dpy, b_maskGC, 1);
323   XFillRectangle (b_dpy, b_mask, b_maskGC, 0,
324                   shape & SHAPE_CONE_TOP ? CONE_HEIGHT : 0, width, height - CONE_HEIGHT);
325
326   /* Enable for cone area */
327   cone[0].x = (shape & SHAPE_CONE_LEFT) ? CONE_WIDTH / 2 : width - (CONE_WIDTH / 2);
328   cone[0].y = (shape & SHAPE_CONE_TOP)  ? CONE_HEIGHT    : height - CONE_HEIGHT;
329   cone[1].x = (shape & SHAPE_CONE_LEFT) ? 0              : width;
330   cone[1].y = (shape & SHAPE_CONE_TOP)  ? 0              : height;
331   cone[2].x = (shape & SHAPE_CONE_LEFT) ? CONE_WIDTH     : width - CONE_WIDTH;
332   cone[2].y = (shape & SHAPE_CONE_TOP)  ? CONE_HEIGHT    : height - CONE_HEIGHT;
333
334   XFillPolygon (b_dpy, b_mask, b_maskGC, cone, 3, Nonconvex, CoordModeOrigin);
335
336 }
337
338 static void
339 show_help (XtPointer data, XtIntervalId* id)
340 {
341   int x, y;
342   int shape;
343   XPoint border[ 3 ];
344
345   if (id == NULL || ((id && b_timer) && b_text))
346     {
347       b_timer = None;
348
349       /* size */
350       get_text_size (b_dpy, b_fontStruct, b_text, &b_width, &b_height);
351       b_width  += 2 * MARGIN_WIDTH + 2 * BORDER_WIDTH;
352       b_height += 2 * MARGIN_WIDTH + 2 * BORDER_WIDTH + CONE_HEIGHT;
353
354       /* origin */
355       get_pointer_xy (b_dpy, &x, &y);
356
357       /* guess at shape */
358       shape = get_shape(b_lastShape, x, y, b_width, b_height,
359                         b_screenWidth, b_screenHeight);
360
361       x += (shape & SHAPE_CONE_LEFT) ? POINTER_OFFSET : -POINTER_OFFSET;
362       y += (shape & SHAPE_CONE_TOP)  ? POINTER_OFFSET : -POINTER_OFFSET;
363
364       /* make sure it is still ok with offset */
365       shape = get_shape (shape, x, y, b_width, b_height, b_screenWidth, b_screenHeight);
366
367       b_lastX = x;
368       b_lastY = y;
369       b_lastShape = shape;
370
371
372       make_mask (shape, x, y, b_width, b_height);
373
374       XShapeCombineMask (b_dpy, b_win, ShapeBounding, 0, 0, b_mask, ShapeSet);
375
376       XMoveResizeWindow(b_dpy, b_win,
377                         (shape & SHAPE_CONE_LEFT) ? x : x - b_width,
378                         (shape & SHAPE_CONE_TOP)  ? y : y - b_height,
379                         b_width, b_height);
380
381       XClearWindow (b_dpy, b_win);
382
383       XMapRaised (b_dpy, b_win);
384       b_winMapped = True;
385
386       draw_text (b_dpy, b_win, b_gc, b_fontStruct,
387                  BORDER_WIDTH + MARGIN_WIDTH,
388                  BORDER_WIDTH + MARGIN_WIDTH + ((shape & SHAPE_CONE_TOP) ? CONE_HEIGHT : 0),
389                b_text);
390
391       /* 3d border */
392       /* shine- top left */
393       border[0].x = 0 + BORDER_WIDTH_HALF;
394       border[0].y = ((shape & SHAPE_CONE_TOP) ? b_height : b_height - CONE_HEIGHT) - BORDER_WIDTH_HALF;
395       border[1].x = 0 + BORDER_WIDTH_HALF;
396       border[1].y = ((shape & SHAPE_CONE_TOP) ? CONE_HEIGHT : 0) + BORDER_WIDTH_HALF;
397       border[2].x = b_width - BORDER_WIDTH_HALF;
398       border[2].y = border[1].y;
399       XDrawLines (b_dpy, b_win, b_shineGC, border, 3, CoordModeOrigin);
400
401       /* shadow- bottom right */
402       border[0].x = 0 + BORDER_WIDTH_HALF;
403       border[0].y = ((shape & SHAPE_CONE_TOP) ? b_height : b_height - CONE_HEIGHT) - BORDER_WIDTH_HALF;
404       border[1].x = b_width - BORDER_WIDTH_HALF;
405       border[1].y = border[0].y;
406       border[2].x = b_width - BORDER_WIDTH_HALF;
407       border[2].y = ((shape & SHAPE_CONE_TOP) ? CONE_HEIGHT : 0) + BORDER_WIDTH_HALF;
408       XDrawLines (b_dpy, b_win, b_shadowGC, border, 3, CoordModeOrigin);
409
410       /* cone */
411     if (SHAPE_CONE_TOP_LEFT == shape)
412       {
413         XClearArea (b_dpy, b_win,
414                     CONE_WIDTH / 2 + BORDER_WIDTH,
415                     CONE_HEIGHT,
416                     CONE_WIDTH / 2 - BORDER_WIDTH,
417                     BORDER_WIDTH, False);
418         XDrawLine (b_dpy, b_win, b_shadowGC,
419                    0,
420                    0,
421                    CONE_WIDTH / 2 + BORDER_WIDTH_HALF,
422                    CONE_HEIGHT);
423         XDrawLine (b_dpy, b_win, b_shineGC,
424                    0,
425                    0,
426                    CONE_WIDTH - BORDER_WIDTH_HALF,
427                    CONE_HEIGHT);
428       }
429     else if (SHAPE_CONE_TOP_RIGHT == shape)
430       {
431         XClearArea (b_dpy, b_win,
432                     b_width - CONE_WIDTH + BORDER_WIDTH,
433                     CONE_HEIGHT,
434                     CONE_WIDTH / 2 - BORDER_WIDTH,
435                     BORDER_WIDTH, False);
436         XDrawLine (b_dpy, b_win, b_shadowGC,
437                    b_width,
438                    0,
439                    b_width - CONE_WIDTH / 2 - BORDER_WIDTH_HALF,
440                    CONE_HEIGHT);
441         XDrawLine (b_dpy, b_win, b_shineGC,
442                    b_width,
443                    0,
444                    b_width - CONE_WIDTH + BORDER_WIDTH_HALF,
445                    CONE_HEIGHT);
446       }
447     else if (SHAPE_CONE_BOTTOM_LEFT == shape)
448       {
449         XClearArea (b_dpy, b_win,
450                     CONE_WIDTH / 2 + BORDER_WIDTH,
451                     b_height - CONE_HEIGHT - BORDER_WIDTH,
452                     CONE_WIDTH / 2 - BORDER_WIDTH,
453                     BORDER_WIDTH, False);
454         XDrawLine (b_dpy, b_win, b_shadowGC,
455                    0,
456                    b_height - 1,
457                    CONE_WIDTH,
458                    b_height - 1 - CONE_HEIGHT);
459         XDrawLine (b_dpy, b_win, b_shineGC,
460                    0,
461                    b_height - 1,
462                    CONE_WIDTH / 2 + BORDER_WIDTH,
463                    b_height - 1 - CONE_HEIGHT);
464       }
465     else if (SHAPE_CONE_BOTTOM_RIGHT == shape)
466       {
467         XClearArea (b_dpy, b_win,
468                     b_width - 1 - CONE_WIDTH + BORDER_WIDTH,
469                     b_height - CONE_HEIGHT - BORDER_WIDTH,
470                     CONE_WIDTH / 2 - BORDER_WIDTH - 1,
471                     BORDER_WIDTH, False);
472         XDrawLine (b_dpy, b_win, b_shadowGC,
473                    b_width - 1,
474                    b_height - 1,
475                    b_width - 1 - CONE_WIDTH,
476                    b_height - 1 - CONE_HEIGHT);
477         XDrawLine (b_dpy, b_win, b_shineGC,
478                    b_width - 1,
479                    b_height - 1,
480                    b_width - 1 - CONE_WIDTH / 2 - BORDER_WIDTH,
481                    b_height - 1 - CONE_HEIGHT);
482       }
483     }
484
485 }
486
487 /*============================================================================
488
489 ============================================================================*/
490
491 static void
492 balloon_help_destroy (void)
493 {
494   assert (b_dpy != NULL);
495   b_dpy = NULL;
496
497   destroy_window (b_dpy, b_win);
498   destroy_gc (b_dpy, b_gc);
499
500   destroy_gc (b_dpy, b_shineGC);
501   destroy_gc (b_dpy, b_shadowGC);
502
503   destroy_pixmap_mask ();
504   destroy_gc (b_dpy, b_maskGC);
505
506   if (b_timer) XtRemoveTimeOut (b_timer);
507 }
508
509 void
510 balloon_help_create (Display* dpy,
511                      Pixel fg, Pixel bg, Pixel shine, Pixel shadow,
512                      XFontStruct* font)
513 {
514   if (b_dpy) balloon_help_destroy ();
515
516   b_dpy = dpy;
517
518   b_fontStruct = font;
519
520   b_win = create_window (dpy, bg);
521   b_gc  = create_gc (dpy, b_win, fg, bg, b_fontStruct);
522
523   b_shineGC  = create_gc (dpy, b_win, shine, bg, b_fontStruct);
524   b_shadowGC = create_gc (dpy, b_win, shadow, bg, b_fontStruct);
525
526   create_pixmap_mask (1, 1);
527   b_maskGC = create_gc (dpy, b_mask, bg, fg, b_fontStruct);
528
529   b_winMapped = False;
530   b_timer     = None;
531   b_delay     = 500;
532
533   b_screenWidth  = DisplayWidth (b_dpy, DefaultScreen(b_dpy));
534   b_screenHeight = DisplayHeight (b_dpy, DefaultScreen(b_dpy));
535
536   b_lastShape = SHAPE_CONE_FREE;
537 }
538
539 void
540 balloon_help_set_delay (unsigned long milliseconds)
541 {
542   b_delay = milliseconds;
543 }
544
545 void
546 balloon_help_show (CONST char* text)
547 {
548   assert (b_dpy != NULL);
549
550   /* We don't copy the text */
551   b_text = text;
552   b_lastShape = SHAPE_CONE_FREE;
553
554   if (b_winMapped)
555     {
556       /* If help is already being shown, don't delay just update */
557       show_help (NULL, NULL);
558     }
559   else
560     {
561       b_timer =
562         XtAppAddTimeOut (XtDisplayToApplicationContext(b_dpy),
563                          b_delay, show_help, NULL);
564     }
565 }
566
567 void
568 balloon_help_hide (void)
569 {
570   assert (b_dpy != NULL);
571
572   b_text = NULL;
573   XUnmapWindow (b_dpy, b_win);
574   b_winMapped = False;
575   if (b_timer)
576     {
577       XtRemoveTimeOut (b_timer);
578       b_timer = None;
579     }
580 }
581
582 void
583 balloon_help_move_to_pointer (void)
584 {
585   assert (b_dpy != NULL);
586
587   if (b_winMapped)
588     {
589       int x, y;
590       int shape = b_lastShape;
591
592       get_pointer_xy (b_dpy, &x, &y);
593
594       x += (shape & SHAPE_CONE_LEFT) ? POINTER_OFFSET : -POINTER_OFFSET;
595       y += (shape & SHAPE_CONE_TOP)  ? POINTER_OFFSET : -POINTER_OFFSET;
596
597       shape = get_shape (shape, x, y, b_width, b_height, b_screenWidth, b_screenHeight);
598
599       if (shape == b_lastShape)
600         {
601           b_lastX = x;
602           b_lastY = y;
603
604           XMoveWindow (b_dpy, b_win,
605                        shape & SHAPE_CONE_LEFT ? x : x - b_width,
606                        shape & SHAPE_CONE_TOP  ? y : y - b_height);
607         }
608       else
609         {
610           /* text would be off screen, rebuild with new shape */
611           b_lastShape = SHAPE_CONE_FREE;
612           show_help (NULL, NULL);
613         }
614     }
615 }