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