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