XEmacs 21.2-b1
[chise/xemacs-chise.git.1] / src / balloon_help.c
diff --git a/src/balloon_help.c b/src/balloon_help.c
new file mode 100644 (file)
index 0000000..b73a711
--- /dev/null
@@ -0,0 +1,615 @@
+/* Balloon Help
+   Copyright (c) 1997 Douglas Keller
+
+This file is part of XEmacs.
+
+XEmacs is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2, or (at your option) any
+later version.
+
+XEmacs is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with XEmacs; see the file COPYING.  If not, write to
+the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.  */
+
+/* Synched up with: Not in FSF. */
+
+/*
+ * Balloon Help
+ *
+ * Version: 1.337 (Sun Apr 13 04:52:10 1997)
+ *
+ * Written by Douglas Keller <dkeller@vnet.ibm.com>
+ *
+ *
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/shape.h>
+
+#include "xintrinsic.h"
+
+#include "balloon_help.h"
+
+#ifndef WINDOWSNT
+#define max(x,y) (x>y?x:y)
+#endif
+
+#undef bool
+#define bool int
+
+#define MARGIN_WIDTH      4
+#define POINTER_OFFSET    8
+#define BORDER_WIDTH      2
+#define BORDER_WIDTH_HALF 1
+
+#define CONE_HEIGHT    20
+#define CONE_WIDTH     50
+
+#define SHAPE_CONE_TOP          (1<<0)
+#define SHAPE_CONE_LEFT         (1<<1)
+#define SHAPE_CONE_TOP_LEFT     (SHAPE_CONE_TOP | SHAPE_CONE_LEFT)
+#define SHAPE_CONE_TOP_RIGHT    (SHAPE_CONE_TOP)
+#define SHAPE_CONE_BOTTOM_LEFT  (SHAPE_CONE_LEFT)
+#define SHAPE_CONE_BOTTOM_RIGHT (0)
+#define SHAPE_CONE_FREE         (-1)
+
+
+static Display* b_dpy;
+
+static XFontStruct* b_fontStruct;
+static GC b_gc;
+
+static GC b_shineGC;
+static GC b_shadowGC;
+
+static Window b_win;
+static bool   b_winMapped;
+
+static Pixmap b_mask;
+static int    b_maskWidth, b_maskHeight;
+static GC     b_maskGC;
+
+static CONST char* b_text;
+static int b_width, b_height;
+
+static int b_lastX, b_lastY;
+
+static XtIntervalId b_timer;
+static unsigned long b_delay;
+
+static int b_screenWidth, b_screenHeight;
+
+static int b_lastShape;
+
+/*============================================================================
+
+============================================================================*/
+
+static GC
+create_gc (Display* dpy, Window win, unsigned long fg, unsigned long bg,
+          XFontStruct* fontStruct)
+{
+  XGCValues gcv;
+  unsigned long mask;
+
+  gcv.foreground = fg;
+  gcv.background = bg;
+  gcv.font       = fontStruct->fid;
+  gcv.join_style = JoinMiter;
+  gcv.line_width = BORDER_WIDTH;
+
+  mask = GCFont | GCBackground | GCForeground | GCJoinStyle | GCLineWidth;
+
+  return XCreateGC (dpy, win, mask, &gcv);
+}
+
+static void
+destroy_gc (Display* dpy, GC gc)
+{
+  if (gc)
+    {
+      XFreeGC (dpy, gc);
+    }
+}
+
+/*============================================================================
+
+============================================================================*/
+
+static Window
+create_window (Display* dpy, unsigned long bg)
+{
+  Window win;
+  XSetWindowAttributes attr;
+  unsigned long attr_mask;
+
+  attr_mask              = CWOverrideRedirect | CWBackPixel | CWSaveUnder;
+  attr.override_redirect = True;
+  attr.background_pixel  = bg;
+  attr.save_under        = True;
+
+  win =
+    XCreateWindow (dpy,
+                   DefaultRootWindow (dpy),
+                   0, 0, 1, 1,
+                   0,
+                   CopyFromParent, InputOutput, CopyFromParent,
+                   attr_mask, &attr);
+
+  XSelectInput (dpy, win,
+                SubstructureRedirectMask |
+                SubstructureNotifyMask |
+                ExposureMask |
+                EnterWindowMask |
+                LeaveWindowMask);
+  return win;
+}
+
+static void
+destroy_window (Display* dpy, Window win)
+{
+  if (win)
+    {
+      XDestroyWindow (dpy, win);
+    }
+}
+
+/*============================================================================
+
+============================================================================*/
+
+static void
+get_pointer_xy (Display* dpy, int* x_return, int* y_return)
+{
+  int dummy;
+  unsigned int mask;
+  Window dummy_win;
+
+  XQueryPointer (dpy, RootWindow(dpy, DefaultScreen(dpy)), &dummy_win, &dummy_win,
+                x_return, y_return, &dummy, &dummy, &mask);
+}
+
+/*============================================================================
+
+============================================================================*/
+
+static void
+create_pixmap_mask (int width, int height)
+{
+  b_maskWidth  = width;
+  b_maskHeight = height;
+  b_mask = XCreatePixmap (b_dpy, b_win, width, height, 1);
+}
+
+static void
+destroy_pixmap_mask(void)
+{
+  XFreePixmap (b_dpy, b_mask);
+}
+
+static void
+grow_pixmap_mask (int width, int height)
+{
+  if (width > b_maskWidth || height > b_maskHeight)
+    {
+      destroy_pixmap_mask ();
+      create_pixmap_mask (width, height);
+    }
+}
+
+/*============================================================================
+
+============================================================================*/
+
+static void
+text_extent (XFontStruct* fontStruct, CONST char* text, int len,
+            int* width, int* height)
+{
+  XCharStruct extent;
+  int dummy;
+
+  XTextExtents (fontStruct, text, len, &dummy, &dummy, &dummy, &extent);
+
+  *width  = extent.width;
+  *height = fontStruct->ascent + fontStruct->descent;
+}
+
+static void
+get_text_size (Display* dpy, XFontStruct* fontStruct, CONST char* text,
+              int* max_width, int* max_height)
+{
+  int width;
+  int height;
+  CONST char* start;
+  CONST char* end;
+
+  *max_width = *max_height = 0;
+
+  start = text;
+  while ((end = strchr(start, '\n')))
+    {
+      text_extent (fontStruct, start, end - start, &width, &height);
+      *max_width  = max (width, *max_width);
+      *max_height += height;
+
+      start = end + 1;
+    }
+  text_extent (fontStruct, start, strlen (start), &width, &height);
+  *max_width  = max (width, *max_width);
+  *max_height += height;
+
+  /* Min width */
+  *max_width  = max (*max_width, CONE_WIDTH / 2 * 3);
+
+}
+
+static void
+draw_text (Display* dpy, Window win, GC gc, XFontStruct* fontStruct,
+          int x, int y, CONST char* text)
+{
+  CONST char* start;
+  CONST char* end;
+  int font_height;
+
+  y += fontStruct->ascent;
+
+  font_height = fontStruct->ascent + fontStruct->descent;
+
+  start = text;
+  while ((end = strchr(start, '\n')))
+    {
+      XDrawString (dpy, win, gc, x, y, start, end - start);
+
+      start = end + 1;
+      y += font_height;
+    }
+  XDrawString (dpy, win, gc, x, y, start, strlen (start));
+}
+
+/*============================================================================
+
+============================================================================*/
+
+static int
+get_shape (int last_shape, int x, int y, int width, int height,
+          int screen_width, int screen_height)
+{
+  /* Can we use last_shape? */
+  if (((last_shape == SHAPE_CONE_TOP_LEFT) &&
+       (x + width < screen_width) && (y + height < screen_height)) ||
+      ((last_shape == SHAPE_CONE_TOP_RIGHT) &&
+       (x - width > 0) && (y + height < screen_height)) ||
+      ((last_shape == SHAPE_CONE_BOTTOM_LEFT) &&
+       (x + width < screen_width) && (y - height > 0)) ||
+      ((last_shape == SHAPE_CONE_BOTTOM_RIGHT) &&
+       (x - width > 0) && (y - height > 0)))
+    return last_shape;
+
+  /* Try to pick a shape that will not get changed,
+     e.g. if top left quadrant, top_left */
+  return (x < screen_width / 2) ?
+    (y < screen_height / 2 ? SHAPE_CONE_TOP_LEFT:  SHAPE_CONE_BOTTOM_LEFT) :
+    (y < screen_height / 2 ? SHAPE_CONE_TOP_RIGHT: SHAPE_CONE_BOTTOM_RIGHT);
+}
+
+static void
+make_mask (int shape, int x, int y, int width, int height)
+{
+  XPoint cone[ 3 ];
+
+  grow_pixmap_mask (width, height);
+
+  /* Clear mask */
+  XSetForeground (b_dpy, b_maskGC, 0);
+  XFillRectangle (b_dpy, b_mask, b_maskGC,
+                  0, 0, width, height);
+
+  /* Enable text area */
+  XSetForeground (b_dpy, b_maskGC, 1);
+  XFillRectangle (b_dpy, b_mask, b_maskGC, 0,
+                 shape & SHAPE_CONE_TOP ? CONE_HEIGHT : 0, width, height - CONE_HEIGHT);
+
+  /* Enable for cone area */
+  cone[0].x = (shape & SHAPE_CONE_LEFT) ? CONE_WIDTH / 2 : width - (CONE_WIDTH / 2);
+  cone[0].y = (shape & SHAPE_CONE_TOP)  ? CONE_HEIGHT    : height - CONE_HEIGHT;
+  cone[1].x = (shape & SHAPE_CONE_LEFT) ? 0              : width;
+  cone[1].y = (shape & SHAPE_CONE_TOP)  ? 0              : height;
+  cone[2].x = (shape & SHAPE_CONE_LEFT) ? CONE_WIDTH     : width - CONE_WIDTH;
+  cone[2].y = (shape & SHAPE_CONE_TOP)  ? CONE_HEIGHT    : height - CONE_HEIGHT;
+
+  XFillPolygon (b_dpy, b_mask, b_maskGC, cone, 3, Nonconvex, CoordModeOrigin);
+
+}
+
+static void
+show_help (XtPointer data, XtIntervalId* id)
+{
+  int x, y;
+  int shape;
+  XPoint border[ 3 ];
+
+  if (id == NULL || ((id && b_timer) && b_text))
+    {
+      b_timer = None;
+
+      /* size */
+      get_text_size (b_dpy, b_fontStruct, b_text, &b_width, &b_height);
+      b_width  += 2 * MARGIN_WIDTH + 2 * BORDER_WIDTH;
+      b_height += 2 * MARGIN_WIDTH + 2 * BORDER_WIDTH + CONE_HEIGHT;
+
+      /* origin */
+      get_pointer_xy (b_dpy, &x, &y);
+
+      /* guess at shape */
+      shape = get_shape(b_lastShape, x, y, b_width, b_height,
+                       b_screenWidth, b_screenHeight);
+
+      x += (shape & SHAPE_CONE_LEFT) ? POINTER_OFFSET : -POINTER_OFFSET;
+      y += (shape & SHAPE_CONE_TOP)  ? POINTER_OFFSET : -POINTER_OFFSET;
+
+      /* make sure it is still ok with offset */
+      shape = get_shape (shape, x, y, b_width, b_height, b_screenWidth, b_screenHeight);
+
+      b_lastX = x;
+      b_lastY = y;
+      b_lastShape = shape;
+
+
+      make_mask (shape, x, y, b_width, b_height);
+
+      XShapeCombineMask (b_dpy, b_win, ShapeBounding, 0, 0, b_mask, ShapeSet);
+
+      XMoveResizeWindow(b_dpy, b_win,
+                       (shape & SHAPE_CONE_LEFT) ? x : x - b_width,
+                       (shape & SHAPE_CONE_TOP)  ? y : y - b_height,
+                       b_width, b_height);
+
+      XClearWindow (b_dpy, b_win);
+
+      XMapRaised (b_dpy, b_win);
+      b_winMapped = True;
+
+      draw_text (b_dpy, b_win, b_gc, b_fontStruct,
+                BORDER_WIDTH + MARGIN_WIDTH,
+                BORDER_WIDTH + MARGIN_WIDTH + ((shape & SHAPE_CONE_TOP) ? CONE_HEIGHT : 0),
+               b_text);
+
+      /* 3d border */
+      /* shine- top left */
+      border[0].x = 0 + BORDER_WIDTH_HALF;
+      border[0].y = ((shape & SHAPE_CONE_TOP) ? b_height : b_height - CONE_HEIGHT) - BORDER_WIDTH_HALF;
+      border[1].x = 0 + BORDER_WIDTH_HALF;
+      border[1].y = ((shape & SHAPE_CONE_TOP) ? CONE_HEIGHT : 0) + BORDER_WIDTH_HALF;
+      border[2].x = b_width - BORDER_WIDTH_HALF;
+      border[2].y = border[1].y;
+      XDrawLines (b_dpy, b_win, b_shineGC, border, 3, CoordModeOrigin);
+
+      /* shadow- bottom right */
+      border[0].x = 0 + BORDER_WIDTH_HALF;
+      border[0].y = ((shape & SHAPE_CONE_TOP) ? b_height : b_height - CONE_HEIGHT) - BORDER_WIDTH_HALF;
+      border[1].x = b_width - BORDER_WIDTH_HALF;
+      border[1].y = border[0].y;
+      border[2].x = b_width - BORDER_WIDTH_HALF;
+      border[2].y = ((shape & SHAPE_CONE_TOP) ? CONE_HEIGHT : 0) + BORDER_WIDTH_HALF;
+      XDrawLines (b_dpy, b_win, b_shadowGC, border, 3, CoordModeOrigin);
+
+      /* cone */
+    if (SHAPE_CONE_TOP_LEFT == shape)
+      {
+       XClearArea (b_dpy, b_win,
+                   CONE_WIDTH / 2 + BORDER_WIDTH,
+                   CONE_HEIGHT,
+                   CONE_WIDTH / 2 - BORDER_WIDTH,
+                   BORDER_WIDTH, False);
+       XDrawLine (b_dpy, b_win, b_shadowGC,
+                  0,
+                  0,
+                  CONE_WIDTH / 2 + BORDER_WIDTH_HALF,
+                  CONE_HEIGHT);
+       XDrawLine (b_dpy, b_win, b_shineGC,
+                  0,
+                  0,
+                  CONE_WIDTH - BORDER_WIDTH_HALF,
+                  CONE_HEIGHT);
+      }
+    else if (SHAPE_CONE_TOP_RIGHT == shape)
+      {
+       XClearArea (b_dpy, b_win,
+                   b_width - CONE_WIDTH + BORDER_WIDTH,
+                   CONE_HEIGHT,
+                   CONE_WIDTH / 2 - BORDER_WIDTH,
+                   BORDER_WIDTH, False);
+       XDrawLine (b_dpy, b_win, b_shadowGC,
+                  b_width,
+                  0,
+                  b_width - CONE_WIDTH / 2 - BORDER_WIDTH_HALF,
+                  CONE_HEIGHT);
+       XDrawLine (b_dpy, b_win, b_shineGC,
+                  b_width,
+                  0,
+                  b_width - CONE_WIDTH + BORDER_WIDTH_HALF,
+                  CONE_HEIGHT);
+      }
+    else if (SHAPE_CONE_BOTTOM_LEFT == shape)
+      {
+       XClearArea (b_dpy, b_win,
+                   CONE_WIDTH / 2 + BORDER_WIDTH,
+                   b_height - CONE_HEIGHT - BORDER_WIDTH,
+                   CONE_WIDTH / 2 - BORDER_WIDTH,
+                   BORDER_WIDTH, False);
+       XDrawLine (b_dpy, b_win, b_shadowGC,
+                  0,
+                  b_height - 1,
+                  CONE_WIDTH,
+                  b_height - 1 - CONE_HEIGHT);
+       XDrawLine (b_dpy, b_win, b_shineGC,
+                  0,
+                  b_height - 1,
+                  CONE_WIDTH / 2 + BORDER_WIDTH,
+                  b_height - 1 - CONE_HEIGHT);
+      }
+    else if (SHAPE_CONE_BOTTOM_RIGHT == shape)
+      {
+       XClearArea (b_dpy, b_win,
+                   b_width - 1 - CONE_WIDTH + BORDER_WIDTH,
+                   b_height - CONE_HEIGHT - BORDER_WIDTH,
+                   CONE_WIDTH / 2 - BORDER_WIDTH - 1,
+                   BORDER_WIDTH, False);
+       XDrawLine (b_dpy, b_win, b_shadowGC,
+                  b_width - 1,
+                  b_height - 1,
+                  b_width - 1 - CONE_WIDTH,
+                  b_height - 1 - CONE_HEIGHT);
+       XDrawLine (b_dpy, b_win, b_shineGC,
+                  b_width - 1,
+                  b_height - 1,
+                  b_width - 1 - CONE_WIDTH / 2 - BORDER_WIDTH,
+                  b_height - 1 - CONE_HEIGHT);
+      }
+    }
+
+}
+
+/*============================================================================
+
+============================================================================*/
+
+static void
+balloon_help_destroy (void)
+{
+  assert (b_dpy != NULL);
+  b_dpy = NULL;
+
+  destroy_window (b_dpy, b_win);
+  destroy_gc (b_dpy, b_gc);
+
+  destroy_gc (b_dpy, b_shineGC);
+  destroy_gc (b_dpy, b_shadowGC);
+
+  destroy_pixmap_mask ();
+  destroy_gc (b_dpy, b_maskGC);
+
+  if (b_timer) XtRemoveTimeOut (b_timer);
+}
+
+void
+balloon_help_create (Display* dpy,
+                    Pixel fg, Pixel bg, Pixel shine, Pixel shadow,
+                    XFontStruct* font)
+{
+  if (b_dpy) balloon_help_destroy ();
+
+  b_dpy = dpy;
+
+  b_fontStruct = font;
+
+  b_win = create_window (dpy, bg);
+  b_gc  = create_gc (dpy, b_win, fg, bg, b_fontStruct);
+
+  b_shineGC  = create_gc (dpy, b_win, shine, bg, b_fontStruct);
+  b_shadowGC = create_gc (dpy, b_win, shadow, bg, b_fontStruct);
+
+  create_pixmap_mask (1, 1);
+  b_maskGC = create_gc (dpy, b_mask, bg, fg, b_fontStruct);
+
+  b_winMapped = False;
+  b_timer     = None;
+  b_delay     = 500;
+
+  b_screenWidth  = DisplayWidth (b_dpy, DefaultScreen(b_dpy));
+  b_screenHeight = DisplayHeight (b_dpy, DefaultScreen(b_dpy));
+
+  b_lastShape = SHAPE_CONE_FREE;
+}
+
+void
+balloon_help_set_delay (unsigned long milliseconds)
+{
+  b_delay = milliseconds;
+}
+
+void
+balloon_help_show (CONST char* text)
+{
+  assert (b_dpy != NULL);
+
+  /* We don't copy the text */
+  b_text = text;
+  b_lastShape = SHAPE_CONE_FREE;
+
+  if (b_winMapped)
+    {
+      /* If help is already being shown, don't delay just update */
+      show_help (NULL, NULL);
+    }
+  else
+    {
+      b_timer =
+       XtAppAddTimeOut (XtDisplayToApplicationContext(b_dpy),
+                        b_delay, show_help, NULL);
+    }
+}
+
+void
+balloon_help_hide (void)
+{
+  assert (b_dpy != NULL);
+
+  b_text = NULL;
+  XUnmapWindow (b_dpy, b_win);
+  b_winMapped = False;
+  if (b_timer)
+    {
+      XtRemoveTimeOut (b_timer);
+      b_timer = None;
+    }
+}
+
+void
+balloon_help_move_to_pointer (void)
+{
+  assert (b_dpy != NULL);
+
+  if (b_winMapped)
+    {
+      int x, y;
+      int shape = b_lastShape;
+
+      get_pointer_xy (b_dpy, &x, &y);
+
+      x += (shape & SHAPE_CONE_LEFT) ? POINTER_OFFSET : -POINTER_OFFSET;
+      y += (shape & SHAPE_CONE_TOP)  ? POINTER_OFFSET : -POINTER_OFFSET;
+
+      shape = get_shape (shape, x, y, b_width, b_height, b_screenWidth, b_screenHeight);
+
+      if (shape == b_lastShape)
+       {
+         b_lastX = x;
+         b_lastY = y;
+
+         XMoveWindow (b_dpy, b_win,
+                      shape & SHAPE_CONE_LEFT ? x : x - b_width,
+                      shape & SHAPE_CONE_TOP  ? y : y - b_height);
+       }
+      else
+       {
+         /* text would be off screen, rebuild with new shape */
+         b_lastShape = SHAPE_CONE_FREE;
+         show_help (NULL, NULL);
+       }
+    }
+}