#include "snd.h"

#include <X11/IntrinsicP.h>
#include <stdint.h>
#include <inttypes.h>

#if __GNUC__
#ifdef LESSTIF_VERSION
  /* moved the warning here so it only is displayed once */
  #warning You appear to be using Lesstif: this is not recommended!  Expect bugs...
#endif
#if (XmVERSION == 1)
  #warning Motif 1 is no longer supported -- this has little chance of working...
#endif
#endif


/* In case of X error that simply exits without any stack trace, first
 *
 *     XSynchronize(dpy, true);
 *     XSync(dpy, true);
 *
 * around line 30817 (where ss->mainapp gets set)
 * then if still trouble, make an X error handler:
 *
 *    static XErrorHandler old_handler = (XErrorHandler) 0;
 *    static int ApplicationErrorHandler(Display *display, XErrorEvent *theEvent)
 *    {
 *      (void) fprintf(stderr, "Xlib error: error code %d request code %d\n", theEvent->error_code, theEvent->request_code);
 *      abort();
 *      return 0;
 *    }
 *
 * and at the same point as before
 *
 *      old_handler = XSetErrorHandler(ApplicationErrorHandler);
 *
 * code 8 seems to mean a newly created window is unhappy (tooltip for example)
 */


static XmRenderTable get_xm_font(XFontStruct *ignore, const char *font, const char *tag)
{
  XmRendition tmp;
  XmRenderTable tabl;
  int n;
  Arg args[12];

  n = 0;
  XtSetArg(args[n], XmNfontName, font); n++;
  XtSetArg(args[n], XmNfontType, XmFONT_IS_FONT); n++; 
  XtSetArg(args[n], XmNloadModel, XmLOAD_IMMEDIATE); n++;
  tmp = XmRenditionCreate(main_shell(ss), (char *)tag, args, n);
  tabl = XmRenderTableAddRenditions(NULL, &tmp, 1, XmMERGE_NEW);

  /* XmRenditionFree(tmp); */ /* valgrind thinks this is a bad idea */
  return(tabl);
}


/* to see all fonts: (format #f "~{~A~%~}" (XListFonts (XtDisplay (cadr (main-widgets))) "*" 10000))
 */

bool set_tiny_font(const char *font)
{
  XFontStruct *fs = NULL;
  fs = XLoadQueryFont(main_display(ss), font);
  if (fs)
    {
      /* it's not clear to me whether this is safe -- what if two fontstructs are pointing to the same font? */
      if (TINY_FONT(ss)) XFreeFont(main_display(ss), TINY_FONT(ss));
      if (tiny_font(ss)) free(tiny_font(ss));
      in_set_tiny_font(mus_strdup(font));
      TINY_FONT(ss) = fs;
      if (ss->tiny_fontlist) XM_FONT_FREE(ss->tiny_fontlist);
      ss->tiny_fontlist = get_xm_font(TINY_FONT(ss), font, "tiny_font");
      return(true);
    }
  return(false);
}


bool set_listener_font(const char *font)
{
  XFontStruct *fs = NULL;
  fs = XLoadQueryFont(main_display(ss), font);
  if (fs)
    {
      if (LISTENER_FONT(ss)) XFreeFont(main_display(ss), LISTENER_FONT(ss));
      if (listener_font(ss)) free(listener_font(ss));
      in_set_listener_font(mus_strdup(font));
      LISTENER_FONT(ss) = fs;
      if (ss->listener_fontlist) XM_FONT_FREE(ss->listener_fontlist);
      ss->listener_fontlist = get_xm_font(LISTENER_FONT(ss), font, "listener_font");
      set_listener_text_font();
      return(true);
    }
  return(false);
}


bool set_peaks_font(const char *font)
{
  XFontStruct *fs = NULL;
  fs = XLoadQueryFont(main_display(ss), font);
  if (fs)
    {
      if (PEAKS_FONT(ss)) XFreeFont(main_display(ss), PEAKS_FONT(ss));
      if (peaks_font(ss)) free(peaks_font(ss));
      in_set_peaks_font(mus_strdup(font));
      PEAKS_FONT(ss) = fs;
      if (ss->peaks_fontlist) XM_FONT_FREE(ss->peaks_fontlist);
      ss->peaks_fontlist = get_xm_font(PEAKS_FONT(ss), font, "peaks_font");
      return(true);
    }
  return(false);
}


bool set_bold_peaks_font(const char *font)
{
  XFontStruct *fs = NULL;
  fs = XLoadQueryFont(main_display(ss), font);
  if (fs)
    {
      if (BOLD_PEAKS_FONT(ss)) XFreeFont(main_display(ss), BOLD_PEAKS_FONT(ss));
      if (bold_peaks_font(ss)) free(bold_peaks_font(ss));
      in_set_bold_peaks_font(mus_strdup(font));
      BOLD_PEAKS_FONT(ss) = fs;
      if (ss->bold_peaks_fontlist) XM_FONT_FREE(ss->bold_peaks_fontlist);
      ss->bold_peaks_fontlist = get_xm_font(BOLD_PEAKS_FONT(ss), font, "bold_peaks_font");
      return(true);
    }
  return(false);
}


bool set_axis_label_font(const char *font)
{
  XFontStruct *fs = NULL;
  fs = XLoadQueryFont(main_display(ss), font);
  if (fs)
    {
      if (AXIS_LABEL_FONT(ss)) XFreeFont(main_display(ss), AXIS_LABEL_FONT(ss));
      if (axis_label_font(ss)) free(axis_label_font(ss));
      in_set_axis_label_font(mus_strdup(font));
      AXIS_LABEL_FONT(ss) = fs;
#if HAVE_GL
      reload_label_font();
#endif
      return(true);
    }
  return(false);
}


bool set_axis_numbers_font(const char *font)
{
  XFontStruct *fs = NULL;
  fs = XLoadQueryFont(main_display(ss), font);
  if (fs)
    {
      if (AXIS_NUMBERS_FONT(ss)) XFreeFont(main_display(ss), AXIS_NUMBERS_FONT(ss));
      if (axis_numbers_font(ss)) free(axis_numbers_font(ss));
      in_set_axis_numbers_font(mus_strdup(font));
      AXIS_NUMBERS_FONT(ss) = fs;
#if HAVE_GL
      reload_number_font();
#endif
      return(true);
    }
  return(false);
}


int mark_name_width(const char *txt)
{
  if (txt)
    return(XTextWidth(PEAKS_FONT(ss), txt, strlen(txt)));
  return(0);
}


int label_width(const char *txt, bool use_tiny_font)
{
  if (txt)
    return(XTextWidth((use_tiny_font) ? TINY_FONT(ss) : AXIS_LABEL_FONT(ss), txt, strlen(txt)));
  else return(0);
}


int number_width(const char *num, bool use_tiny_font)
{
  if (num)
    return(XTextWidth((use_tiny_font) ? TINY_FONT(ss) : AXIS_NUMBERS_FONT(ss), num, strlen(num)));
  return(0);
}


int number_height(XFontStruct *numbers_font)
{
  return(numbers_font->ascent);
}


int label_height(bool use_tiny_font)
{
  XFontStruct *label_font;
  if (use_tiny_font)
    label_font = TINY_FONT(ss);
  else label_font = AXIS_LABEL_FONT(ss);
  return(label_font->ascent + label_font->descent);
}


void clear_window(graphics_context *ax)
{
  if ((ax) && (ax->dp) && (ax->wn))
    XClearWindow(ax->dp, ax->wn);
}


static void map_over_children(Widget w, void (*func)(Widget uw))
{
  /* apply func to each child in entire tree beneath top widget */
  /* taken from Douglas Young, "Motif Debugging and Performance Tuning" Prentice-Hall 1995 */
  /* used mostly to get colors right in environments with "convenience" widgets */
  if (w)
    {
      uint32_t i;
      (*func)(w);
      if (XtIsComposite(w))
	{
	  CompositeWidget cw = (CompositeWidget)w;
	  for (i = 0; i < cw->composite.num_children; i++)
	    map_over_children(cw->composite.children[i], func);
	}

      if (XtIsWidget(w))
	for (i = 0; i < w->core.num_popups; i++)
	  map_over_children(w->core.popup_list[i], func);
    }
}


void map_over_children_with_color(Widget w, void (*func)(Widget uw, color_t color), color_t color)
{
  if (w)
    {
      uint32_t i;
      (*func)(w, color);
      if (XtIsComposite(w))
	{
	  CompositeWidget cw = (CompositeWidget)w;
	  for (i = 0; i < cw->composite.num_children; i++)
	    map_over_children_with_color(cw->composite.children[i], func, color);
	}

      if (XtIsWidget(w))
	for (i = 0; i < w->core.num_popups; i++)
	  map_over_children_with_color(w->core.popup_list[i], func, color);
    }
}


static void raise_dialog(Widget w)
{
  /* since we're using non-transient message dialogs, the dialog window can become completely
   * hidden behind other windows, with no easy way to raise it back to the top, so...
   */
  if ((w) && (XtIsManaged(w)))
    {
      Widget parent;
      parent = XtParent(w);
      if ((parent) && 
	  (XtIsSubclass(parent, xmDialogShellWidgetClass)))
	XtPopup(parent, XtGrabNone);
      /* XtGrabNone means don't lock out events to rest of App (i.e. modeless dialog) */
    }
}


static void set_main_color_of_widget(Widget w)
{
  if (XtIsWidget(w))
    {
      if (XmIsScrollBar(w)) 
	XmChangeColor(w, (Pixel)ss->position_color);
      else 
	{
	  Pixel cur_color;
	  XtVaGetValues(w, XmNbackground, &cur_color, NULL);
	  if ((cur_color != ss->highlight_color) &&
	      (cur_color != ss->white))
	    XmChangeColor(w, (Pixel)ss->basic_color);
	}
    }
}


void set_label(Widget label, const char *str)
{
  XmString s1;
  s1 = XmStringCreateLocalized((char *)str);
  XtVaSetValues(label, XmNlabelString, s1, NULL);
  XmStringFree(s1);
}


static char *get_label(Widget label)
{
  char *text;
  XmString str = NULL;
  XtVaGetValues(label, XmNlabelString, &str, NULL);
  if (XmStringEmpty(str)) return(NULL);
  text = (char *)XmStringUnparse(str, NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
  XmStringFree(str);
  return(text);
}


void set_button_label(Widget label, const char *str) 
{
  set_label(label, str);
}


void set_title(const char *title)
{
  XtVaSetValues(main_shell(ss), XmNtitle, (char *)title, NULL);
}


void goto_window(Widget text)
{
  if (XmIsTraversable(text))
    XmProcessTraversal(text, XmTRAVERSE_CURRENT);
}


static XtCallbackList make_callback_list(XtCallbackProc callback, XtPointer closure)
{
  XtCallbackList nlist;
  nlist = (XtCallbackList)calloc(2, sizeof(XtCallbackRec));
  nlist[0].callback = callback;
  nlist[0].closure = closure;
  nlist[1].callback = NULL;
  nlist[1].closure = NULL;
  return(nlist);
}


#include <Xm/SashP.h>
static void color_sashes(Widget w)
{
  if ((XtIsWidget(w)) && 
      (XtIsSubclass(w, xmSashWidgetClass)))
    XmChangeColor(w, (Pixel)ss->sash_color);
}


void check_for_event(void)
{
  /* this is needed to force label updates and provide interrupts from long computations */
  XEvent event;
  XtInputMask msk = 0;
  XtAppContext app;

  if (ss->checking_explicitly) return;
  ss->checking_explicitly = true;

  app = main_app(ss);
  while (true)
    {
      msk = XtAppPending(app);
      /* if (msk & (XtIMXEvent | XtIMAlternateInput)) */
      /* if (msk & XtIMXEvent) */
	/* was also tracking alternate input events, but these are problematic if libfam is in use (even with check)
	 *   but libfam is now long-since forgotten; new form below is thanks to Tito Latini
	 */
      if ((msk & (XtIMXEvent | XtIMAlternateInput)) == XtIMXEvent)
	{
	  XtAppNextEvent(app, &event);
	  XtDispatchEvent(&event);
	  /* widget = XtWindowToWidget(event.xany.display, event.xany.window); */
	}
      else break;
    }
  ss->checking_explicitly = false;
}


void color_cursor(Pixel color)
{
  ss->cursor_color = color;
#if HAVE_SCHEME
  s7_symbol_set_value(s7, ss->cursor_color_symbol, Xen_wrap_pixel(color));
#endif
  XSetForeground(main_display(ss), ss->cursor_gc, (Pixel)(XOR(color, ss->graph_color)));
  XSetForeground(main_display(ss), ss->selected_cursor_gc, (Pixel)(XOR(color, ss->selected_graph_color)));
}


void color_marks(Pixel color)
{
  ss->mark_color = color;
#if HAVE_SCHEME
  s7_symbol_set_value(s7, ss->mark_color_symbol, Xen_wrap_pixel(color));
#endif
  XSetForeground(main_display(ss), ss->mark_gc, (Pixel)(XOR(color, ss->graph_color)));
  XSetForeground(main_display(ss), ss->selected_mark_gc, (Pixel)(XOR(color, ss->selected_graph_color)));
}


void color_selection(Pixel color)
{
  ss->selection_color = color;
#if HAVE_SCHEME
  s7_symbol_set_value(s7, ss->selection_color_symbol, Xen_wrap_pixel(color));
#endif
  XSetForeground(main_display(ss), ss->selection_gc, (Pixel)(XOR(color, ss->graph_color)));
  XSetForeground(main_display(ss), ss->selected_selection_gc, (Pixel)(XOR(color, ss->selected_graph_color)));
}


void color_graph(Pixel color)
{
  Display *dpy;
  dpy = main_display(ss);
  XSetBackground(dpy, ss->basic_gc, color);
  XSetForeground(dpy, ss->erase_gc, color);
  XSetForeground(dpy, ss->selection_gc, (Pixel)(XOR(ss->selection_color, color)));
  XSetForeground(dpy, ss->cursor_gc, (Pixel)(XOR(ss->cursor_color, color)));
  XSetForeground(dpy, ss->mark_gc, (Pixel)(XOR(ss->mark_color, color)));
}


void color_selected_graph(Pixel color)
{
  Display *dpy;
  dpy = main_display(ss);
  XSetBackground(dpy, ss->selected_basic_gc, color);
  XSetForeground(dpy, ss->selected_erase_gc, color);
  XSetForeground(dpy, ss->selected_selection_gc, (Pixel)(XOR(ss->selection_color, color)));
  XSetForeground(dpy, ss->selected_cursor_gc, (Pixel)(XOR(ss->cursor_color, color)));
  XSetForeground(dpy, ss->selected_mark_gc, (Pixel)(XOR(ss->mark_color, color)));
}


void color_data(Pixel color)
{
  Display *dpy;
  dpy = main_display(ss);
  XSetForeground(dpy, ss->basic_gc, color);
  XSetBackground(dpy, ss->erase_gc, color);
}


void color_selected_data(Pixel color)
{
  Display *dpy;
  dpy = main_display(ss);
  XSetForeground(dpy, ss->selected_basic_gc, color);
  XSetBackground(dpy, ss->selected_erase_gc, color);
}


void recolor_graph(chan_info *cp, bool selected)
{
  XtVaSetValues(channel_graph(cp), XmNbackground, (selected) ? ss->selected_graph_color : ss->graph_color, NULL);
}


void set_mix_color(Pixel color)
{
  Display *dpy;
  dpy = main_display(ss);
  ss->mix_color = color;
#if HAVE_SCHEME
  s7_symbol_set_value(s7, ss->mix_color_symbol, Xen_wrap_pixel(color));
#endif
  XSetForeground(dpy, ss->mix_gc, color);
  
}


void set_sensitive(Widget wid, bool val) 
{
  if (wid) XtSetSensitive(wid, val);
}


void set_toggle_button(Widget wid, bool val, bool passed, void *ignore) 
{
  XmToggleButtonSetState(wid, (Boolean)val, (Boolean)passed);
}


Dimension widget_height(Widget w)
{
  Dimension height;
  XtVaGetValues(w, XmNheight, &height, NULL);
  return(height);
}


Dimension widget_width(Widget w)
{
  Dimension width;
  XtVaGetValues(w, XmNwidth, &width, NULL);
  return(width);
}


void set_widget_height(Widget w, Dimension height)
{
  XtVaSetValues(w, XmNheight, height, NULL);
}


void set_widget_width(Widget w, Dimension width)
{
  XtVaSetValues(w, XmNwidth, width, NULL);
}


void set_widget_size(Widget w, Dimension width, Dimension height)
{
  XtVaSetValues(w, XmNwidth, width, XmNheight, height, NULL);
}


Position widget_x(Widget w)
{
  Position x;
  XtVaGetValues(w, XmNx, &x, NULL);
  return(x);
}


Position widget_y(Widget w)
{
  Position y;
  XtVaGetValues(w, XmNy, &y, NULL);
  return(y);
}


void set_widget_x(Widget w, Position x)
{
  XtVaSetValues(w, XmNx, x, NULL);
}


void set_widget_y(Widget w, Position y)
{
  XtVaSetValues(w, XmNy, y, NULL);
}


void set_widget_position(Widget w, Position x, Position y)
{
  XtVaSetValues(w, XmNx, x, XmNy, y, NULL);
}


idle_t add_work_proc(XtWorkProc func, XtPointer data)
{
  /* during auto-testing I need to force the background procs to run to completion */
  if (with_background_processes(ss))
    return(XtAppAddWorkProc(main_app(ss), func, data));
  else
    {
      while (((*func)(data)) == BACKGROUND_CONTINUE) ;
      return((idle_t)0);
    }
}


static int attach_all_sides(Arg *args, int n)
{
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  return(n);
}


static void widget_int_to_text(Widget w, int val)
{
  char *str;
  str = (char *)calloc(16, sizeof(char));
  snprintf(str, 16, "%d", val);
  XmTextFieldSetString(w, str);
  free(str);
}


static void widget_mus_long_t_to_text(Widget w, mus_long_t val)
{
  char *str;
  str = (char *)calloc(32, sizeof(char));
  snprintf(str, 32, "%" print_mus_long, val);
  XmTextFieldSetString(w, str);
  free(str);
}


static Pixmap rotate_text(Widget w, const char *str, XFontStruct *font, mus_float_t angle_in_degrees, int *nw, int *nh, Pixel bg, Pixel fg, GC d_gc)
{
  /* rotate clockwise by angle_in_degrees degrees (i.e. 45 points text south-east), 
   * new bounding box (text centered in it) returned in nw and nh
   * bg = background color, fg = foreground (text) color) 
   */
  mus_float_t matrix[4];
  mus_float_t angle_in_radians;
  XImage *before, *after;
  Pixmap pix, rotpix;
  uint32_t width, height, depth, nwidth, nheight, x, y, nx, ny, tx, ty, depth_bytes;
  char *data;
  unsigned long px;
  Display *dp;
  Drawable wn;
  Visual *vis;
  int scr;
  int bx0 = 0, bx1 = 0, by0 = 0, by1 = 0, b;
  if (!str) return(BadPixmap);

  angle_in_radians = mus_degrees_to_radians(angle_in_degrees);
  matrix[0] = cos(angle_in_radians);
  matrix[1] = sin(angle_in_radians);
  matrix[2] = -sin(angle_in_radians);
  matrix[3] = cos(angle_in_radians);

  dp = XtDisplay(w);
  wn = XtWindow(w);
  scr = DefaultScreen(dp);
  vis = DefaultVisual(dp, scr);

  XtVaGetValues(w, XmNdepth, &depth, NULL);
  depth_bytes = (depth >> 3);
  if (depth_bytes == 0) depth_bytes = 1; /* unsigned so can't be negative */

  /* find extent of original text, expand out to byte boundaries */
  XSetFont(dp, d_gc, font->fid);
  width = XTextWidth(font, str, strlen(str)) + 8;
  height = (font->ascent + font->descent) + 8;
  if (width % 8) width = 8 * (1 + (int)(width / 8));
  if (height % 8) height = 8 * (1 + (int)(height / 8));

  /* get bounding box of rotated text (this could be simplfied -- used to involve scaling) */
  b = (int)(width * matrix[0]);
  if (b < 0) bx0 = b; else bx1 = b;
  b = (int)(height * matrix[2]);
  if (b < 0) bx0 += b; else bx1 += b;
  b = (int)(width * matrix[1]);
  if (b < 0) by0 = b; else by1 = b;
  b = (int)(height * matrix[3]);
  if (b < 0) by0 += b; else by1 += b;
  
  /* set translation vector so we're centered in the resultant pixmap */
  if (bx0 < 0) tx = -bx0; else tx = 0;
  if (by0 < 0) ty = -by0; else ty = 0;
  nx = bx1 - bx0;
  ny = by1 - by0;

  /* expand result bounds to byte boundaries */
  if (nx % 8) nwidth = 8 * (1 + (int)(nx / 8)); else nwidth = nx;
  if (ny % 8) nheight = 8 * (1 + (int)(ny / 8)); else nheight = ny;
  (*nw) = nwidth;
  (*nh) = nheight;

  XSetBackground(dp, d_gc, bg); 
  XSetForeground(dp, d_gc, bg); 

  /* create pixmaps, fill with background color, write string to pix */
  pix = XCreatePixmap(dp, wn, width, height, depth);
  rotpix= XCreatePixmap(dp, wn, nwidth, nheight, depth);
  XFillRectangle(dp, pix, d_gc, 0, 0, width, height);
  XFillRectangle(dp, rotpix, d_gc, 0, 0, nwidth, nheight);
#if HAVE_SUN
  XSync(dp, 0);
  /* needed to get the numbers drawn at all */
#endif
  XSetForeground(dp, d_gc, fg);
  XDrawImageString(dp, pix, d_gc, 4, height - 4, str, strlen(str));

  /* dump pixmap bits into an image; image data will be freed automatically later */
  data = (char *)calloc((width + 1) * (height + 1) * depth_bytes, sizeof(char)); /* not calloc since X will free this */
  before = XCreateImage(dp, vis, depth, XYPixmap, 0, data, width, height, 8, 0);
  XGetSubImage(dp, pix, 0, 0, width, height, AllPlanes, XYPixmap, before, 0, 0);
  data = (char *)calloc((nwidth + 1) * (nheight + 1) * depth_bytes, sizeof(char));
  after = XCreateImage(dp, vis, depth, XYPixmap, 0, data, nwidth, nheight, 8, 0);

  /* clear background of result image */
  for (x = 0; x < nwidth; x++) 
    for (y = 0; y < nheight; y++) 
      XPutPixel(after, x, y, bg);

  /* write rotated pixels to result image */
  for (x = 0; x < width; x++)
    for (y = 0; y < height; y++)
      {
	px = XGetPixel(before, x, y);
	if (px != bg)
	  XPutPixel(after, 
		    mus_iclamp(0, (int)snd_round(tx + x * matrix[0] + y * matrix[2]), nwidth - 1),
		    mus_iclamp(0, (int)snd_round(ty + x * matrix[1] + y * matrix[3]), nheight - 1),
		    px);
      }

  /* dump image into result pixmap (needed for later display) */
  XPutImage(dp, rotpix, d_gc, after, 0, 0, 0, 0, nwidth, nheight);

  /* cleanup */
  XDestroyImage(before);  /* frees data as well */
  XDestroyImage(after);
  XFreePixmap(dp, pix);
  return(rotpix);
}


void draw_rotated_axis_label(chan_info *cp, graphics_context *ax, const char *text, int x0, int y0)
{
  Pixmap pix;
  int h = 0, w = 0;
  XGCValues gv;
  Display *dp;
  Widget widget;

  if ((cp->chan > 0) && (cp->sound->channel_style == CHANNELS_COMBINED))
    widget = channel_graph(cp->sound->chans[0]);
  else widget = channel_graph(cp);
  dp = XtDisplay(widget);
  XGetGCValues(main_display(ss), ax->gc, GCForeground | GCBackground, &gv);

  pix = rotate_text(widget, text, AXIS_LABEL_FONT(ss), -90.0, &w, &h, gv.background, gv.foreground, ax->gc);

  XCopyArea(dp, pix, XtWindow(widget), ax->gc, 0, 0, w, h, x0, y0); /* XtWindow?? */
  XFreePixmap(dp, pix);  
}


static void ensure_list_row_visible(widget_t list, int pos)
{
  if (pos >= 0)
    {
      int top, visible, num_rows;
      XtVaGetValues(list,
		    XmNtopItemPosition, &top,
		    XmNvisibleItemCount, &visible,
		    XmNitemCount, &num_rows,
		    NULL);
      if (pos <= top)
	XmListSetPos(list, pos); /* was pos+1?? (new file dialog sample type list off by 1 in that case) */
      else
	{
	  if (pos >= (top + visible))
	    {
	      if ((pos + visible) > num_rows)
		XmListSetBottomPos(list, num_rows);
	      else XmListSetPos(list, pos);
	    }
	}
    }
}


static void ensure_scrolled_window_row_visible(widget_t list, int row, int num_rows)
{
  int minimum, maximum, value, size, new_value, increment, page_increment;
  Widget scrollbar, work_window;

  XtVaGetValues(list, 
		XmNverticalScrollBar, &scrollbar, 
		XmNworkWindow, &work_window,
		NULL);

  XtVaGetValues(scrollbar, 
		XmNminimum, &minimum,
		XmNmaximum, &maximum,
		XmNvalue, &value,
		XmNsliderSize, &size,
		XmNincrement, &increment, /* needed for XmScrollBarSetValues which is needed to force list display update */
		XmNpageIncrement, &page_increment,
		NULL);

  maximum -= size;
  if (row == 0)
    new_value = 0;
  else
    {
      if (row >= (num_rows - 1))
	new_value = maximum;
      else new_value = (int)((row + 0.5) * ((double)(maximum - minimum) / (double)(num_rows - 1)));
    }
  XmScrollBarSetValues(scrollbar, new_value, size, increment, page_increment, true);
}


static XmString multi_line_label(const char *s, int *lines)
{
  /* taken from the Motif FAQ */
  XmString xms1, xms2, line, separator;
  char *p, *tmp;

  (*lines) = 1;
  tmp = mus_strdup(s);
  separator = XmStringSeparatorCreate();
  p = strtok(tmp, "\n");
  xms1 = XmStringCreateLocalized(p);

  p = strtok(NULL, "\n");
  while (p)
    {
      (*lines)++;
      line = XmStringCreateLocalized(p);
      xms2 = XmStringConcat(xms1, separator);
      XmStringFree(xms1);
      xms1 = XmStringConcat(xms2, line);
      XmStringFree(xms2);
      XmStringFree(line);
      p = strtok(NULL, "\n");
    }

  XmStringFree(separator);
  free(tmp);
  return(xms1);
}

#include <Xm/ScaleP.h>
/* needed to set the scale title background */


void draw_line(graphics_context *ax, int x0, int y0, int x1, int y1) 
{
  XDrawLine(ax->dp, ax->wn, ax->gc, x0, y0, x1, y1);
}


void fill_rectangle(graphics_context *ax, int x0, int y0, int width, int height)
{
  XFillRectangle(ax->dp, ax->wn, ax->gc, x0, y0, width, height);
}


void erase_rectangle(chan_info *cp, graphics_context *ax, int x0, int y0, int width, int height)
{
  XFillRectangle(ax->dp, ax->wn, erase_GC(cp), x0, y0, width, height);
}


void draw_string(graphics_context *ax, int x0, int y0, const char *str, int len)
{
  if ((str) && (*str))
    XDrawString(ax->dp, ax->wn, ax->gc, x0, y0, str, len);
}


void gtk_style_draw_string(graphics_context *ax, int x0, int y0, const char *str, int len)
{
  /* for callers of Scheme-level draw-string, the Motif and (now removed Gtk) versions should agree on where "y0" is */
  XGCValues gv;
  static XFontStruct *fs = NULL;

  XGetGCValues(main_display(ss), ax->gc, GCFont, &gv);

  /* now gv.font is the default font */
  if (fs) XFree(fs);
  /*  this doesn't free all the space */
  /* but this: */
  /* if (fs) XFreeFont(main_display(ss), fs); */
  /* gets:
     X Error of failed request:  BadFont (invalid Font parameter)
     Major opcode of failed request:  56 (X_ChangeGC)
     Resource id in failed request:  0x4e0035c
     Serial number of failed request:  8479111
     Current serial number in output stream:  8479240
  */

  fs = XQueryFont(main_display(ss), gv.font);
  if (fs)
    XDrawString(ax->dp, ax->wn, ax->gc, x0, y0 + fs->ascent, str, len);
  else XDrawString(ax->dp, ax->wn, ax->gc, x0, y0, str, len); /* not sure why this happens... */

  /* XFreeFont here is trouble, but handling it as above seems ok -- Font.c in xlib does allocate new space */
}


static void draw_polygon_va(graphics_context *ax, bool filled, int points, va_list ap)
{
  int i;
  XPoint *pts;
  pts = (XPoint *)calloc(points, sizeof(XPoint));
  for (i = 0; i < points; i++)
    {
      pts[i].x = va_arg(ap, int);
      pts[i].y = va_arg(ap, int);
    }
  if (filled)
    XFillPolygon(ax->dp, ax->wn, ax->gc, pts, points, Convex, CoordModeOrigin);
  else XDrawLines(ax->dp, ax->wn, ax->gc, pts, points, CoordModeOrigin);
  free(pts);
}


void fill_polygon(graphics_context *ax, int points, ...)
{ /* currently used only in snd-marks.c */
  va_list ap;
  if (points == 0) return;
  va_start(ap, points);
  draw_polygon_va(ax, true, points, ap);
  va_end(ap);
}

#if 0
void draw_polygon(graphics_context *ax, int points, ...)
{ 
  va_list ap;
  if (points == 0) return;
  va_start(ap, points);
  draw_polygon_va(ax, false, points, ap);
  va_end(ap);
}
#endif

void draw_lines(graphics_context *ax, point_t *points, int num)
{
  if (num == 0) return;
  XDrawLines(ax->dp, ax->wn, ax->gc, points, num, CoordModeOrigin);
}


void draw_points(graphics_context *ax, point_t *points, int num, int size)
{
  if (num == 0) return;
  if (size == 1)
    XDrawPoints(ax->dp, ax->wn, ax->gc, points, num, CoordModeOrigin);
  else
    {
      int i, size2;
      XArc *rs;
      /* create squares or whatever centered on each point */
      size2 = size / 2;
      rs = (XArc *)calloc(num, sizeof(XArc));
      for (i = 0; i < num; i++)
	{
	  rs[i].x = points[i].x - size2;
	  rs[i].y = points[i].y - size2;
	  rs[i].angle1 = 0;
	  rs[i].angle2 = 360 * 64;
	  rs[i].width = size;
	  rs[i].height = size;
	}
      XFillArcs(ax->dp, ax->wn, ax->gc, rs, num);
      free(rs);
    }
}


#if 0
void draw_point(graphics_context *ax, point_t point, int size)
{
  if (size == 1)
    XDrawPoint(ax->dp, ax->wn, ax->gc, point.x, point.y);
  else
    XFillArc(ax->dp, ax->wn, ax->gc, 
	     point.x - size / 2, 
	     point.y - size / 2, 
	     size, size, 0, 
	     360 * 64);
}
#endif


void draw_dot(graphics_context *ax, int x, int y, int size)
{
  XFillArc(ax->dp, ax->wn, ax->gc, 
	   x - size / 2, 
	   y - size / 2, 
	   size, size, 0, 
	   360 * 64);
}


void fill_polygons(graphics_context *ax, point_t *points, int num, int y0)
{
  XPoint polypts[4];
  int i;
  for (i = 1; i < num; i++)
    {
      polypts[0].x = points[i - 1].x;
      polypts[0].y = points[i - 1].y;
      polypts[1].x = points[i].x;
      polypts[1].y = points[i].y;
      polypts[2].x = polypts[1].x;
      polypts[2].y = y0;
      polypts[3].x = points[i - 1].x;
      polypts[3].y = y0;
      XFillPolygon(ax->dp, ax->wn, ax->gc, polypts, 4, Convex, CoordModeOrigin);
    }
}


void fill_two_sided_polygons(graphics_context *ax, point_t *points, point_t *points1, int num)
{
  XPoint polypts[4];
  int i;
  for (i = 1; i < num; i++)
    {
      polypts[0].x = points[i - 1].x;
      polypts[0].y = points[i - 1].y;
      polypts[1].x = points[i].x;
      polypts[1].y = points[i].y;
      polypts[2].x = points1[i].x;
      polypts[2].y = points1[i].y;
      polypts[3].x = points1[i - 1].x;
      polypts[3].y = points1[i - 1].y;
      XFillPolygon(ax->dp, ax->wn, ax->gc, polypts, 4, Convex, CoordModeOrigin);
    }
}


void setup_graphics_context(chan_info *cp, graphics_context *ax)
{
  Widget w;
  w = channel_to_widget(cp);
  ax->dp = XtDisplay(w);
  ax->gc = copy_GC(cp);
  ax->wn = XtWindow(w);
}


/* colormaps */

static int sono_bins = 0;             /* tracks total_bins -- each sono_data[i] is an array of total_bins rectangles */
static Pixel *current_colors = NULL;
static int current_colors_size = 0;
static int current_colormap = BLACK_AND_WHITE_COLORMAP;
static XRectangle **sono_data = NULL; /* each entry in sono_data is an array of colormap_size arrays: sono_data[colormap_size][total_bins] */
static int sono_colors = 0;           /* tracks colormap_size */
static GC colormap_GC;


void check_colormap_sizes(int colors)
{
  int i, old_size;
  if (current_colors_size > 0)
    {
      if (current_colormap != BLACK_AND_WHITE_COLORMAP)
	{
	  int scr;
	  Colormap cmap;
	  Display *dpy;
	  dpy = XtDisplay(main_shell(ss));
	  scr = DefaultScreen(dpy);
	  cmap = DefaultColormap(dpy, scr);
	  XFreeColors(dpy, cmap, current_colors, current_colors_size, 0);
	  current_colormap = BLACK_AND_WHITE_COLORMAP;
	}
      if ((current_colors) && (current_colors_size < colors))
	{
	  old_size = current_colors_size;
	  current_colors_size = colors;
	  current_colors = (Pixel *)realloc(current_colors, current_colors_size * sizeof(Pixel));
	  for (i = old_size; i < current_colors_size; i++) current_colors[i] = 0;
	}
    }
  if ((sono_data) && (sono_colors < colors) && (sono_bins > 0))
    {
      old_size = sono_colors;
      sono_colors = colors;
      sono_data = (XRectangle **)realloc(sono_data, sono_colors * sizeof(XRectangle *));
      for (i = old_size; i < sono_colors; i++) sono_data[i] = (XRectangle *)calloc(sono_bins, sizeof(XRectangle));
    }
}


static void initialize_colormap(void)
{
  XGCValues gv;
  gv.background = ss->white;
  gv.foreground = ss->data_color;
  colormap_GC = XCreateGC(main_display(ss), XtWindow(main_shell(ss)), GCForeground | GCBackground, &gv);
  sono_colors = color_map_size(ss);
  sono_data = (XRectangle **)calloc(sono_colors, sizeof(XRectangle *));
  current_colors_size = color_map_size(ss);
  current_colors = (Pixel *)calloc(current_colors_size, sizeof(Pixel));
}


void draw_spectro_line(graphics_context *ax, int color, int x0, int y0, int x1, int y1)
{
  XSetForeground(ax->dp, colormap_GC, current_colors[color]);
  XDrawLine(ax->dp, ax->wn, colormap_GC, x0, y0, x1, y1);
}


void draw_sono_rectangles(graphics_context *ax, int color, int jmax)
{
  XSetForeground(ax->dp, colormap_GC, current_colors[color]);
  XFillRectangles(ax->dp, ax->wn, colormap_GC, sono_data[color], jmax); 
}


void set_sono_rectangle(int j, int color, int x, int y, int width, int height)
{
  XRectangle *r;
  r = sono_data[color];
  r[j].x = x;
  r[j].y = y;
  r[j].width = width;
  r[j].height = height;
}


void allocate_sono_rects(int bins)
{
  if (bins != sono_bins)
    {
      int i;
      for (i = 0; i < sono_colors; i++)
	{
	  if ((sono_bins > 0) && (sono_data[i]))
	    free(sono_data[i]); /* each is array of XRectangle structs, but it's the wrong size */
	  sono_data[i] = (XRectangle *)calloc(bins, sizeof(XRectangle));
	}
      sono_bins = bins;
    }
}


void allocate_color_map(int colormap)
{
  static bool warned_color = false;
  if (current_colormap != colormap)
    {
      int i;
      Colormap cmap;
      XColor tmp_color;
      Display *dpy;
      int scr;
      tmp_color.flags = DoRed | DoGreen | DoBlue;

      dpy = XtDisplay(main_shell(ss));
      scr = DefaultScreen(dpy);
      cmap = DefaultColormap(dpy, scr);

      /* 8-bit color displays can't handle all these colors, apparently, so we have to check status */
      if (current_colormap != BLACK_AND_WHITE_COLORMAP) 
	XFreeColors(dpy, cmap, current_colors, current_colors_size, 0);

      for (i = 0; i < current_colors_size; i++)
	{
	  get_current_color(colormap, i, &(tmp_color.red), &(tmp_color.green), &(tmp_color.blue));
	  if ((XAllocColor(dpy, cmap, &tmp_color)) == 0) /* 0 = failure -- try black as a fallback */
	    {
	      tmp_color.red = 0;
	      tmp_color.green = 0;
	      tmp_color.blue = 0;
	      if ((XAllocColor(dpy, cmap, &tmp_color)) == 0)
		{
		  if (!warned_color)
		    snd_error_without_format("can't even allocate black?!?");
		  warned_color = true;
		}
	    }
	  current_colors[i] = tmp_color.pixel;
	}
      current_colormap = colormap;
    }
}


void draw_colored_lines(chan_info *cp, graphics_context *ax, point_t *points, int num, int *colors, int axis_y0, color_t default_color)
{
  int i, x0, y0, y2 = 0, y00 = -1, cur, prev;
  color_t old_color;

  if (num <= 0) return;

  old_color = get_foreground_color(ax);

  x0 = points[0].x;
  y0 = points[0].y;

  if (abs(y0 - axis_y0) < 5)
    prev = -1;
  else prev = colors[0];

  set_foreground_color(ax, (prev == -1) ? default_color : current_colors[prev]);

  for (i = 1; i < num; i++)
    {
      int x1, y1;
      x1 = points[i].x;
      y1 = points[i].y;
      if (i < num - 1)
	y2 = points[i + 1].y;
      else y2 = y1;

      if ((abs(y0 - axis_y0) < 5) &&
	  (abs(y1 - axis_y0) < 5))
	cur = -1;
      else 
	{
	  if ((y00 > y0) &&
	      (y00 > y1) &&
	      (i > 1))
	    cur = colors[i - 2];
	  else
	    {
	      if ((y2 > y1) &&
		  (y2 > y0))
		cur = colors[i + 1];
	      else
		{
		  if (y0 > y1)
		    cur = colors[i];
		  else cur = colors[i - 1]; /* coords are upside down */
		}
	    }
	}

      if (cur != prev)
	{
	  set_foreground_color(ax, (cur == -1) ? default_color : current_colors[cur]);
	  prev = cur;
	}

      if (cp->transform_graph_style == GRAPH_DOTS)
	draw_dot(ax, x0, y0, cp->dot_size);
      else draw_line(ax, x0, y0, x1, y1);

      y00 = y0;
      x0 = x1;
      y0 = y1;
    }

  set_foreground_color(ax, old_color);
}



/* -------- color/orientation browser -------- */

static Xen color_hook;

static void check_color_hook(void)
{
  if (Xen_hook_has_list(color_hook))
    run_hook(color_hook, Xen_empty_list, S_color_hook);
}


static Widget ccd_dialog = NULL, ccd_list, ccd_scale, ccd_invert, ccd_cutoff;

static void update_graph_setting_fft_changed(chan_info *cp)
{
  cp->fft_changed = FFT_CHANGE_LOCKED;
  update_graph(cp);
}


static void invert_color_callback(Widget w, XtPointer context, XtPointer info)
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  in_set_color_inverted(cb->set);
  check_color_hook();
  for_each_chan(update_graph_setting_fft_changed);
}


void set_color_inverted(bool val)
{
  in_set_color_inverted(val);
  if (ccd_dialog) 
    XmToggleButtonSetState(ccd_invert, (Boolean)val, false);
  check_color_hook();
  if (!(ss->graph_hook_active)) 
    for_each_chan(update_graph_setting_fft_changed);
}


static void scale_color_callback(Widget w, XtPointer context, XtPointer info)
{
  mus_float_t val;
  int scale_val;
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  scale_val = cbs->value;
  if (scale_val <= 50) 
    val = (mus_float_t)(scale_val + 1) / 51.0;
  else val = 1.0 + (mus_float_t)((scale_val - 50) * (scale_val - 50)) / 12.5;
  in_set_color_scale(val);
  check_color_hook();
  for_each_chan(update_graph_setting_fft_changed);
}


static void reflect_color_scale(mus_float_t val)
{
  if (val < 0.02)
    XmScaleSetValue(ccd_scale, 0);
  else
    {
      if (val <= 1.0) 
	XmScaleSetValue(ccd_scale, mus_iclamp(0, (int)(val * 51.0 - 1), 100));
      else XmScaleSetValue(ccd_scale, mus_iclamp(0, 50 + (int)sqrt((val - 1.0) * 12.5), 100));
    }
}


void set_color_scale(mus_float_t val)
{
  in_set_color_scale(val);
  if (ccd_dialog) 
    reflect_color_scale(color_scale(ss));
  if (!(ss->graph_hook_active)) 
    for_each_chan(update_graph_setting_fft_changed);
}


static void list_color_callback(Widget w, XtPointer context, XtPointer info)
{
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)info;
  if (is_colormap(cbs->item_position - 1))
    {
      in_set_color_map(cbs->item_position - 1);
      check_color_hook();
      for_each_chan(update_graph_setting_fft_changed);
    }
}


void set_color_map(int val)
{
  in_set_color_map(val);
  if ((ccd_dialog) && (val >= 0))
    XmListSelectPos(ccd_list, val + 1, false);
  check_color_hook();
  if (!(ss->graph_hook_active)) 
    for_each_chan(update_graph_setting_fft_changed);
}


static XmString fscale_label(const char *orig_label, mus_float_t value)
{
  XmString x;
  char *lab;
  lab = mus_format("%s: %.3f", orig_label, value);
  x = XmStringCreateLocalized(lab);
  free(lab);
  return(x);
}


static void fscale_set_label(const char *orig_label, Widget w, mus_float_t value)
{
  XmString x;
  char *lab;
  lab = mus_format("%s: %.3f", orig_label, value);
  x = XmStringCreateLocalized(lab);
  XtVaSetValues(w, XmNtitleString, x, NULL);
  free(lab);
  XmStringFree(x);
}


static void cutoff_color_callback(Widget w, XtPointer context, XtPointer info) /* cutoff point */
{
  /* cutoff point for color chooser */
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  in_set_color_cutoff((mus_float_t)(cbs->value) / 1000.0);
  fscale_set_label("data cutoff", w, color_cutoff(ss));
  check_color_hook();
  for_each_chan(update_graph_setting_fft_changed);
}


void set_color_cutoff(mus_float_t val)
{
  in_set_color_cutoff(val);
  if (ccd_dialog) 
    XmScaleSetValue(ccd_cutoff, (int)(val * 1000.0));
  if (!(ss->graph_hook_active)) 
    for_each_chan(update_graph_setting_fft_changed);
}


static void dismiss_color_orientation_callback(Widget w, XtPointer context, XtPointer info)
{
  XtUnmanageChild(ccd_dialog);
}


static void help_color_orientation_callback(Widget w, XtPointer context, XtPointer info)
{
  color_orientation_dialog_help();
}


void reflect_color_list(bool setup_time)
{
  if ((ccd_dialog) && (ccd_list))
    {
      int i, size;
      XmString *cmaps;
      size = num_colormaps();
      cmaps = (XmString *)calloc(size, sizeof(XmString));
      for (i = 0; i < size; i++)
	cmaps[i] = XmStringCreateLocalized(colormap_name(i));
      XtVaSetValues(ccd_list, 
		    XmNitems, cmaps, 
		    XmNitemCount, size,
		    NULL);
      if (setup_time)
	XtVaSetValues(ccd_list, 
		      XmNvisibleItemCount, 6,
		      NULL);
      for (i = 0; i < size; i++) XmStringFree(cmaps[i]);
      free(cmaps);
    }
}


static Xen orientation_hook;

static void check_orientation_hook(void)
{
  if (Xen_hook_has_list(orientation_hook))
    run_hook(orientation_hook, Xen_empty_list, S_orientation_hook);
}


static Widget oid_ax, oid_ay, oid_az, oid_sx, oid_sy, oid_sz, oid_hop;
#if HAVE_GL
  static Widget oid_glbutton; 
#endif

#define HOP_MAX 20

static XmString scale_label(const char *orig_label, int value, bool dec)
{
  XmString x;
  char *lab;
  if (!dec)
    lab = mus_format("%s: %d", orig_label, value);
  else lab = mus_format("%s: %.2f", orig_label, value * 0.01);
  x = XmStringCreateLocalized(lab);
  free(lab);
  return(x);
}


static void scale_set_label(const char *orig_label, Widget w, int value, bool dec)
{
  /* in new motif (after version 2.1), showValue not XmNONE (= 0) clobbers XmScale title! 
   *   also XmNEAR_BORDER has no effect -- same as XmNEAR_SLIDER
   * so...
   *   we create the full label by hand here.
   */

  XmString x;
  char *lab;
  if (!dec)
    lab = mus_format("%s: %d", orig_label, value);
  else lab = mus_format("%s: %.2f", orig_label, value * 0.01);
  x = XmStringCreateLocalized(lab);
  XtVaSetValues(w, XmNtitleString, x, NULL);
  free(lab);
  XmStringFree(x);
}


static void ax_orientation_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  scale_set_label("x angle", w, cbs->value, false);
  in_set_spectro_x_angle((mus_float_t)(cbs->value));
  chans_field(FCP_X_ANGLE, (mus_float_t)(cbs->value));
  check_orientation_hook();
  for_each_chan(update_graph);
}


void set_spectro_x_angle(mus_float_t val)
{
  if (val < 0.0) val += 360.0; else if (val >= 360.0) val = fmod(val, 360.0);
  in_set_spectro_x_angle(val);
  if (ccd_dialog) 
    {
      XmScaleSetValue(oid_ax, (int)val);
      scale_set_label("x angle", oid_ax, (int)val, false);
    }
  chans_field(FCP_X_ANGLE, val);
  check_orientation_hook();
  if (!(ss->graph_hook_active)) 
    for_each_chan(update_graph);
}


static void ay_orientation_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  scale_set_label("y angle", w, cbs->value, false);
  in_set_spectro_y_angle((mus_float_t)(cbs->value));
  chans_field(FCP_Y_ANGLE, (mus_float_t)(cbs->value));
  check_orientation_hook();
  for_each_chan(update_graph);
}


void set_spectro_y_angle(mus_float_t val)
{
  if (val < 0.0) val += 360.0; else if (val >= 360.0) val = fmod(val, 360.0);
  in_set_spectro_y_angle(val);
  if (ccd_dialog) 
    {
      XmScaleSetValue(oid_ay, (int)val);
      scale_set_label("y angle", oid_ay, (int)val, false);
    }
  chans_field(FCP_Y_ANGLE, val);
  check_orientation_hook();
  if (!(ss->graph_hook_active)) 
    for_each_chan(update_graph);
}


static void az_orientation_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  scale_set_label("z angle", w, cbs->value, false);
  in_set_spectro_z_angle((mus_float_t)(cbs->value));
  chans_field(FCP_Z_ANGLE, (mus_float_t)(cbs->value));
  check_orientation_hook();
  for_each_chan(update_graph);
}


void set_spectro_z_angle(mus_float_t val)
{
  if (val < 0.0) val += 360.0; else if (val >= 360.0) val = fmod(val, 360.0);
  in_set_spectro_z_angle(val);
  if (ccd_dialog) 
    {
      XmScaleSetValue(oid_az, (int)val);
      scale_set_label("z angle", oid_az, (int)val, false);
    }
  chans_field(FCP_Z_ANGLE, val);
  check_orientation_hook();
  if (!(ss->graph_hook_active)) 
    for_each_chan(update_graph);
}


static void sx_orientation_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  scale_set_label("x scale", w, cbs->value, true);
  in_set_spectro_x_scale((mus_float_t)(cbs->value) * 0.01);
  chans_field(FCP_X_SCALE, (mus_float_t)(cbs->value) * 0.01);
  check_orientation_hook();
  for_each_chan(update_graph);
}


void set_spectro_x_scale(mus_float_t val)
{
  in_set_spectro_x_scale(val);
  if (ccd_dialog) 
    {
      int value;
      value = mus_iclamp(0, (int)(val * 100), (int)(100 * SPECTRO_X_SCALE_MAX));
      XmScaleSetValue(oid_sx, value);
      scale_set_label("x scale", oid_sx, value, true);
    }
  chans_field(FCP_X_SCALE, val);
  check_orientation_hook();
  if (!(ss->graph_hook_active)) 
    for_each_chan(update_graph);
}


static void sy_orientation_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  scale_set_label("y scale", w, cbs->value, true);
  in_set_spectro_y_scale((mus_float_t)(cbs->value) * 0.01);
  chans_field(FCP_Y_SCALE, (mus_float_t)(cbs->value) * 0.01);
  check_orientation_hook();
  for_each_chan(update_graph);
}


void set_spectro_y_scale(mus_float_t val)
{
  in_set_spectro_y_scale(val);
  if (ccd_dialog) 
    {
      int value;
      value = mus_iclamp(0, (int)(val * 100), (int)(100 * SPECTRO_Y_SCALE_MAX));
      XmScaleSetValue(oid_sy, value);
      scale_set_label("y scale", oid_sy, value, true);
    }
  chans_field(FCP_Y_SCALE, val);
  check_orientation_hook();
  if (!(ss->graph_hook_active)) 
    for_each_chan(update_graph);
}


static void sz_orientation_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  scale_set_label("z scale", w, cbs->value, true);
  in_set_spectro_z_scale((mus_float_t)(cbs->value) * 0.01);
  chans_field(FCP_Z_SCALE, (mus_float_t)(cbs->value) * 0.01);
  check_orientation_hook();
  for_each_chan(update_graph);
}


void set_spectro_z_scale(mus_float_t val)
{
  in_set_spectro_z_scale(val);
  if (ccd_dialog) 
    {
      int value;
      value = mus_iclamp(0, (int)(val * 100), (int)(100 * SPECTRO_Z_SCALE_MAX));
      XmScaleSetValue(oid_sz, value);
      scale_set_label("z scale", oid_sz, value, true);
    }
  chans_field(FCP_Z_SCALE, val);
  check_orientation_hook();
  if (!(ss->graph_hook_active)) 
    for_each_chan(update_graph);
}


static void chans_spectro_hop(chan_info *cp, int value)
{
  cp->spectro_hop = value;
}


static void hop_orientation_callback(Widget w, XtPointer context, XtPointer info) 
{
  int val;
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  scale_set_label("hop", w, cbs->value, false);
  val = mus_iclamp(1, cbs->value, HOP_MAX);
  in_set_spectro_hop(val);
  for_each_chan_with_int(chans_spectro_hop,val);
  check_orientation_hook();
  for_each_chan(update_graph);
}


void set_spectro_hop(int val)
{
  if (val > 0)
    {
      in_set_spectro_hop(val);
      if (ccd_dialog) 
	{
	  int value;
	  value = mus_iclamp(1, val, HOP_MAX);
	  XmScaleSetValue(oid_hop, value);
	  scale_set_label("hop", oid_hop, value, false);
	}
      for_each_chan_with_int(chans_spectro_hop, val);
      check_orientation_hook();
      if (!(ss->graph_hook_active)) 
	for_each_chan(update_graph);
    }
}


static int fixup_angle(mus_float_t ang)
{
  int na;
  na = (int)ang;
  if (na < 0) na += 360;
  na = na % 360;
  return(na);
}


void reflect_spectro(void)
{
  /* set color/orientaton widget values */
  if (ccd_dialog) 
    {
      XmToggleButtonSetState(ccd_invert, (Boolean)(color_inverted(ss)), false);
      XtVaSetValues(ccd_cutoff, XmNvalue, (int)((color_cutoff(ss)) * 1000), NULL);
      reflect_color_scale(color_scale(ss));

      XtVaSetValues(oid_ax, XmNvalue, fixup_angle(spectro_x_angle(ss)), NULL);
      XtVaSetValues(oid_ay, XmNvalue, fixup_angle(spectro_y_angle(ss)), NULL);
      XtVaSetValues(oid_az, XmNvalue, fixup_angle(spectro_z_angle(ss)), NULL);
      XtVaSetValues(oid_sx, XmNvalue, mus_iclamp(0, (int)(spectro_x_scale(ss) * 100), 100), NULL);
      XtVaSetValues(oid_sy, XmNvalue, mus_iclamp(0, (int)(spectro_y_scale(ss) * 100), 100), NULL);
      XtVaSetValues(oid_sz, XmNvalue, mus_iclamp(0, (int)(spectro_z_scale(ss) * 100), 100), NULL);
      XtVaSetValues(oid_hop, XmNvalue, mus_iclamp(1, spectro_hop(ss), HOP_MAX), NULL);
      check_orientation_hook();
    }
}


void set_with_gl(bool val, bool with_dialogs)
{
#if HAVE_GL
  sgl_save_currents();
#endif
  in_set_with_gl(val);
#if HAVE_GL
  sgl_set_currents(with_dialogs);
  if ((ccd_dialog) && (with_dialogs))
    XmToggleButtonSetState(oid_glbutton, val, false);
#endif
} 


#if HAVE_GL
static void with_gl_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  sgl_save_currents();
  in_set_with_gl(cb->set);
  sgl_set_currents(true);
  /* this only sets the slider positions -- it doesn't update the labels! */
  /*   and  reflect_spectro() doesn't help! */
  if (ccd_dialog)
    {
      scale_set_label("x angle", oid_ax, spectro_x_angle(ss), false);
      scale_set_label("y angle", oid_ay, spectro_y_angle(ss), false);
      scale_set_label("z angle", oid_az, spectro_z_angle(ss), false);
      scale_set_label("x scale", oid_sx, spectro_x_scale(ss), false);
      scale_set_label("y scale", oid_sy, spectro_y_scale(ss), false);
      scale_set_label("z scale", oid_sz, spectro_z_scale(ss), false);
    }
  for_each_chan(update_graph);
}
#endif


static void reset_color_orientation_callback(Widget w, XtPointer context, XtPointer info) 
{
  /* put everything back the way it was at the start.
   *     this sets everything to the startup defaults -- should they be the dialog startup values instead? 
   */
  set_color_cutoff(DEFAULT_COLOR_CUTOFF);
  set_color_inverted(DEFAULT_COLOR_INVERTED);
  set_color_scale(DEFAULT_COLOR_SCALE);
  set_color_map(DEFAULT_COLOR_MAP);

  reset_spectro(); 
  reflect_spectro();
  for_each_chan(update_graph);
}



/* I tried a scrolled window with each colormap name in an appropriate color, but it looked kinda dumb */

Widget make_color_orientation_dialog(bool managed)
{
  if (!ccd_dialog)
    {
      Arg args[32];
      int n, initial_value;
      XmString xhelp, xdismiss, xinvert, titlestr, xreset, xstr;
      Widget mainform, light_label, lsep, rsep, sep1, tsep, color_frame, orientation_frame, color_form, orientation_form;
      Widget color_title, orientation_title;
#if HAVE_GL
      XmString glstr;
#endif

      xdismiss = XmStringCreateLocalized((char *)I_GO_AWAY); /* needed by template dialog */
      xhelp = XmStringCreateLocalized((char *)I_HELP);
      xreset = XmStringCreateLocalized((char *)"Reset");
      titlestr = XmStringCreateLocalized((char *)"Color and Orientation");

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNcancelLabelString, xdismiss); n++;
      XtSetArg(args[n], XmNhelpLabelString, xhelp); n++;
      XtSetArg(args[n], XmNokLabelString, xreset); n++;
      XtSetArg(args[n], XmNautoUnmanage, false); n++;
      XtSetArg(args[n], XmNdialogTitle, titlestr); n++;
      XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
      XtSetArg(args[n], XmNnoResize, false); n++;
      XtSetArg(args[n], XmNtransient, false); n++;
      ccd_dialog = XmCreateTemplateDialog(main_shell(ss), (char *)"Color and Orientation", args, n);

      XtAddCallback(ccd_dialog, XmNcancelCallback, dismiss_color_orientation_callback, NULL);
      XtAddCallback(ccd_dialog, XmNhelpCallback, help_color_orientation_callback, NULL);
      XtAddCallback(ccd_dialog, XmNokCallback, reset_color_orientation_callback, NULL);

      XmStringFree(xhelp);
      XmStringFree(xdismiss);
      XmStringFree(titlestr);
      XmStringFree(xreset);

      XtVaSetValues(XmMessageBoxGetChild(ccd_dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(ccd_dialog, XmDIALOG_HELP_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(ccd_dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(ccd_dialog, XmDIALOG_HELP_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(ccd_dialog, XmDIALOG_OK_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(ccd_dialog, XmDIALOG_OK_BUTTON), XmNbackground, ss->highlight_color, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, XmMessageBoxGetChild(ccd_dialog, XmDIALOG_SEPARATOR)); n++;
      mainform = XtCreateManagedWidget("formd", xmFormWidgetClass, ccd_dialog, args, n);

      /* color section */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNborderWidth, 10); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      color_frame = XtCreateManagedWidget("color", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      color_form = XtCreateManagedWidget("cform", xmFormWidgetClass, color_frame, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;
      color_title = XtCreateManagedWidget("colors", xmLabelWidgetClass, color_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNleftPosition, 60); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, color_title); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNlistMarginWidth, 3); n++;
      ccd_list = XmCreateScrolledList(color_form, (char *)"colormap-list", args, n);

      XtVaSetValues(ccd_list, 
		    XmNbackground, ss->white, 
		    XmNforeground, ss->black, 
		    NULL);
      reflect_color_list(true);
      XtAddCallback(ccd_list, XmNbrowseSelectionCallback, list_color_callback, NULL);
      XtManageChild(ccd_list);
      XmListSelectPos(ccd_list, color_map(ss) + 1, false);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, color_title); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
      XtSetArg(args[n], XmNwidth, 10); n++;
      lsep = XtCreateManagedWidget("sep", xmSeparatorWidgetClass, color_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, ccd_list); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, color_title); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
      XtSetArg(args[n], XmNwidth, 10); n++;
      rsep = XtCreateManagedWidget("sep", xmSeparatorWidgetClass, color_form, args, n);

      /* this horizontal separator exists solely to keep the "light" label from clobbering the "dark" label! */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, lsep); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, rsep); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, color_title); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNwidth, 250); n++;
      XtSetArg(args[n], XmNheight, 10); n++;
      sep1 = XtCreateManagedWidget("sep1", xmSeparatorWidgetClass, color_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, lsep); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, rsep); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep1); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNshowValue, XmNEAR_SLIDER); n++;
      XtSetArg(args[n], XmNvalue, 50); n++;
      ccd_scale = XtCreateManagedWidget("ccdscl", xmScaleWidgetClass, color_form, args, n);
      XtAddCallback(ccd_scale, XmNvalueChangedCallback, scale_color_callback, NULL);
      XtAddCallback(ccd_scale, XmNdragCallback, scale_color_callback, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, lsep); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, ccd_scale); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      light_label = XtCreateManagedWidget("light", xmLabelWidgetClass, color_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, rsep); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, ccd_scale); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtCreateManagedWidget("dark", xmLabelWidgetClass, color_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, lsep); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, rsep); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, light_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNwidth, 250); n++;
      XtSetArg(args[n], XmNheight, 10); n++;
      tsep = XtCreateManagedWidget("tsep", xmSeparatorWidgetClass, color_form, args, n);

      n = 0;
      xstr = fscale_label("data cutoff", color_cutoff(ss));
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, lsep); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, rsep); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, tsep); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNshowValue, XmNONE); n++;
      XtSetArg(args[n], XmNmaximum, 250); n++;
      XtSetArg(args[n], XmNdecimalPoints, 3); n++;
      XtSetArg(args[n], XmNtitleString, xstr); n++;
      XtSetArg(args[n], XmNvalue, (int)(color_cutoff(ss) * 1000)); n++;
      ccd_cutoff = XtCreateManagedWidget("cutoff", xmScaleWidgetClass, color_form, args, n);
      XtAddCallback(ccd_cutoff, XmNvalueChangedCallback, cutoff_color_callback, NULL);
      XtAddCallback(ccd_cutoff, XmNdragCallback, cutoff_color_callback, NULL);
      XmStringFree(xstr);

      XtVaSetValues(((XmScaleWidget)ccd_cutoff)->composite.children[0], XmNbackground, ss->basic_color, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, lsep); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, ccd_cutoff); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNset, color_inverted(ss)); n++;
      xinvert = XmStringCreateLocalized((char *)"invert");
      XtSetArg(args[n], XmNlabelString, xinvert); n++;
      ccd_invert = make_togglebutton_widget("invert", color_form, args, n);
      XtAddCallback(ccd_invert, XmNvalueChangedCallback, invert_color_callback, NULL);
      XmStringFree(xinvert);


      /* orientation section */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, color_frame); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNborderWidth, 10); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      orientation_frame = XtCreateManagedWidget("color", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      orientation_form = XtCreateManagedWidget("oform", xmFormWidgetClass, orientation_frame, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;
      orientation_title = XtCreateManagedWidget("orientation", xmLabelWidgetClass, orientation_form, args, n);

      #define SCALE_BORDER_WIDTH 6

      n = 0;
      initial_value = fixup_angle(spectro_x_angle(ss));
      xstr = scale_label("x angle", initial_value, false);
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNshowValue, XmNONE); n++;
      XtSetArg(args[n], XmNvalue, initial_value); n++;
      XtSetArg(args[n], XmNmaximum, 360); n++;
      XtSetArg(args[n], XmNtitleString, xstr); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 48); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, orientation_title); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNborderWidth, SCALE_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      oid_ax = XtCreateManagedWidget("ax", xmScaleWidgetClass, orientation_form, args, n);
      XtAddCallback(oid_ax, XmNvalueChangedCallback, ax_orientation_callback, NULL);
      XtAddCallback(oid_ax, XmNdragCallback, ax_orientation_callback, NULL);
      XmStringFree(xstr);

      XtVaSetValues(((XmScaleWidget)oid_ax)->composite.children[0], XmNbackground, ss->basic_color, NULL);

      n = 0;
      initial_value = mus_iclamp(0, (int)(spectro_x_scale(ss) * 100), (int)(100 * SPECTRO_X_SCALE_MAX));
      xstr = scale_label("x scale", initial_value, true);
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNshowValue, XmNONE); n++;
      XtSetArg(args[n], XmNmaximum, (int)(100 * SPECTRO_X_SCALE_MAX)); n++;
      XtSetArg(args[n], XmNvalue, initial_value); n++;
      XtSetArg(args[n], XmNtitleString, xstr); n++;
      XtSetArg(args[n], XmNdecimalPoints, 2); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNleftPosition, 52); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, oid_ax); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNborderWidth, SCALE_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      oid_sx = XtCreateManagedWidget("xs", xmScaleWidgetClass, orientation_form, args, n);
      XtAddCallback(oid_sx, XmNvalueChangedCallback, sx_orientation_callback, NULL);
      XtAddCallback(oid_sx, XmNdragCallback, sx_orientation_callback, NULL);
      XmStringFree(xstr);

      XtVaSetValues(((XmScaleWidget)oid_sx)->composite.children[0], XmNbackground, ss->basic_color, NULL);

      n = 0;
      initial_value = fixup_angle(spectro_y_angle(ss));
      xstr = scale_label("y angle", initial_value, false);
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNshowValue, XmNONE); n++;
      XtSetArg(args[n], XmNvalue, initial_value); n++;
      XtSetArg(args[n], XmNmaximum, 360); n++;
      XtSetArg(args[n], XmNtitleString, xstr); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 48); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, oid_ax); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNborderWidth, SCALE_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      oid_ay = XtCreateManagedWidget("ay", xmScaleWidgetClass, orientation_form, args, n);
      XtAddCallback(oid_ay, XmNvalueChangedCallback, ay_orientation_callback, NULL);
      XtAddCallback(oid_ay, XmNdragCallback, ay_orientation_callback, NULL);
      XmStringFree(xstr);

      XtVaSetValues(((XmScaleWidget)oid_ay)->composite.children[0], XmNbackground, ss->basic_color, NULL);

      n = 0;
      initial_value = mus_iclamp(0, (int)(spectro_y_scale(ss) * 100), (int)(100 * SPECTRO_Y_SCALE_MAX));
      xstr = scale_label("y scale", initial_value, true);
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNshowValue, XmNONE); n++;
      XtSetArg(args[n], XmNmaximum, (int)(100 * SPECTRO_Y_SCALE_MAX)); n++;
      XtSetArg(args[n], XmNvalue, initial_value); n++;
      XtSetArg(args[n], XmNtitleString, xstr); n++;
      XtSetArg(args[n], XmNdecimalPoints, 2); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNleftPosition, 52); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, oid_sx); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNborderWidth, SCALE_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      oid_sy = XtCreateManagedWidget("ys", xmScaleWidgetClass, orientation_form, args, n);
      XtAddCallback(oid_sy, XmNvalueChangedCallback, sy_orientation_callback, NULL);
      XtAddCallback(oid_sy, XmNdragCallback, sy_orientation_callback, NULL);
      XmStringFree(xstr);

      XtVaSetValues(((XmScaleWidget)oid_sy)->composite.children[0], XmNbackground, ss->basic_color, NULL);

      n = 0;
      initial_value = fixup_angle(spectro_z_angle(ss));
      xstr = scale_label("z angle", initial_value, false);
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNtitleString, xstr); n++;
      XtSetArg(args[n], XmNshowValue, XmNONE); n++;
      XtSetArg(args[n], XmNvalue, initial_value); n++;
      XtSetArg(args[n], XmNmaximum, 360); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 48); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, oid_ay); n++;
      XtSetArg(args[n], XmNborderWidth, SCALE_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      oid_az = XtCreateManagedWidget("az", xmScaleWidgetClass, orientation_form, args, n);
      XtAddCallback(oid_az, XmNvalueChangedCallback, az_orientation_callback, NULL);
      XtAddCallback(oid_az, XmNdragCallback, az_orientation_callback, NULL);
      XmStringFree(xstr);

      XtVaSetValues(((XmScaleWidget)oid_az)->composite.children[0], XmNbackground, ss->basic_color, NULL);

      n = 0;
      initial_value = mus_iclamp(0, (int)(spectro_z_scale(ss) * 100), (int)(100 * SPECTRO_Z_SCALE_MAX));
      xstr = scale_label("z scale", initial_value, true);
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNshowValue, XmNONE); n++;
      XtSetArg(args[n], XmNdecimalPoints, 2); n++;
      XtSetArg(args[n], XmNmaximum, (int)(100 * SPECTRO_Z_SCALE_MAX)); n++;
      XtSetArg(args[n], XmNvalue, initial_value); n++;
      XtSetArg(args[n], XmNtitleString, xstr); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNleftPosition, 52); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, oid_sy); n++;
      XtSetArg(args[n], XmNborderWidth, SCALE_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      oid_sz = XtCreateManagedWidget("zs", xmScaleWidgetClass, orientation_form, args, n);
      XtAddCallback(oid_sz, XmNvalueChangedCallback, sz_orientation_callback, NULL);
      XtAddCallback(oid_sz, XmNdragCallback, sz_orientation_callback, NULL);
      XmStringFree(xstr);

      XtVaSetValues(((XmScaleWidget)oid_sz)->composite.children[0], XmNbackground, ss->basic_color, NULL);

      n = 0;
      initial_value = mus_iclamp(1, spectro_hop(ss), HOP_MAX);
      xstr = scale_label("hop", initial_value, false);
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNshowValue, XmNONE); n++;
      XtSetArg(args[n], XmNvalue, initial_value); n++;
      XtSetArg(args[n], XmNmaximum, HOP_MAX); n++;
      XtSetArg(args[n], XmNtitleString, xstr); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 48); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, oid_az); n++;
#if HAVE_GL
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
#else
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
#endif
      XtSetArg(args[n], XmNborderWidth, SCALE_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      oid_hop = XtCreateManagedWidget("hop", xmScaleWidgetClass, orientation_form, args, n);
      XtAddCallback(oid_hop, XmNvalueChangedCallback, hop_orientation_callback, NULL);
      XtAddCallback(oid_hop, XmNdragCallback, hop_orientation_callback, NULL);
      XmStringFree(xstr);

      XtVaSetValues(((XmScaleWidget)oid_hop)->composite.children[0], XmNbackground, ss->basic_color, NULL);

#if HAVE_GL
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      XtSetArg(args[n], XmNset, with_gl(ss)); n++;
      glstr = XmStringCreateLocalized((char *)"use OpenGL");
      XtSetArg(args[n], XmNlabelString, glstr); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, oid_hop); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      oid_glbutton = make_togglebutton_widget("use OpenGL", orientation_form, args, n);
      XtAddCallback(oid_glbutton, XmNvalueChangedCallback, with_gl_callback, NULL);
      XmStringFree(glstr);
#endif

      if (color_scale(ss) != 1.0)
	reflect_color_scale(color_scale(ss));

      map_over_children(ccd_dialog, set_main_color_of_widget);
      set_dialog_widget(COLOR_ORIENTATION_DIALOG, ccd_dialog);
      if (managed) XtManageChild(ccd_dialog);
    }
  else 
    {
      if (managed)
	{
	  if (!XtIsManaged(ccd_dialog)) XtManageChild(ccd_dialog);
	  raise_dialog(ccd_dialog);
	}
    }
  return(ccd_dialog);
}


static void view_color_orientation_callback(Widget w, XtPointer context, XtPointer info)
{
  make_color_orientation_dialog(true);
}


bool color_orientation_dialog_is_active(void)
{
  return((ccd_dialog) && (XtIsManaged(ccd_dialog)));
}



#define HELP_ROWS 10
#define HELP_XREFS 8
#define HELP_COLUMNS 72
/* these set the initial size of the help dialog text area */

static Widget help_dialog = NULL;
static Widget help_text = NULL;
static char *original_help_text = NULL;
static with_word_wrap_t outer_with_wrap = WITHOUT_WORD_WRAP;
static const char **help_urls = NULL; /* shouldn't this be static char* const char*? */

static int old_help_text_width = 0; 

static void help_expose(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  int curwid;
  curwid = widget_width(help_text);
  if (old_help_text_width == 0)
    old_help_text_width = curwid;
  else
    {
      if ((outer_with_wrap == WITH_WORD_WRAP) && 
	  (abs(curwid - old_help_text_width) > 10))
	{
	  char *cur_help_str, *new_help_str = NULL;
	  cur_help_str = XmTextGetString(help_text);
	  new_help_str = word_wrap(original_help_text, curwid);
	  XmTextSetString(help_text, new_help_str);
	  if (new_help_str) free(new_help_str);
	  if (cur_help_str) XtFree(cur_help_str);
	  old_help_text_width = curwid;
	}
    }
}


static XmString parse_crossref(const char *xref)
{
  XmString xs = NULL, tmp;
  int i, len, start = 0, j, k;
  char *str;
  /* crossref has text for scrolled list entry, but url is in '{}'.  It is displayed via the texts rendition */
  len = strlen(xref);
  for (i = 0; i < len; i++)
    {
      if (xref[i] == '{')
	{
	  if (i > 0)
	    {
	      str = (char *)calloc(i - start + 1, sizeof(char));
	      for (k = 0, j = start; j < i; k++, j++) str[k] = xref[j];
	      tmp = XmStringGenerate(str, NULL, XmCHARSET_TEXT, (char *)"normal_text");
	      free(str);
	      if (xs) 
		xs = XmStringConcatAndFree(xs, tmp);
	      else xs = tmp;
	    }
	  start = i + 1;
	}
      else
	{
	  if (xref[i] == '}')
	    {
	      str = (char *)calloc(i - start + 1, sizeof(char));
	      for (k = 0, j = start; j < i; k++, j++) str[k] = xref[j];
	      if (xs)
		xs = XmStringConcatAndFree(xs, XmStringGenerate(str, NULL, XmCHARSET_TEXT, (char *)"url_text"));
	      else xs = XmStringGenerate(str, NULL, XmCHARSET_TEXT, (char *)"url_text");
	      free(str);
	      start = i + 1;
	    }
	}
    }
  if (start < len)
    {
      str = (char *)calloc(len - start + 1, sizeof(char));
      for (k = 0, j = start; j < len; k++, j++) str[k] = xref[j];
      if (xs)
	xs = XmStringConcatAndFree(xs, XmStringGenerate(str, NULL, XmCHARSET_TEXT, (char *)"normal_text"));
      else xs = XmStringGenerate(str, NULL, XmCHARSET_TEXT, (char *)"normal_text");
      free(str);
    }
  return(xs);
}


static char *find_highlighted_text(XmString xs)
{
  /* search xs for text in "url_text" rendition, returning first such portion */
  XtPointer text;
  bool in_red_text = false;
  uint32_t len;
  char *result;
  XmStringComponentType type;
  XmStringContext ctx;

  XmStringInitContext(&ctx, xs);

  while ((type = XmStringGetNextTriple(ctx, &len, &text)) != XmSTRING_COMPONENT_END)
    {
      switch (type)
	{
	case XmSTRING_COMPONENT_RENDITION_BEGIN: 
	  in_red_text = mus_strcmp((char *)text, "url_text");
	  break;

	case XmSTRING_COMPONENT_RENDITION_END:
	  in_red_text = false;
	  break;

	case XmSTRING_COMPONENT_TEXT:
	  if (in_red_text) 
	    {
	      result = mus_strdup((char *)text);
	      XtFree((char *)text);
	      XmStringFreeContext(ctx);
	      return(result);
	    }
	}

      /* this from the Motif docs, though it looks odd to me */
      if (text) XtFree((char *)text);
      text = NULL;
    }

  XmStringFreeContext(ctx);
  return(NULL);
}


static Widget related_items = NULL;

static char *help_completer(widget_t w, const char *text, void *data) 
{
  return(expression_completer(w, text, data));
  /* might want to look at help topics too */
} 


static bool new_help(const char *pattern, bool complain)
{
  const char *url = NULL;
  const char **xrefs;

  url = snd_url(pattern);
  if (url)
    {
      /* given name, find doc string, if any */
      Xen xstr;
      xstr = g_snd_help(C_string_to_Xen_string(pattern), 0);
      if (Xen_is_string(xstr))
	{
	  int gc_loc;
	  gc_loc = snd_protect(xstr);
	  xrefs = help_name_to_xrefs(pattern);
	  snd_help_with_xrefs(pattern, Xen_string_to_C_string(xstr), WITH_WORD_WRAP, xrefs, NULL);
	  snd_unprotect_at(gc_loc);
	  if (xrefs) free(xrefs);
	  return(true);
	}
      url_to_html_viewer(url);
      return(true);
    }

  if ((!(snd_topic_help(pattern))) && (complain))
    {
      xrefs = help_name_to_xrefs(pattern);
      if (xrefs)
	{
	  snd_help_with_xrefs(pattern, "(no help found)", WITH_WORD_WRAP, xrefs, NULL);
	  free(xrefs);
	  return(true);
	}
      else snd_help_with_xrefs(pattern, "(no help found)", WITH_WORD_WRAP, NULL, NULL);
    }

  return(false);
}


static void help_browse_callback(Widget w, XtPointer context, XtPointer info) 
{
  /* single-click to select item in "related items" list */
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)info;
  if ((help_urls) && (help_urls[cbs->item_position - 1]))
    url_to_html_viewer(help_urls[cbs->item_position - 1]);
  else
    {
      char *red_text;
      red_text = find_highlighted_text(cbs->item);
      if (red_text)
	{
	  name_to_html_viewer(red_text);
	  free(red_text);
	}
      else
	{
	  red_text = (char *)XmStringUnparse(cbs->item, NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
	  if (red_text) 
	    {
	      new_help(red_text, true);
	      XtFree(red_text);
	    }
	}
    }
}


static void help_double_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  /* double-click item in "related items" list */
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)info;
  if ((help_urls) && (help_urls[cbs->item_position - 1]))
    url_to_html_viewer(help_urls[cbs->item_position - 1]);
  else
    {
      char *red_text;
      red_text = find_highlighted_text(cbs->selected_items[0]);
      if (red_text)
	{
	  name_to_html_viewer(red_text);
	  free(red_text);
	}
      else
	{
	  red_text = (char *)XmStringUnparse(cbs->selected_items[0], NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
	  if (red_text)
	    {
	      name_to_html_viewer(red_text);
	      XtFree(red_text);
	    }
	}
    }
}


static Widget help_search = NULL;

static void help_quit_callback(Widget w, XtPointer context, XtPointer info) 
{
  /* this focus widget check is actually needed! */
  if (XmGetFocusWidget(help_dialog) == XmMessageBoxGetChild(help_dialog, XmDIALOG_CANCEL_BUTTON))
    XtUnmanageChild(help_dialog);
}


static void text_release_callback(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  char *help_str;
  help_str = XmTextGetSelection(w);
  if (help_str)
    {
      int i, len;
      bool one_word = true;
      len = mus_strlen(help_str);
      for (i = 0; i < len; i++)
	if (isspace(help_str[i]))
	  {
	    one_word = false;
	    break;
	  }
      if (one_word) new_help(help_str, false);
      XtFree(help_str);
    }
}


static void help_search_callback(Widget w, XtPointer context, XtPointer info)
{
  char *pattern = NULL;
  pattern = XmTextFieldGetString(w);
  if (new_help(pattern, true))
    XmTextFieldSetString(w, (char *)"");
  if (pattern) XtFree(pattern);
}


static XmRendition texts[2];

static void create_help_monolog(void)
{
  /* create scrollable but not editable text window */
  Arg args[20];
  int n;
  XmString titlestr, go_away;
  Widget holder, xref_label; /* documentation says this isn't needed, but it is */
  Widget frame, label, inner_holder, sep, parent;
  XmRenderTable rs = NULL;

  titlestr = XmStringCreateLocalized((char *)I_HELP);
  go_away = XmStringCreateLocalized((char *)I_GO_AWAY);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNdialogTitle, titlestr); n++;
  /* this window should be resizable by the user (i.e. have the resize bars), but not resize itself */
  XtSetArg(args[n], XmNautoUnmanage, false); n++;
  XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
  XtSetArg(args[n], XmNnoResize, false); n++;
  XtSetArg(args[n], XmNtransient, false); n++;
  XtSetArg(args[n], XmNcancelLabelString, go_away); n++;

  help_dialog = XmCreateMessageDialog(main_pane(ss), (char *)"snd-help", args, n);
  XtAddEventHandler(help_dialog, ExposureMask, false, help_expose, NULL);

  XtAddCallback(help_dialog, XmNcancelCallback, help_quit_callback, NULL);

  XtUnmanageChild(XmMessageBoxGetChild(help_dialog, XmDIALOG_OK_BUTTON));
  XtUnmanageChild(XmMessageBoxGetChild(help_dialog, XmDIALOG_HELP_BUTTON));
  XtUnmanageChild(XmMessageBoxGetChild(help_dialog, XmDIALOG_SYMBOL_LABEL));

  XtVaSetValues(XmMessageBoxGetChild(help_dialog, XmDIALOG_MESSAGE_LABEL), XmNbackground, ss->highlight_color, NULL);

  XmStringFree(titlestr);
  holder = XtCreateManagedWidget("holder", xmFormWidgetClass, help_dialog, NULL, 0);

  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
  XtSetArg(args[n], XmNeditable, false); n++;
  XtSetArg(args[n], XmNcolumns, HELP_COLUMNS); n++;
  XtSetArg(args[n], XmNrows, HELP_ROWS); n++;
  XtSetArg(args[n], XmNforeground, ss->black); n++; /* needed if color allocation fails completely */
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  help_text = XmCreateScrolledText(holder, (char *)"help-text", args, n);
  XtAddEventHandler(help_text, ButtonReleaseMask, false, text_release_callback, NULL);
  XtManageChild(help_text);

  /* to display the url-related portion of the text in red, we need a rendition for it in the rendertable */
  /* try to find the current default render table. */
  parent = help_text;
  while ((parent) && (!rs))
    {
      XtVaGetValues(parent, XmNrenderTable, &rs, NULL);
      parent = XtParent(parent);
    }
  n = 0;
  if (!rs)
    {
      /* failed to find a rendertable to specialize, so we need an explicit font */
      XtSetArg(args[n], XmNfontName, listener_font(ss)); n++;
      XtSetArg(args[n], XmNfontType, XmFONT_IS_FONT); n++; 
      XtSetArg(args[n], XmNloadModel, XmLOAD_IMMEDIATE); n++;
    }
  XtSetArg(args[n], XmNrenditionBackground, ss->white); n++;
  XtSetArg(args[n], XmNrenditionForeground, ss->red); n++;
  texts[0] = XmRenditionCreate(help_text, (char *)"url_text", args, n);
  XtSetArg(args[n - 1], XmNrenditionForeground, ss->black); 
  texts[1] = XmRenditionCreate(help_text, (char *)"normal_text", args, n);
  rs = XmRenderTableCopy(XmRenderTableAddRenditions(rs, texts, 2, XmMERGE_NEW), NULL, 0);
  /*
   * valgrind says this data is used later
   * XmRenditionFree(texts[0]);
   * XmRenditionFree(texts[1]);
  */

  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, XtParent(help_text)); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNheight, 6); n++;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
  XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
  sep = XtCreateManagedWidget("sep", xmSeparatorWidgetClass, holder, args, n);
  
  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNheight, 24); n++;
  /* XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++; */
  label = XtCreateManagedWidget("help topic:", xmLabelWidgetClass, holder, args, n);
  
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, label); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  help_search = make_textfield_widget("help-search", holder, args, n, ACTIVATABLE, add_completer_func(help_completer, NULL));
  XtAddCallback(help_search, XmNactivateCallback, help_search_callback, NULL);
  
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, sep); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNbottomWidget, help_search); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNshadowThickness, 4); n++;
  frame = XtCreateManagedWidget("frame", xmFrameWidgetClass, holder, args, n);
  
  inner_holder = XtCreateManagedWidget("inner-holder", xmFormWidgetClass, frame, NULL, 0);
  
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
  xref_label = XtCreateManagedWidget("related topics:", xmLabelWidgetClass, inner_holder, args, n);
  
  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, xref_label); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  
  /* in operton-based solaris 10 this (next) line causes an X server error (with no stack...):
   *   complaint is X_ChangeGC got an invalid font.
   *   Do I need a configure test program for this?  Is there some other way to force the render table to take effect?
   */
#if (!HAVE_SUN) || (!MUS_LITTLE_ENDIAN)
  XtSetArg(args[n], XmNfontList, 0); n++; /* needed or new rendertable doesn't take effect! */
                                          /* also, 0, not NULL so types match */
  XtSetArg(args[n], XmNrenderTable, rs); n++;
#endif

  XtSetArg(args[n], XmNvisibleItemCount, HELP_XREFS); n++; /* appears to be a no-op */
  XtSetArg(args[n], XmNheight, 150); n++;

  XtSetArg(args[n], XmNscrollBarDisplayPolicy, XmAS_NEEDED); n++;
  related_items = XmCreateScrolledList(inner_holder, (char *)"help-list", args, n);
  XtManageChild(related_items);
  XtAddCallback(related_items, XmNbrowseSelectionCallback, help_browse_callback, NULL);
  XtAddCallback(related_items, XmNdefaultActionCallback, help_double_click_callback, NULL);
  
  XtManageChild(help_dialog);
  
  map_over_children(help_dialog, set_main_color_of_widget);
  XtVaSetValues(help_text, XmNbackground, ss->white, XmNforeground, ss->black, NULL);
  XtVaSetValues(related_items, XmNbackground, ss->highlight_color, XmNforeground, ss->black, NULL);
  XtVaSetValues(xref_label, XmNbackground, ss->highlight_color, XmNforeground, ss->black, NULL);

  XtVaSetValues(XmMessageBoxGetChild(help_dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor, ss->selection_color, NULL);
  XtVaSetValues(XmMessageBoxGetChild(help_dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color, NULL);

  set_dialog_widget(HELP_DIALOG, help_dialog);
}


int help_text_width(const char *txt, int start, int end)
{
#if 0
  /* this is full of problems... -- adding renditions below makes everything else flakey */
  if ((help_text) && (end > start))
    {
      char *msg;
      int i, j;
      XmString s1;
      Dimension text_wid = 0;
      XmFontList fonts;
      XtVaGetValues(help_text, XmNfontList, &fonts, NULL);
      msg = (char *)calloc(end - start + 1, sizeof(char));
      for (i = start, j = 0; i < end; i++, j++) msg[j] = txt[i];
      s1 = XmStringCreateLocalized(msg);
      text_wid = XmStringWidth(fonts, s1);
      XmStringFree(s1);
      free(msg);
      return((int)text_wid);
    }
#endif
  return((end - start) * 8);
}


Widget snd_help(const char *subject, const char *helpstr, with_word_wrap_t with_wrap)
{
  /* place help string in scrollable help window */
  /* if window is already active, add this help at the top and reposition */
  XmString xstr1;

  outer_with_wrap = with_wrap;
  if (!(help_dialog)) 
    create_help_monolog(); 
  else raise_dialog(help_dialog);

  xstr1 = XmStringCreateLocalized((char *)subject);
  XtVaSetValues(help_dialog, XmNmessageString, xstr1, NULL);
  original_help_text = (char *)helpstr;

  if (with_wrap == WITH_WORD_WRAP)
    {
      char *new_help_str = NULL;
      new_help_str = word_wrap(helpstr, widget_width(help_text));
      XmTextSetString(help_text, new_help_str);
      if (new_help_str) free(new_help_str);
    }
  else XmTextSetString(help_text, (char *)helpstr);

  if (!XtIsManaged(help_dialog)) 
    XtManageChild(help_dialog);

  XmStringFree(xstr1);
  XtVaSetValues(related_items, XmNitems, NULL, XmNitemCount, 0, NULL);
  return(help_dialog);
}


Widget snd_help_with_xrefs(const char *subject, const char *helpstr, with_word_wrap_t with_wrap, const char **xrefs, const char **urls)
{
  Widget w;
  w = snd_help(subject, helpstr, with_wrap);
  help_urls = urls; /* can't associate the url with the help item in any "natural" way in Motif (no user-data per item) */
  if (xrefs)
    {
      int i, len;

      for (i = 0; ; i++)
	if (!xrefs[i])
	  {
	    len = i;
	    break;
	  }

      if (len > 0)
	{
	  XmString *strs;
	  strs = (XmString *)calloc(len, sizeof(XmString));
	  
	  for (i = 0; i < len; i++)
	    strs[i] = parse_crossref((const char *)(xrefs[i]));
	  XtVaSetValues(related_items, XmNitems, strs, XmNitemCount, len, NULL);

	  for (i = 0; i < len; i++)
	    XmStringFree(strs[i]);
	  free(strs);
	}
    }
  return(w);
}


void snd_help_append(const char *text)
{
  if (help_text) 
    XmTextInsert(help_text,
		 XmTextGetLastPosition(help_text), 
		 (char *)text);
}


void snd_help_back_to_top(void)
{
  if (help_text) XmTextShowPosition(help_text, 0);
}


static Widget edit_find_dialog, edit_find_text, cancelB, edit_find_label, previousB;
static Widget find_error_frame = NULL, find_error_label = NULL;
static chan_info *find_channel = NULL; /* sigh */


static void clear_find_error(void);
static void edit_find_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  clear_find_error();
}


static void clear_find_error(void)
{
  if ((find_error_frame) && (XtIsManaged(find_error_frame)))
    XtUnmanageChild(find_error_frame);
  XtRemoveCallback(edit_find_text, XmNmodifyVerifyCallback, edit_find_modify_callback, NULL);
  /* squeezing out the error label room here moves the text widget, which is irritating since it
   *   means the text we're typing gets lost 
   */
}


void find_dialog_stop_label(bool show_stop)
{
  XmString s1;
  if (show_stop)
    s1 = XmStringCreateLocalized((char *)I_STOP);
  else s1 = XmStringCreateLocalized((char *)I_GO_AWAY);
  XtVaSetValues(cancelB, XmNlabelString, s1, NULL);
  XmStringFree(s1);
}


void errors_to_find_text(const char *msg, void *data)
{
  Dimension find_height = 0;
  int lines = 0;
  XmString label;
  find_dialog_set_label("error");
  label = multi_line_label(msg, &lines);
  XtVaSetValues(find_error_label, 
		XmNlabelString, label, 
		XmNheight, lines * 20,
		NULL);
  XtVaSetValues(find_error_frame, XmNheight, lines * 20, NULL);
  XtVaGetValues(edit_find_dialog, XmNheight, &find_height, NULL);
  if (find_height < (lines * 20 + 140))
    {
      XtUnmanageChild(edit_find_dialog);
      XtVaSetValues(edit_find_dialog, XmNheight, 140 + 20 * lines, NULL);
      XtManageChild(edit_find_dialog);
    }
  XmStringFree(label);
  XtManageChild(find_error_frame);
  XtAddCallback(edit_find_text, XmNmodifyVerifyCallback, edit_find_modify_callback, NULL);
}


void stop_search_if_error(const char *msg, void *data)
{
  errors_to_find_text(msg, data);
  ss->stopped_explicitly = true; /* should be noticed in global_search in snd-find.c */
}


static void edit_find_help_callback(Widget w, XtPointer context, XtPointer info) 
{
  find_dialog_help();
} 


static void edit_find_ok_callback(read_direction_t direction, Widget w, XtPointer context, XtPointer info)
{ 
  char *str;
  str = XmTextGetString(edit_find_text);
#if HAVE_EXTENSION_LANGUAGE
  find_dialog_find(str, direction, find_channel);
#endif
  if (str) free(str);
}


void find_dialog_set_label(const char *str) 
{
  if (edit_find_label) 
    set_label(edit_find_label, str);
}


static void edit_find_next_callback(Widget w, XtPointer context, XtPointer info) 
{
  edit_find_ok_callback(READ_FORWARD, w, context, info);
}


static void edit_find_previous_callback(Widget w, XtPointer context, XtPointer info) 
{
  edit_find_ok_callback(READ_BACKWARD, w, context, info);
}


static void find_dialog_close(Widget w, XtPointer context, XtPointer info)
{
  clear_find_error();
}


static void edit_find_cancel_callback(Widget w, XtPointer context, XtPointer info)
{
  if (ss->checking_explicitly)
    ss->stopped_explicitly = true;
  else 
    {
      XtUnmanageChild(edit_find_dialog);
      clear_find_error();
    }
} 


static void make_edit_find_dialog(bool managed, chan_info *cp)
{
  find_channel = cp;

  if (!edit_find_dialog)
    {
      Widget dl, rc;
      Arg args[20];
      int n;
      XmString go_away, next;

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;

      go_away = XmStringCreateLocalized((char *)I_GO_AWAY);
      next = XmStringCreateLocalized((char *)I_NEXT);

      XtSetArg(args[n], XmNokLabelString, next); n++;
      XtSetArg(args[n], XmNcancelLabelString, go_away); n++;
      XtSetArg(args[n], XmNautoUnmanage, false); n++;
      XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
      XtSetArg(args[n], XmNnoResize, false); n++;
      XtSetArg(args[n], XmNtransient, false); n++;
      edit_find_dialog = XmCreateMessageDialog(main_shell(ss), (char *)I_FIND, args, n);
      
      XmStringFree(go_away);
      XmStringFree(next);
      
      XtUnmanageChild(XmMessageBoxGetChild(edit_find_dialog, XmDIALOG_SYMBOL_LABEL));
      XtUnmanageChild(XmMessageBoxGetChild(edit_find_dialog, XmDIALOG_MESSAGE_LABEL));
      
      XtAddCallback(edit_find_dialog, XmNhelpCallback, edit_find_help_callback, NULL);
      XtAddCallback(edit_find_dialog, XmNcancelCallback, edit_find_cancel_callback, NULL);
      XtAddCallback(edit_find_dialog, XmNokCallback, edit_find_next_callback, NULL);
      
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      previousB = XtCreateManagedWidget(I_PREVIOUS, xmPushButtonGadgetClass, edit_find_dialog, args, n);
      XtAddCallback(previousB, XmNactivateCallback, edit_find_previous_callback, NULL);
      
      rc = XtCreateManagedWidget("row", xmFormWidgetClass, edit_find_dialog, NULL, 0);
      
      n = 0;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      dl = XtCreateManagedWidget(I_find, xmLabelWidgetClass, rc, args, n);
      
      n = 0;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, dl); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      edit_find_text = make_textfield_widget("text", rc, args, n, ACTIVATABLE, add_completer_func(expression_completer, NULL));
      
      n = 0;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, edit_find_text); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNmarginHeight, 10); n++;
      edit_find_label = XtCreateManagedWidget("    ", xmLabelWidgetClass, rc, args, n);
      
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, edit_find_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNallowResize, true); n++;
      XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_IN); n++;
      XtSetArg(args[n], XmNshadowThickness, 2); n++;
      find_error_frame = XtCreateManagedWidget("find-error-frame", xmFrameWidgetClass, rc, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      find_error_label = XtCreateManagedWidget("", xmLabelWidgetClass, find_error_frame, args, n);
      
      map_over_children(edit_find_dialog, set_main_color_of_widget);
      XtVaSetValues(XmMessageBoxGetChild(edit_find_dialog, XmDIALOG_OK_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(edit_find_dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(edit_find_dialog, XmDIALOG_HELP_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(edit_find_dialog, XmDIALOG_OK_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(edit_find_dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(edit_find_dialog, XmDIALOG_HELP_BUTTON), XmNbackground, ss->highlight_color, NULL);

      cancelB = XmMessageBoxGetChild(edit_find_dialog, XmDIALOG_CANCEL_BUTTON);
      set_dialog_widget(FIND_DIALOG, edit_find_dialog);

      XtUnmanageChild(find_error_frame);
      if (managed) XtManageChild(edit_find_dialog);
      {
	Atom wm_delete_window;
	wm_delete_window = XmInternAtom(main_display(ss), (char *)"WM_DELETE_WINDOW", false);
	XmAddWMProtocolCallback(XtParent(edit_find_dialog), wm_delete_window, find_dialog_close, NULL);
      }
    }
  else
    {
      if (managed)
	{
	  if (!XtIsManaged(edit_find_dialog)) XtManageChild(edit_find_dialog);
	  raise_dialog(edit_find_dialog);
	}
    }

  {
    XmString titlestr;
    if (cp)
      titlestr = XmStringCreateLocalized(mus_format("%s in %s channel %d", (char *)I_FIND, cp->sound->short_filename, cp->chan));
    else titlestr = XmStringCreateLocalized((char *)I_FIND);
    XtVaSetValues(edit_find_dialog, XmNdialogTitle, titlestr, NULL);
    XmStringFree(titlestr);
  }
}


static void edit_find_callback(Widget w, XtPointer context, XtPointer info)
{
  make_edit_find_dialog(true, NULL);
}


void find_dialog(chan_info *cp)
{
  make_edit_find_dialog(true, cp);
}


bool find_dialog_is_active(void)
{
  return((edit_find_dialog) && (XtIsManaged(edit_find_dialog)));
}


void save_find_dialog_state(FILE *fd)
{
  if (find_dialog_is_active())
    {
      char *text = NULL;
      text = XmTextGetString(edit_find_text);
      if ((text) && (*text))
	{
#if HAVE_SCHEME
	  fprintf(fd, "(%s #t \"%s\")\n", S_find_dialog, text);
#endif
#if HAVE_RUBY
	  fprintf(fd, "%s(true, \"%s\")\n", to_proc_name(S_find_dialog), text);
#endif
#if HAVE_FORTH
	  fprintf(fd, "#t \"%s\" %s drop\n", text, S_find_dialog);
#endif
	  XtFree(text);
	}
      else 
	{
#if HAVE_SCHEME
	  if (ss->search_expr)
	    fprintf(fd, "(%s #t \"%s\")\n", S_find_dialog, ss->search_expr);
	  else fprintf(fd, "(%s #t)\n", S_find_dialog);
#endif
#if HAVE_RUBY
	  if (ss->search_expr)
	    fprintf(fd, "%s(true, \"%s\")\n", to_proc_name(S_find_dialog), ss->search_expr);
	  else fprintf(fd, "%s(true)\n", to_proc_name(S_find_dialog));
#endif
#if HAVE_FORTH
	  if (ss->search_expr)
	    fprintf(fd, "#t \"%s\" %s drop\n", ss->search_expr, S_find_dialog);
	  else fprintf(fd, "#t %s drop\n", S_find_dialog);
#endif
	}
    }
}


static Xen g_find_dialog(Xen managed, Xen text)
{
  #define H_find_dialog "(" S_find_dialog " :optional managed text): create and activate the Edit:Find dialog, return the dialog widget. \
If 'text' is included, it is preloaded into the find dialog text widget."

  Xen_check_type(Xen_is_boolean_or_unbound(managed), managed, 1, S_find_dialog, "a boolean");
  Xen_check_type(Xen_is_string_or_unbound(text), text, 2, S_find_dialog, "a string");

  make_edit_find_dialog(Xen_boolean_to_C_bool(managed), NULL);
  if ((edit_find_text) && (Xen_is_string(text)))
    XmTextSetString(edit_find_text, (char *)Xen_string_to_C_string(text));

  return(Xen_wrap_widget(edit_find_dialog));
}


static Xen g_find_dialog_widgets(void)
{
  if (edit_find_dialog)
    return(Xen_cons(Xen_wrap_widget(edit_find_dialog),
	     Xen_cons(Xen_wrap_widget(edit_find_text),
  	       Xen_cons(Xen_wrap_widget(XmMessageBoxGetChild(edit_find_dialog, XmDIALOG_OK_BUTTON)),           /* find next */
		 Xen_cons(Xen_wrap_widget(previousB),                                                          /* find previous */
		   Xen_cons(Xen_wrap_widget(XmMessageBoxGetChild(edit_find_dialog, XmDIALOG_CANCEL_BUTTON)),   /* go away */
		     Xen_empty_list))))));
  return(Xen_empty_list);
}



#define NAME_COLUMNS 8

/* ---------------- mix dialog ---------------- */

static Widget mix_dialog = NULL;
static int mix_dialog_id = INVALID_MIX_ID, old_mix_dialog_id = INVALID_MIX_ID;
static env *dialog_env = NULL;

static bool dragging = false;
static int edpos_before_drag;
static with_hook_t hookable_before_drag;
static mus_long_t drag_beg = 0, drag_end = 0;

static void start_dragging(int mix_id) 
{
  chan_info *cp;
  cp = mix_chan_info_from_id(mix_id);
  edpos_before_drag = cp->edit_ctr;
  hookable_before_drag = cp->hookable;
  cp->hookable = WITHOUT_HOOK;
  dragging = true;
  drag_beg = mix_position_from_id(mix_id);
  drag_end = drag_beg + mix_length_from_id(mix_id);
  start_dragging_syncd_mixes(mix_id);
}


static void keep_dragging(int mix_id) 
{
  chan_info *cp;
  cp = mix_chan_info_from_id(mix_id);
  cp->edit_ctr = edpos_before_drag;
  keep_dragging_syncd_mixes(mix_id);
}


static void stop_dragging(int mix_id) 
{
  chan_info *cp;
  cp = mix_chan_info_from_id(mix_id);
  undo_edit(cp, 1);
  cp->hookable = hookable_before_drag;
  dragging = false;
  stop_dragging_syncd_mixes(mix_id);
}


/* -------- speed -------- */

static Widget w_speed_number, w_speed_label, w_speed;
static speed_style_t xmix_speed_control_style = SPEED_CONTROL_AS_FLOAT;

static int speed_to_scrollbar(mus_float_t minval, mus_float_t val, mus_float_t maxval)
{
  if (val <= minval) return(0);
  if (val >= maxval) return((int)(0.9 * SCROLLBAR_MAX));
  return(snd_round(0.9 * SCROLLBAR_MAX * ((log(val) - log(minval)) / (log(maxval) - log(minval)))));
}


static mus_float_t set_speed_label(Widget speed_number, int ival)
{
  char speed_number_buffer[6];
  mus_float_t speed;
  speed = speed_changed(exp((ival * (log(speed_control_max(ss)) - log(speed_control_min(ss))) / (0.9 * SCROLLBAR_MAX)) + log(speed_control_min(ss))),
			speed_number_buffer,
			xmix_speed_control_style,
			speed_control_tones(ss),
			6);
  set_label(speed_number, speed_number_buffer);
  return(speed);
}


static void mix_speed_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  char speed_number_buffer[6];
  mus_float_t speed;

  if (!(mix_is_active(mix_dialog_id))) return;
  speed = speed_changed(1.0,
			speed_number_buffer,
			xmix_speed_control_style,
			speed_control_tones(ss),
			6);

  drag_beg = mix_position_from_id(mix_dialog_id);
  drag_end = drag_beg + mix_length_from_id(mix_dialog_id);

  mix_set_speed_edit(mix_dialog_id, speed);
  syncd_mix_set_speed(mix_dialog_id, speed);
  after_mix_edit(mix_dialog_id);
  set_label(w_speed_number, speed_number_buffer);
  XtVaSetValues(w_speed, XmNvalue, speed_to_scrollbar(speed_control_min(ss), 1.0, speed_control_max(ss)), NULL);
}


static void mix_speed_label_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  char speed_number_buffer[6];
  if (!(mix_is_active(mix_dialog_id))) return;
  switch (xmix_speed_control_style)
    {
    default:
    case SPEED_CONTROL_AS_FLOAT:    xmix_speed_control_style = SPEED_CONTROL_AS_RATIO;    break;
    case SPEED_CONTROL_AS_RATIO:    xmix_speed_control_style = SPEED_CONTROL_AS_SEMITONE; break;
    case SPEED_CONTROL_AS_SEMITONE: xmix_speed_control_style = SPEED_CONTROL_AS_FLOAT;    break;
    }
  speed_changed(mix_speed_from_id(mix_dialog_id),
		speed_number_buffer,
		xmix_speed_control_style,
		speed_control_tones(ss),
		6);
  set_label(w_speed_number, speed_number_buffer);
}


static void mix_speed_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  int ival;
  mus_float_t speed;
  mus_long_t beg, end;

  if (!(mix_is_active(mix_dialog_id))) return;

  ival = ((XmScrollBarCallbackStruct *)info)->value;
  if (!dragging) 
    start_dragging(mix_dialog_id);
  else keep_dragging(mix_dialog_id);

  speed = set_speed_label(w_speed_number, ival);
  mix_set_speed_edit(mix_dialog_id, speed);

  beg = mix_position_from_id(mix_dialog_id);
  end = beg + mix_length_from_id(mix_dialog_id);
  if (drag_beg > beg) drag_beg = beg;
  if (drag_end < end) drag_end = end;

  mix_display_during_drag(mix_dialog_id, drag_beg, drag_end);
  syncd_mix_set_speed(mix_dialog_id, speed);
}


static void mix_speed_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScrollBarCallbackStruct *cb = (XmScrollBarCallbackStruct *)info;
  mus_float_t speed;

  if (!(mix_is_active(mix_dialog_id))) return;
  if (dragging)
    stop_dragging(mix_dialog_id);

  speed = set_speed_label(w_speed_number, cb->value);
  mix_set_speed_edit(mix_dialog_id, speed);
  syncd_mix_set_speed(mix_dialog_id, speed);
  after_mix_edit(mix_dialog_id);
  after_syncd_mix_edit(mix_dialog_id);
}


/* -------- amp -------- */

static Widget w_amp_number, w_amp_label, w_amp;

static mus_float_t scrollbar_to_amp(int val)
{
  if (val <= 0) 
    return(amp_control_min(ss));
  if (val >= (0.9 * SCROLLBAR_MAX)) 
    return(amp_control_max(ss));
  if (val > (0.5 * 0.9 * SCROLLBAR_MAX))
    return((((val / (0.5 * 0.9 * SCROLLBAR_MAX)) - 1.0) * (amp_control_max(ss) - 1.0)) + 1.0);
  else return((val * (1.0 - amp_control_min(ss)) / (0.5 * 0.9 * SCROLLBAR_MAX)) + amp_control_min(ss));
}


static int amp_to_scrollbar(Widget amp_number, mus_float_t amp)
{
  char sfs[6];
  snprintf(sfs, 6, "%.2f", amp);
  set_label(amp_number, sfs);
  return(amp_to_scroll(amp_control_min(ss), amp, amp_control_max(ss)));
}


static void change_mix_amp(int mix_id, mus_float_t val)
{
  char sfs[6];
  mix_set_amp_edit(mix_id, val);
  syncd_mix_set_amp(mix_id, val);
  snprintf(sfs, 6, "%.2f", val);
  set_label(w_amp_number, sfs);
}


static void mix_amp_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  if (!(mix_is_active(mix_dialog_id))) return;
  change_mix_amp(mix_dialog_id, 1.0);
  after_mix_edit(mix_dialog_id);
  XtVaSetValues(w_amp, XmNvalue, amp_to_scroll(amp_control_min(ss), 1.0, amp_control_max(ss)), NULL);
}


static void mix_amp_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  int ival;
  if (!(mix_is_active(mix_dialog_id))) return;
  ival = ((XmScrollBarCallbackStruct *)info)->value;
  if (!dragging) 
    start_dragging(mix_dialog_id);
  else keep_dragging(mix_dialog_id);
  change_mix_amp(mix_dialog_id, scrollbar_to_amp(ival));
  mix_display_during_drag(mix_dialog_id, drag_beg, drag_end);
}


static void mix_amp_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  int ival;
  if (!(mix_is_active(mix_dialog_id))) return;
  ival = ((XmScrollBarCallbackStruct *)info)->value;
  if (dragging)
    stop_dragging(mix_dialog_id);
  change_mix_amp(mix_dialog_id, scrollbar_to_amp(ival));
  after_mix_edit(mix_dialog_id);
  after_syncd_mix_edit(mix_dialog_id);
}


/* -------- amp-env -------- */

static Widget w_env_frame, w_env;
static graphics_context *ax = NULL;
static GC cur_gc;
static env_editor *spf = NULL;
static bool with_mix_background_wave = false;

static void show_mix_background_wave(int mix_id)
{
  int pts;
  bool two_sided = false;
  if (!spf) return;
  pts = prepare_mix_dialog_waveform(mix_id, spf->axis, &two_sided);
  if (pts > 0)
    {
      XSetForeground(main_display(ss), ax->gc, ss->enved_waveform_color);
      if (two_sided)
	draw_both_grf_points(1, ax, pts, GRAPH_LINES);
      else draw_grf_points(1, ax, pts, spf->axis, ungrf_y(spf->axis, 0.0), GRAPH_LINES);
      XSetForeground(main_display(ss), ax->gc, ss->black);
    }
}


static void mix_amp_env_resize(Widget w, XtPointer context, XtPointer info) 
{
  env *cur_env;
  if (!(mix_is_active(mix_dialog_id))) return;

  if (!ax)
    {
      XGCValues gv;
      gv.function = GXcopy;
      XtVaGetValues(w_env, XmNbackground, &gv.background, XmNforeground, &gv.foreground, NULL);
      cur_gc = XtGetGC(w_env, GCForeground | GCFunction, &gv);
      ax = (graphics_context *)calloc(1, sizeof(graphics_context));
      ax->wn = XtWindow(w_env);
      ax->dp = XtDisplay(w_env);
      ax->gc = cur_gc;
    }
  else clear_window(ax);

  cur_env = dialog_env;
  spf->with_dots = true;
  env_editor_display_env(spf, cur_env, ax, "mix env", 0, 0, widget_width(w), widget_height(w), NOT_PRINTING);
  if (with_mix_background_wave)
    show_mix_background_wave(mix_dialog_id);
}


#ifdef __APPLE__
static int press_x, press_y;
#endif

static void mix_drawer_button_motion(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  XMotionEvent *ev = (XMotionEvent *)event;
  if (!(mix_is_active(mix_dialog_id))) return;
#ifdef __APPLE__
  if ((press_x == ev->x) && (press_y == ev->y)) return;
#endif
  env_editor_button_motion(spf, ev->x, ev->y, ev->time, dialog_env);
  mix_amp_env_resize(w, NULL, NULL);
}


static void mix_drawer_button_press(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  XButtonEvent *ev = (XButtonEvent *)event;
  if (!(mix_is_active(mix_dialog_id))) return;
#ifdef __APPLE__
  press_x = ev->x;
  press_y = ev->y;
#endif
  if (env_editor_button_press(spf, ev->x, ev->y, ev->time, dialog_env))
    mix_amp_env_resize(w, NULL, NULL);
}


static void mix_drawer_button_release(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  if (!(mix_is_active(mix_dialog_id))) return;
  env_editor_button_release(spf, dialog_env);
  mix_amp_env_resize(w, NULL, NULL);
}


static Widget w_id = NULL, w_beg = NULL;
#if WITH_AUDIO
  static Widget mix_play = NULL;
#endif
static Widget error_frame = NULL, error_label = NULL;

static void clear_mix_error(void)
{
  if ((error_frame) && (XtIsManaged(error_frame)))
    XtUnmanageChild(error_frame);
}


static void unpost_mix_error(XtPointer data, XtIntervalId *id)
{
  clear_mix_error();
}


static void errors_to_mix_text(const char *msg, void *data)
{
  int lines = 0;
  XmString label;
  label = multi_line_label(msg, &lines);
  XtVaSetValues(error_label, 
		XmNlabelString, label, 
		XmNheight, lines * 20,
		NULL);
  XtVaSetValues(error_frame, XmNheight, lines * 20, NULL);
  XmStringFree(label);
  XtManageChild(error_frame);
  /* since the offending text is automatically overwritten, we can't depend on subsequent text modify callbacks
   *   to clear things, so we'll just use a timer
   */
  XtAppAddTimeOut(main_app(ss),
		  5000,
		  (XtTimerCallbackProc)unpost_mix_error,
		  NULL);
}


static void widget_mix_to_text(Widget w, int id)
{
  if (mix_name(id))
    XmTextFieldSetString(w, (char *)mix_name(id));
  else widget_int_to_text(w, id);
}


static bool id_changed = false;

static void id_activated(void)
{
  char *val;
  id_changed = false;
  val = XmTextGetString(w_id);
  if (val)
    {
      int id;
      /* look for a mix name first, then a number */
      id = mix_name_to_id(val);
      if (id < 0)
	{
	  redirect_errors_to(errors_to_mix_text, NULL);
	  id = string_to_int(val, 0, "id");
	  redirect_errors_to(NULL, NULL);
	}
      if (mix_is_active(id))
	{
	  mix_dialog_id = id;
	  reflect_mix_change(id);
	}
      XtFree(val);
    }
}


static void id_modify_callback(Widget w, XtPointer context, XtPointer info) 
{
  id_changed = true;
}


static void id_check_callback(Widget w, XtPointer context, XtPointer info)
{
  if (id_changed) id_activated();
}


static void beg_activated(void)
{
  char *val;
  if (!(mix_is_active(mix_dialog_id))) return;
  val = XmTextGetString(w_beg);
  if (val)
    {
      chan_info *cp;
      char *up_to_colon;
      mus_float_t beg;
      cp = mix_chan_info_from_id(mix_dialog_id);
      up_to_colon = string_to_colon(val);
      redirect_errors_to(errors_to_mix_text, NULL);
      beg = string_to_mus_float_t(up_to_colon, 0.0, "begin time");
      redirect_errors_to(NULL, NULL);
      if (beg >= 0.0)
	{
	  mus_long_t pos, old_pos;
	  old_pos = mix_position_from_id(mix_dialog_id);
	  pos = (mus_long_t)(beg * snd_srate(cp->sound));
	  mix_set_position_edit(mix_dialog_id, pos);
	  syncd_mix_change_position(mix_dialog_id, pos - old_pos);
	}
      after_mix_edit(mix_dialog_id);
      free(up_to_colon);
      XtFree(val);
    }
}


static void apply_mix_dialog_callback(Widget w, XtPointer context, XtPointer info) 
{
  if (!(mix_is_active(mix_dialog_id))) return;
  if ((dialog_env) && 
      (!(is_default_env(dialog_env))))
    {
      mix_set_amp_env_edit(mix_dialog_id, dialog_env);
      syncd_mix_set_amp_env(mix_dialog_id, dialog_env);  
    }
  else 
    {
      mix_set_amp_env_edit(mix_dialog_id, NULL);
      syncd_mix_set_amp_env(mix_dialog_id, NULL);  
    }
  mix_amp_env_resize(w_env, NULL, NULL);
  after_mix_edit(mix_dialog_id);
}


static void copy_mix_dialog_callback(Widget w, XtPointer context, XtPointer info) 
{
  Widget active_widget;
  active_widget = XmGetFocusWidget(mix_dialog);
  if (active_widget == XmMessageBoxGetChild(mix_dialog, XmDIALOG_OK_BUTTON))
    {
      copy_mix(mix_dialog_id);
      after_mix_edit(mix_dialog_id);
    }
  else
    {
      if (active_widget == w_id)
	id_activated();
      else
	{
	  if (active_widget == w_beg)
	    beg_activated();
	}
    }
}


static void quit_mix_dialog_callback(Widget w, XtPointer context, XtPointer info) 
{
  clear_mix_error();
  XtUnmanageChild(mix_dialog);
}


static void help_mix_dialog_callback(Widget w, XtPointer context, XtPointer info) 
{
  mix_dialog_help();
}


/* -------- play -------- */

#if WITH_AUDIO
  static bool mix_playing = false;
#endif

void reflect_mix_play_stop(void)
{
#if WITH_AUDIO
  if (mix_play)
    XmChangeColor(mix_play, ss->basic_color);
  mix_playing = false;
#endif
}


#if WITH_AUDIO
static void mix_dialog_play_callback(Widget w, XtPointer context, XtPointer info) 
{
  if (mix_playing)
    reflect_mix_play_stop();
  else
    {
      if (!(mix_exists(mix_dialog_id))) return;
      if (mix_play)
	XmChangeColor(mix_play, ss->selection_color); /* this needs to happen before trying to play */
      syncd_mix_play(mix_dialog_id);
      mix_playing = true;                                      /* don't use the return value here */
      play_mix_from_id(mix_dialog_id);
    }
}
#endif


/* -------- dB -------- */

static void mix_dB_callback(Widget w, XtPointer context, XtPointer info)
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info; 
  spf->in_dB = cb->set;
  mix_amp_env_resize(w_env, NULL, NULL);
}


/* -------- sync -------- */

static void mix_sync_callback(Widget w, XtPointer context, XtPointer info)
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info; 
  if ((cb->set) &&
      (mix_sync_from_id(mix_dialog_id) == 0))
    {
      mix_set_sync_from_id(mix_dialog_id, GET_ORIGINAL_SYNC);  /* choose a new sync val or return to previous */
      /* check for resync */
      syncd_mix_set_color(mix_dialog_id, ss->red);
    }
  else
    {
      if ((!(cb->set)) &&
	  (mix_sync_from_id(mix_dialog_id) != 0))
	{
	  syncd_mix_unset_color(mix_dialog_id); /* unset colors of any syncd mixes */
	  mix_set_sync_from_id(mix_dialog_id, 0);
	}
    }
  for_each_normal_chan(display_channel_mixes);
}


/* -------- clip -------- */

static void mix_clip_callback(Widget w, XtPointer context, XtPointer info)
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info; 
  spf->clipping = cb->set;
  mix_amp_env_resize(w_env, NULL, NULL);
}


/* -------- wave -------- */

static void mix_wave_callback(Widget w, XtPointer context, XtPointer info)
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info; 
  with_mix_background_wave = cb->set;
  mix_amp_env_resize(w_env, NULL, NULL);
}


/* -------- next/previous -------- */

static Widget mix_next_button, mix_previous_button;

static void mix_next_callback(Widget w, XtPointer context, XtPointer info)
{
  int id;
  clear_mix_error();
  id = next_mix_id(mix_dialog_id);
  if (id != INVALID_MIX_ID)
    {
      mix_dialog_id = id;
      reflect_mix_change(id);
      if (next_mix_id(id) == INVALID_MIX_ID) 
	set_sensitive(mix_next_button, false);
    }
}


static void mix_previous_callback(Widget w, XtPointer context, XtPointer info)
{
  int id;
  clear_mix_error();
  id = previous_mix_id(mix_dialog_id);
  if (id != INVALID_MIX_ID)
    {
      mix_dialog_id = id;
      reflect_mix_change(id);
      if (previous_mix_id(id) == INVALID_MIX_ID) 
	set_sensitive(mix_previous_button, false);
    }
}


static Pixmap make_pixmap(unsigned char *bits, int width, int height, int depth, GC gc)
{
  Pixmap rb, nr;
  rb = XCreateBitmapFromData(main_display(ss), 
			     RootWindowOfScreen(XtScreen(main_pane(ss))), 
			     (const char *)bits, 
			     width, height);
  nr = XCreatePixmap(main_display(ss), 
		     RootWindowOfScreen(XtScreen(main_pane(ss))), 
		     width, height, depth);
  XCopyPlane(main_display(ss), rb, nr, gc, 0, 0, width, height, 0, 0, 1);
  XFreePixmap(main_display(ss), rb);
  return(nr);
}



#define p_speaker_width 12
#define p_speaker_height 12
static unsigned char p_speaker_bits[] = {
   0x00, 0x07, 0xc0, 0x04, 0x30, 0x04, 0x0e, 0x04, 0x06, 0x04, 0x06, 0x04,
   0x06, 0x04, 0x06, 0x04, 0x0e, 0x04, 0x30, 0x04, 0xc0, 0x04, 0x00, 0x07};

static int mixer_depth;
static GC gc;
static Pixmap speaker_r;

void make_mixer_icons_transparent_again(Pixel old_color, Pixel new_color)
{
  if (mix_dialog)
    {
      XFreePixmap(XtDisplay(mix_dialog), speaker_r);
      XSetBackground(XtDisplay(mix_dialog), gc, new_color);
      speaker_r = make_pixmap(p_speaker_bits, p_speaker_width, p_speaker_height, mixer_depth, gc);
#if WITH_AUDIO
      XtVaSetValues(mix_play, XmNlabelPixmap, speaker_r, NULL);
#endif
    }
}

static Widget w_sync;

Widget make_mix_dialog(void) 
{
  if (!mix_dialog)
    {
      Widget mainform, mix_row, mix_frame, sep, w_sep1;
      Widget w_dB_frame, w_dB, w_clip, w_wave, w_dB_row, env_button, copy_button;
      XmString xgo_away, xhelp, xtitle, s1, xcopy;
      int n;
      Arg args[20];
      XtCallbackList n1, n2;
      XGCValues v;
      char amplab[LABEL_BUFFER_SIZE];

      xmix_speed_control_style = speed_control_style(ss);

      mix_dialog_id = any_mix_id();
      xgo_away = XmStringCreateLocalized((char *)I_GO_AWAY);
      xcopy = XmStringCreateLocalized((char *)"Copy mix");
      xhelp = XmStringCreateLocalized((char *)I_HELP);
      xtitle = XmStringCreateLocalized((char *)"Mixes");

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNokLabelString, xcopy); n++;
      XtSetArg(args[n], XmNcancelLabelString, xgo_away); n++;
      XtSetArg(args[n], XmNhelpLabelString, xhelp); n++;
      XtSetArg(args[n], XmNautoUnmanage, false); n++;
      XtSetArg(args[n], XmNdialogTitle, xtitle); n++;
      XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
      XtSetArg(args[n], XmNnoResize, false); n++;
      XtSetArg(args[n], XmNtransient, false); n++;
      mix_dialog = XmCreateTemplateDialog(main_shell(ss), (char *)"Mixes", args, n);
      copy_button = XmMessageBoxGetChild(mix_dialog, XmDIALOG_OK_BUTTON);

      /* XtAddCallback(mix_dialog, XmNokCallback, copy_mix_dialog_callback, NULL); */
      XtAddCallback(copy_button, XmNactivateCallback, copy_mix_dialog_callback, NULL);
      XtAddCallback(mix_dialog, XmNcancelCallback, quit_mix_dialog_callback, NULL);
      XtAddCallback(mix_dialog, XmNhelpCallback, help_mix_dialog_callback, NULL);

      XmStringFree(xhelp);
      XmStringFree(xcopy);
      XmStringFree(xgo_away);
      XmStringFree(xtitle);

      XtVaSetValues(XmMessageBoxGetChild(mix_dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(mix_dialog, XmDIALOG_OK_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(mix_dialog, XmDIALOG_HELP_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(mix_dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(mix_dialog, XmDIALOG_OK_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(mix_dialog, XmDIALOG_HELP_BUTTON), XmNbackground, ss->highlight_color, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      env_button = XtCreateManagedWidget("Apply env", xmPushButtonGadgetClass, mix_dialog, args, n);
      XtAddCallback(env_button, XmNactivateCallback, apply_mix_dialog_callback, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, XmMessageBoxGetChild(mix_dialog, XmDIALOG_SEPARATOR)); n++;
      mainform = XtCreateManagedWidget("formd", xmFormWidgetClass, mix_dialog, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNallowResize, true); n++;
      XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_IN); n++;
      XtSetArg(args[n], XmNshadowThickness, 2); n++;
      mix_frame = XtCreateManagedWidget("mix-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      mix_row = XtCreateManagedWidget("mix-dialog-row", xmRowColumnWidgetClass, mix_frame, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtCreateManagedWidget("mix:", xmLabelWidgetClass, mix_row, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNresizeWidth, false); n++;
      XtSetArg(args[n], XmNcolumns, NAME_COLUMNS); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      w_id = make_textfield_widget("mix-id", mix_row, args, n, ACTIVATABLE, NO_COMPLETER);
      XtAddCallback(w_id, XmNlosingFocusCallback, id_check_callback, NULL);
      XtAddCallback(w_id, XmNmodifyVerifyCallback, id_modify_callback, NULL);
      XmTextSetString(w_id, (char *)"0");

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      w_beg = make_textfield_widget("mix-times", mix_row, args, n, ACTIVATABLE, NO_COMPLETER);
      XmTextSetString(w_beg, (char *)"0.000 : 1.000");

      XtVaGetValues(mix_row, XmNforeground, &v.foreground, XmNbackground, &v.background, XmNdepth, &mixer_depth, NULL);
      gc = XtGetGC(mix_row, GCForeground | GCBackground, &v);
      speaker_r = make_pixmap(p_speaker_bits, p_speaker_width, p_speaker_height, mixer_depth, gc);

#if WITH_AUDIO
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNlabelType, XmPIXMAP); n++;
      XtSetArg(args[n], XmNlabelPixmap, speaker_r); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      mix_play = XtCreateManagedWidget("mix-play", xmPushButtonWidgetClass, mix_row, args, n);
      XtAddCallback(mix_play, XmNactivateCallback, mix_dialog_play_callback, NULL);
#endif

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      mix_previous_button = XtCreateManagedWidget(I_PREVIOUS, xmPushButtonWidgetClass, mix_row, args, n);
      if (previous_mix_id(mix_dialog_id) == INVALID_MIX_ID) 
	set_sensitive(mix_previous_button, false);
      XtAddCallback(mix_previous_button, XmNactivateCallback, mix_previous_callback, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      mix_next_button = XtCreateManagedWidget(I_NEXT, xmPushButtonWidgetClass, mix_row, args, n);
      if (next_mix_id(mix_dialog_id) == INVALID_MIX_ID) 
	set_sensitive(mix_next_button, false);
      XtAddCallback(mix_next_button, XmNactivateCallback, mix_next_callback, NULL);



      /* separator before sliders */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, mix_row); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNheight, 10); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      sep = XtCreateManagedWidget("mix-dialog-sep", xmSeparatorWidgetClass, mainform, args, n);

      /* SPEED */
      n = 0;
      s1 = XmStringCreateLocalized((char *)"speed:");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      w_speed_label = make_pushbutton_widget("mix-speed-label", mainform, args, n);
      XtAddCallback(w_speed_label, XmNactivateCallback, mix_speed_click_callback, NULL);
      XmStringFree(s1);

      n = 0;
      s1 = initial_speed_label(xmix_speed_control_style);
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, w_speed_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, w_speed_label); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      w_speed_number = make_pushbutton_widget("mix-speed-number", mainform, args, n);
      XtAddCallback(w_speed_number, XmNactivateCallback, mix_speed_label_click_callback, NULL);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->position_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, w_speed_number); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, w_speed_number); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNvalue, speed_to_scrollbar(speed_control_min(ss), 1.0, speed_control_max(ss))); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNdragCallback, n1 = make_callback_list(mix_speed_drag_callback, NULL)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n2 = make_callback_list(mix_speed_valuechanged_callback, NULL)); n++;
      w_speed = XtCreateManagedWidget("mix-speed", xmScrollBarWidgetClass, mainform, args, n);
  
      free(n1);
      free(n2);

      n = 0;
      snprintf(amplab, LABEL_BUFFER_SIZE, "%s", "amp:");
      s1 = XmStringCreateLocalized(amplab);
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, w_speed_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      w_amp_label = make_pushbutton_widget("mix-amp-label", mainform, args, n);
      XtAddCallback(w_amp_label, XmNactivateCallback, mix_amp_click_callback, NULL);
      XmStringFree(s1);

      n = 0;
      s1 = XmStringCreateLocalized((char *)"1.00");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, w_speed_number); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, w_amp_label); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      w_amp_number = XtCreateManagedWidget("mix-amp-number", xmLabelWidgetClass, mainform, args, n);
      XmStringFree(s1);

      n = 0;      
      XtSetArg(args[n], XmNbackground, ss->position_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, w_amp_number); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, w_amp_number); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNvalue, 0); n++; 
      XtSetArg(args[n], XmNdragCallback, n1 = make_callback_list(mix_amp_drag_callback, NULL)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n2 = make_callback_list(mix_amp_valuechanged_callback, NULL)); n++;
      w_amp = XtCreateManagedWidget("mix-amp", xmScrollBarWidgetClass, mainform, args, n);
      free(n1);
      free(n2);

      /* separator before envelopes */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, w_amp_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNheight, 8); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      w_sep1 = XtCreateManagedWidget("mix-dialog-sep1", xmSeparatorWidgetClass, mainform, args, n);

      /* button box for dB clip wave sync */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, w_sep1); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_IN); n++;
      XtSetArg(args[n], XmNshadowThickness, 4); n++;
      w_dB_frame = XtCreateManagedWidget("mix-dB-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      w_dB_row = XtCreateManagedWidget("mix-dB-row", xmRowColumnWidgetClass, w_dB_frame, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      if (ss->toggle_size > 0) {XtSetArg(args[n], XmNindicatorSize, ss->toggle_size); n++;}

      w_clip = make_togglebutton_widget("clip", w_dB_row, args, n);
      XtAddCallback(w_clip, XmNvalueChangedCallback, mix_clip_callback, NULL);
      XmToggleButtonSetState(w_clip, true, false);

      w_wave = make_togglebutton_widget("wave", w_dB_row, args, n);
      XtAddCallback(w_wave, XmNvalueChangedCallback, mix_wave_callback, NULL);

      w_dB = make_togglebutton_widget("dB", w_dB_row, args, n);
      XtAddCallback(w_dB, XmNvalueChangedCallback, mix_dB_callback, NULL);

      if (mix_sync_from_id(mix_dialog_id) != 0)
	{XtSetArg(args[n], XmNset, true); n++;}
      w_sync = make_togglebutton_widget("sync", w_dB_row, args, n);
      XtAddCallback(w_sync, XmNvalueChangedCallback, mix_sync_callback, NULL);


      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNallowResize, true); n++;
      XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_IN); n++;
      XtSetArg(args[n], XmNshadowThickness, 2); n++;
      error_frame = XtCreateManagedWidget("error-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      error_label = XtCreateManagedWidget("", xmLabelWidgetClass, error_frame, args, n);

      
      /* amp env */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, w_sep1); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNleftPosition, 4); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, w_dB_frame); n++;
      XtSetArg(args[n], XmNallowResize, true); n++;
      XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_IN); n++;
      XtSetArg(args[n], XmNshadowThickness, 4); n++;
      w_env_frame = XtCreateManagedWidget("mix-amp-env-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNallowResize, true); n++;
      w_env = XtCreateManagedWidget("mix-amp-env-window", xmDrawingAreaWidgetClass, w_env_frame, args, n);

      XtManageChild(mix_dialog);

      XtAddCallback(w_env, XmNresizeCallback, mix_amp_env_resize, NULL);
      XtAddCallback(w_env, XmNexposeCallback, mix_amp_env_resize, NULL);

      spf = new_env_editor();

      XtAddEventHandler(w_env, ButtonPressMask, false, mix_drawer_button_press, NULL);
      XtAddEventHandler(w_env, ButtonMotionMask, false, mix_drawer_button_motion, NULL);
      XtAddEventHandler(w_env, ButtonReleaseMask, false, mix_drawer_button_release, NULL);

      set_dialog_widget(MIX_DIALOG, mix_dialog);

      XtUnmanageChild(error_frame);
    }
  else 
    {
      if (!(XtIsManaged(mix_dialog))) XtManageChild(mix_dialog);
      raise_dialog(mix_dialog);
    }
  reflect_mix_change(mix_dialog_id);
  return(mix_dialog);
}


void reflect_mix_change(int mix_id)
{
  if ((mix_dialog) && 
      (XtIsManaged(mix_dialog)))
    {
      if (mix_id != ANY_MIX_ID)
	mix_dialog_id = mix_id;

      if (!(mix_exists(mix_dialog_id))) 
	{
	  mix_dialog_id = any_mix_id(); 
	  mix_id = mix_dialog_id;
	}

      if ((mix_id == mix_dialog_id) || (mix_id == ANY_MIX_ID))
	{
	  mus_float_t val;
	  set_sensitive(mix_next_button, (next_mix_id(mix_dialog_id) != INVALID_MIX_ID));
	  set_sensitive(mix_previous_button, (previous_mix_id(mix_dialog_id) != INVALID_MIX_ID));

	  /* now reflect current mix state in mix dialog controls */
	  if (mix_exists(mix_dialog_id))
	    {
	      chan_info *cp = NULL;
	      mus_long_t beg, len;
	      char lab[LABEL_BUFFER_SIZE];
	      
	      /* syncd mixes have the same color (red) reverting to old color when sync changes */
	      cp = mix_chan_info_from_id(mix_dialog_id);
	      if (old_mix_dialog_id != INVALID_MIX_ID)
		{
		  mix_unset_color_from_id(old_mix_dialog_id);
		  syncd_mix_unset_color(old_mix_dialog_id);
		}
	      old_mix_dialog_id = mix_dialog_id;
	      mix_set_color_from_id(mix_dialog_id, ss->red);
	      syncd_mix_set_color(mix_dialog_id, ss->red);

	      for_each_normal_chan(display_channel_mixes);

	      if (!dragging)
		{
		  val = mix_speed_from_id(mix_dialog_id);
		  XtVaSetValues(w_speed, XmNvalue, speed_to_scrollbar(speed_control_min(ss), val, speed_control_max(ss)), NULL);
		  speed_changed(val, lab, xmix_speed_control_style, speed_control_tones(ss), 6);
		  set_label(w_speed_number, lab);
		}
	      widget_mix_to_text(w_id, mix_dialog_id);
	      
	      beg = mix_position_from_id(mix_dialog_id);
	      len = mix_length_from_id(mix_dialog_id);
	      snprintf(lab, LABEL_BUFFER_SIZE, "%.3f : %.3f%s",
			   ((double)beg / (double)snd_srate(cp->sound)),
			   ((double)(beg + len) / (double)snd_srate(cp->sound)),
			   (mix_is_active(mix_dialog_id)) ? "" : " (locked)");
	      XmTextSetString(w_beg, lab);
	      
	      set_sensitive(XmMessageBoxGetChild(mix_dialog, XmDIALOG_OK_BUTTON), true);
	    }
	  else
	    {
	      XmTextSetString(w_id, (char *)"-1");
	      XmTextSetString(w_beg, (char *)"no active mixes");
	      set_sensitive(XmMessageBoxGetChild(mix_dialog, XmDIALOG_OK_BUTTON), false);
	    }
	  if (!dragging)
	    {
	      if (mix_is_active(mix_dialog_id))
		val = mix_amp_from_id(mix_dialog_id);
	      else val = 1.0;
	      XtVaSetValues(w_amp, XmNvalue, amp_to_scrollbar(w_amp_number, val), NULL);
	    }
	  
	  if (mix_amp_env_from_id(mix_dialog_id))
	    {
	      if (dialog_env) free_env(dialog_env);
	      dialog_env = copy_env(mix_amp_env_from_id(mix_dialog_id));
	    }
	  /* copy here else we're editing it directly afterwards (and we free old in mix_set_amp_env_edit) */
	  if (!dialog_env) 
	    dialog_env = default_env(1.0, 1.0);
	  mix_amp_env_resize(w_env, NULL, NULL);

	  set_toggle_button(w_sync, (mix_sync_from_id(mix_dialog_id) != 0), false, NULL);
	}
    }
}


int mix_dialog_mix(void) 
{
  return(mix_dialog_id);
}


void mix_dialog_set_mix(int id) 
{
  mix_dialog_id = id; 
  reflect_mix_change(mix_dialog_id);
}



/* envelope editor and viewer */


static Widget enved_dialog = NULL;
static Widget apply_button, apply2_button, cancel_button, drawer, show_button, save_button, revert_button, undo_button, redo_button;
static Widget brkptL, graph_button, flt_button, amp_button, src_button, clip_button;
static Widget nameL, textL, screnvlst, dB_button, orderL, reset_button, fir_button = NULL;
static Widget baseScale, baseValue, selection_button;
static GC gc1, rgc, ggc;

static const char *env_names[3] = {"amp env:", "flt env:", "src env:"};

static bool showing_all_envs = false; /* edit one env (0), or view all currently defined envs (1) */
static bool apply_to_selection = false, we_turned_selection_off = false;

static int env_window_width = 0;
static int env_window_height = 0;

static chan_info *active_channel = NULL, *last_active_channel = NULL;

static env* selected_env = NULL; /* if during view, one env is clicked, it is "selected" and can be pasted elsewhere */
static env* active_env = NULL;   /* env currently being edited */

static axis_info *axis = NULL;
static axis_info *gray_ap = NULL;
static bool is_FIR = true;
static bool old_clipping = false;
static bool ignore_button_release = false;
static bool cancelling = true;


static void fixup_graphics_context(graphics_context *ax, Widget w, GC gc)
{
  ax->dp = XtDisplay(w);
  ax->wn = XtWindow(w);
  if (gc) ax->gc = gc;
}


axis_info *enved_make_axis(const char *name, graphics_context *ax, 
			   int ex0, int ey0, int width, int height, 
			   mus_float_t xmin, mus_float_t xmax, mus_float_t ymin, mus_float_t ymax,
			   printing_t printing)
{
  /* conjure up minimal context for axis drawer in snd-axis.c */
  if (!axis) 
    {
      axis = (axis_info *)calloc(1, sizeof(axis_info));
      axis->ax = (graphics_context *)calloc(1, sizeof(graphics_context));
      fixup_graphics_context(axis->ax, drawer, ax->gc);
    }
  if (!gray_ap) 
    {
      gray_ap = (axis_info *)calloc(1, sizeof(axis_info));
      gray_ap->ax = (graphics_context *)calloc(1, sizeof(graphics_context));
      gray_ap->graph_active = true;
      fixup_graphics_context(gray_ap->ax, drawer, ggc);
    }
  init_env_axes(axis, name, ex0, ey0, width, height, xmin, xmax, ymin, ymax, printing);
  return(axis);
}


static void display_env(env *e, const char *name, GC cur_gc, int x0, int y0, int width, int height, bool dots, printing_t printing)
{
  graphics_context *ax = NULL;  
  ax = (graphics_context *)calloc(1, sizeof(graphics_context));
  ax->wn = XtWindow(drawer);
  if (!(ax->wn)) {free(ax); return;}
  ax->dp = XtDisplay(drawer);
  ax->gc = cur_gc;
  ss->enved->with_dots = dots;
  env_editor_display_env(ss->enved, e, ax, name, x0, y0, width, height, printing);
  free(ax);
}


void display_enved_env_with_selection(env *e, const char *name, int x0, int y0, int width, int height, bool dots, printing_t printing)
{
  display_env(e, name, (selected_env == e) ? rgc : gc1, x0, y0, width, height, dots, printing);
}


static void reflect_segment_state(void)
{
  if ((enved_dialog) &&
      (active_env) && 
      (!(showing_all_envs)))
    env_redisplay();
}


static void prepare_env_edit(env *new_env)
{
  prepare_enved_edit(new_env);
  if (new_env->base == 1.0)
    set_enved_style(ENVELOPE_LINEAR);
  else
    {
      set_enved_base(new_env->base);
      set_enved_style(ENVELOPE_EXPONENTIAL);
    }
  reflect_segment_state();
}


void set_enved_redo_sensitive(bool val) {set_sensitive(redo_button, val);}
void set_enved_revert_sensitive(bool val) {set_sensitive(revert_button, val);}
void set_enved_undo_sensitive(bool val) {set_sensitive(undo_button, val);}
void set_enved_save_sensitive(bool val) {set_sensitive(save_button, val);}
void set_enved_show_sensitive(bool val) {set_sensitive(show_button, val);}


static bool use_listener_font = false;

void make_scrolled_env_list(void)
{
  XmString *strs;
  int n, size;
  size = enved_all_envs_top();
  XtVaSetValues(screnvlst, XmNbackground, ss->highlight_color, NULL); 
  strs = (XmString *)calloc(size, sizeof(XmString)); 
  for (n = 0; n < size; n++) 
    strs[n] = XmStringCreate(enved_all_names(n), (char *)((use_listener_font) ? "listener_font" : XmFONTLIST_DEFAULT_TAG));
  XtVaSetValues(screnvlst, 
		XmNitems, strs, 
		XmNitemCount, size, 
		NULL);
  for (n = 0; n < size; n++) 
    XmStringFree(strs[n]);
  free(strs);
}


void enved_reflect_peak_env_completion(snd_info *sp)
{
  if ((enved_dialog) && (active_channel) && (enved_with_wave(ss)))
    if (active_channel->sound == sp) 
      env_redisplay();
}


void new_active_channel_alert(void)
{
  if (enved_dialog)
    {
      /* if showing current active channel in gray, update */
      active_channel = current_channel();
      env_redisplay();
    }
}


static void dismiss_enved_callback(Widget w, XtPointer context, XtPointer info) 
{
  if (!cancelling)
    {
      if (ss->checking_explicitly)
	ss->stopped_explicitly = true;
    }
  else XtUnmanageChild(enved_dialog);
}


static void help_enved_callback(Widget w, XtPointer context, XtPointer info) 
{
  envelope_editor_dialog_help();
}


static bool within_selection_src = false;

static void apply_enved(void)
{
  if (active_env)
    {
      active_channel = current_channel();
      if (active_channel)
	{
	  int i, j;
	  char *origin = NULL, *estr = NULL;
	  env *max_env = NULL;

	  set_sensitive(apply_button, false);
	  set_sensitive(apply2_button, false);
	  set_button_label(cancel_button, I_STOP);
	  cancelling = false;
	  check_for_event();

	  switch (enved_target(ss))
	    {
	    case ENVED_AMPLITUDE:
#if HAVE_FORTH
	      origin = mus_format("%s%s %s drop", 
				  estr = env_to_string(active_env),
				  (apply_to_selection) ? "" : " 0 " PROC_FALSE,
				  (apply_to_selection) ? S_env_selection : S_env_channel);
#else
	      origin = mus_format("%s" PROC_OPEN "%s%s", 
				  to_proc_name((apply_to_selection) ? S_env_selection : S_env_channel),
				  estr = env_to_string(active_env),
				  (apply_to_selection) ? "" : PROC_SEP "0" PROC_SEP PROC_FALSE);
#endif
	      apply_env(active_channel, active_env, 0,
			current_samples(active_channel), 
			apply_to_selection, 
			origin, NULL,
			C_int_to_Xen_integer(AT_CURRENT_EDIT_POSITION), 0);
	      /* calls update_graph, I think, but in short files that doesn't update the amp-env */
	      if (estr) free(estr);
	      if (origin) free(origin);
	      break;
	    case ENVED_SPECTRUM: 
#if HAVE_FORTH
	      origin = mus_format("%s %d%s %s drop",
				  estr = env_to_string(active_env), 
				  enved_filter_order(ss),
				  (apply_to_selection) ? "" : " 0 " PROC_FALSE,
				  (apply_to_selection) ? S_filter_selection : S_filter_channel);
#else
	      origin = mus_format("%s" PROC_OPEN "%s" PROC_SEP "%d%s",
				  to_proc_name((apply_to_selection) ? S_filter_selection : S_filter_channel),
				  estr = env_to_string(active_env), 
				  enved_filter_order(ss),
				  (apply_to_selection) ? "" : PROC_SEP "0" PROC_SEP PROC_FALSE);
#endif
	      apply_filter(active_channel,
			   (is_FIR) ? enved_filter_order(ss) : 0,
			   active_env, 
			   origin, NULL, apply_to_selection,
			   NULL, NULL,
			   C_int_to_Xen_integer(AT_CURRENT_EDIT_POSITION), 0, false);
	      if (estr) free(estr);
	      if (origin) free(origin);
	      break;
	    case ENVED_SRATE:
	      /* mus_src no longer protects against 0 srate */
	      max_env = copy_env(active_env);
	      for (i = 0, j = 1; i < max_env->pts; i++, j += 2)
		if (max_env->data[j] < .01) 
		  max_env->data[j] = .01;
	      within_selection_src = true;
	      src_env_or_num(active_channel, max_env, 0.0, 
			     false, "Enved: src", 
			     apply_to_selection, NULL,
			     C_int_to_Xen_integer(AT_CURRENT_EDIT_POSITION), 0);
	      within_selection_src = false;
	      max_env = free_env(max_env);
	      break;
	    }
	  if (enved_with_wave(ss)) env_redisplay();
	  set_sensitive(apply_button, true);
	  set_sensitive(apply2_button, true);
	  set_button_label(cancel_button, I_GO_AWAY);
	  cancelling = true;
	}
    }
}


static void env_redisplay_1(printing_t printing)
{
  if (enved_dialog_is_active())
    {
      XClearWindow(XtDisplay(drawer), XtWindow(drawer));
      if (showing_all_envs) 
	view_envs(env_window_width, env_window_height, printing);
      else 
	{
	  char *name = NULL;
	  name = XmTextGetString(textL);
	  if (!name) name = mus_strdup("noname");
	  /* active_env can be null here if just showing axes (empty initial graph) */
	  
	  if ((enved_with_wave(ss)) &&
	      (active_channel) &&
	      (!(active_channel->squelch_update)))
	    {
	      if ((enved_target(ss) == ENVED_SPECTRUM) && (active_env) && (is_FIR) && (printing == NOT_PRINTING))
		display_frequency_response(active_env, axis, gray_ap->ax, enved_filter_order(ss), enved_in_dB(ss));
	      enved_show_background_waveform(axis, gray_ap, apply_to_selection, (enved_target(ss) == ENVED_SPECTRUM), printing);
	    }

	  display_env(active_env, name, gc1, 0, 0, env_window_width, env_window_height, true, printing);
	  if (name) XtFree(name);
	}
    }
}


void env_redisplay(void) 
{
  env_redisplay_1(NOT_PRINTING);
}

void env_redisplay_with_print(void) 
{
  env_redisplay_1(PRINTING);
}


void update_enved_background_waveform(chan_info *cp)
{
  if ((enved_dialog_is_active()) &&
      (enved_with_wave(ss)) &&
      (enved_target(ss) == ENVED_AMPLITUDE) &&
      (cp == active_channel) &&
      ((!apply_to_selection) || (selection_is_active_in_channel(cp))))
    env_redisplay();
}


static void enved_reset(void)
{
  set_enved_clipping(DEFAULT_ENVED_CLIPPING);
  set_enved_style(ENVELOPE_LINEAR);
  set_enved_power(DEFAULT_ENVED_POWER);
  set_enved_base(DEFAULT_ENVED_BASE);
  set_enved_target(DEFAULT_ENVED_TARGET);
  set_enved_with_wave(DEFAULT_ENVED_WITH_WAVE);
  set_enved_in_dB(DEFAULT_ENVED_IN_DB);
  XmTextSetString(textL, NULL);
  set_enved_filter_order(DEFAULT_ENVED_FILTER_ORDER);
  if (active_env) active_env = free_env(active_env);
#if HAVE_SCHEME
  active_env = string_to_env("'(0 0 1 0)");
#endif
#if HAVE_FORTH
  active_env = string_to_env("'( 0 0 1 0 )");
#endif
#if HAVE_RUBY
  active_env = string_to_env("[0, 0, 1, 0]");
#endif
  set_enved_env_list_top(0);
  prepare_env_edit(active_env);
  set_sensitive(save_button, true);
  reflect_enved_style();
  env_redisplay();
}


static void clear_point_label(void);

static void clear_xenv_error(void)
{
  if (brkptL) 
    clear_point_label();
}


static void unpost_xenv_error(XtPointer data, XtIntervalId *id)
{
  clear_xenv_error();
}


static void errors_to_xenv_text(const char *msg, void *data)
{
  set_button_label(brkptL, msg);
  XtAppAddTimeOut(main_app(ss),
		  5000,
		  (XtTimerCallbackProc)unpost_xenv_error,
		  NULL);
}


static void order_field_activated(void)
{
  /* return in order text field */
  char *str = NULL;
  str = XmTextGetString(orderL);
  if ((str) && (*str))
    {
      int order;
      redirect_errors_to(errors_to_xenv_text, NULL);
      order = string_to_int(str, 1, "order");
      redirect_errors_to(NULL, NULL);
      if (order & 1) order++;
      if ((order > 0) && 
	  (order < 2000)) 
	set_enved_filter_order(order);
      else widget_int_to_text(orderL, enved_filter_order(ss));
    }
  if (str) XtFree(str);
}


static void text_field_activated(void)
{ /* might be breakpoints to load or an envelope name (<cr> in enved text field) */
  char *name = NULL;
  name = XmTextGetString(textL);
  if ((name) && (*name))
    {
      char *str;
      env *e = NULL;
      str = name;
      while (isspace((int)(*str))) str++;
      e = name_to_env(str);
      if (!e)
	{
	  if (isalpha((int)(str[0])))
	    {
	      alert_envelope_editor(str, copy_env(active_env));
	      add_or_edit_symbol(str, active_env);
	      set_sensitive(save_button, false);
	      env_redisplay(); /* updates label */
	      /* e is null here */
	    }
	  else 
	    {
	      redirect_errors_to(errors_to_xenv_text, NULL);
	      e = string_to_env(str);
	      redirect_errors_to(NULL, NULL);
	    }
	}
      if (e) 
	{
	  if (active_env) 
	    {
	      #define ENVED_TEMP_NAME "enved-backup"
	      /* save current under a temp name!  -- user might have mistakenly reused a name */
	      alert_envelope_editor((char *)ENVED_TEMP_NAME, copy_env(active_env));
	      add_or_edit_symbol(ENVED_TEMP_NAME, active_env);
	      active_env = free_env(active_env);
	    }
	  active_env = copy_env(e);
	  set_enved_env_list_top(0);
	  prepare_env_edit(active_env);
	  set_sensitive(save_button, true);
	  set_sensitive(undo_button, false);
	  set_sensitive(revert_button, false);
	  env_redisplay();
	  e = free_env(e);
	}
    }
  if (name) XtFree(name);
}

static void enved_text_activate_callback(Widget w, XtPointer context, XtPointer info) 
{
  text_field_activated();
}


static void order_activate_callback(Widget w, XtPointer context, XtPointer info) 
{
  order_field_activated();
}


static void save_button_pressed(Widget w, XtPointer context, XtPointer info) 
{
  char *name = NULL;
  if (!active_env) return;
  name = XmTextGetString(textL);
  if ((!name) || (!(*name))) 
    name = mus_strdup("unnamed");
  alert_envelope_editor(name, copy_env(active_env));
  add_or_edit_symbol(name, active_env);
  set_sensitive(save_button, false);
  env_redisplay();
  if (name) XtFree(name);
}


static void apply_enved_callback(Widget w, XtPointer context, XtPointer info) 
{
  /* apply current envs to currently sync'd channels */
  Widget active_widget;
  active_widget = XmGetFocusWidget(enved_dialog);
  if (active_widget == XmMessageBoxGetChild(enved_dialog, XmDIALOG_OK_BUTTON))
    {
      apply_enved();
      last_active_channel = active_channel;
    }
}


static void undo_and_apply_enved_callback(Widget w, XtPointer context, XtPointer info) 
{
  /* undo previous amp env, then apply */
  /* this blindly undoes the previous edit (assumed to be an envelope) -- if the user made some other change in the meantime, too bad */
  if ((active_channel) && (active_channel == last_active_channel))
    {
      active_channel->squelch_update = true;
      undo_edit_with_sync(active_channel, 1);
      active_channel->squelch_update = false;
      clear_status_area(active_channel->sound);
    }
  apply_enved();
  last_active_channel = active_channel;
}


static void select_or_edit_env(int pos)
{
  if (showing_all_envs)
    {
      showing_all_envs = false;
      set_button_label(show_button, "view envs");
    }
  if (active_env) active_env = free_env(active_env);
  selected_env = position_to_env(pos);
  if (!selected_env) return;
  active_env = selected_env;
  XmTextSetString(textL, enved_all_names(pos));
  set_enved_env_list_top(0);
  prepare_env_edit(active_env);
  set_sensitive(undo_button, false);
  set_sensitive(revert_button, false);
  set_sensitive(save_button, false);
  env_redisplay();
}


static void clear_point_label(void)
{
  XtVaSetValues(brkptL, XmNlabelType, XmSTRING, XmNlabelString, NULL, NULL);
}


static void enved_display_point_label(mus_float_t x, mus_float_t y)
{
  char brkpt_buf[LABEL_BUFFER_SIZE];
  if ((enved_in_dB(ss)) && (min_dB(ss) < -60))
    snprintf(brkpt_buf, LABEL_BUFFER_SIZE, "%.3f : %.5f", x, y);
  else snprintf(brkpt_buf, LABEL_BUFFER_SIZE, "%.3f : %.3f", x, y);
  set_button_label(brkptL, brkpt_buf);
}


static void drawer_button_motion(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  XMotionEvent *ev = (XMotionEvent *)event;
  ignore_button_release = false;

  if (!showing_all_envs)
    {
      mus_float_t x, y;
#ifdef __APPLE__
      if ((ev->x == press_x) && (ev->y == press_y)) return;
#endif
      env_editor_button_motion_with_xy(ss->enved, ev->x, ev->y, ev->time, active_env, &x, &y);
      enved_display_point_label(x, y);
      env_redisplay();
    }
}


static void drawer_button_press(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  XButtonEvent *ev = (XButtonEvent *)event;
#ifdef __APPLE__
  press_x = ev->x;
  press_y = ev->y;
#endif
  ss->enved->down_time = ev->time;
  ss->enved->env_dragged = false;
  if (showing_all_envs)
    {
      int pos;
      pos = hit_env(ev->x, ev->y, env_window_width, env_window_height);
      XmListSelectPos(screnvlst, pos + 1, false);
      if ((pos >= 0) && 
	  (pos < enved_all_envs_top())) 
	{
	  select_or_edit_env(pos);
	  ignore_button_release = true;
	}
    }
  else
    {
      if (!active_env)
	{
	  active_env = default_env(1.0, 0.0);
	  active_env->base = enved_base(ss);
	  env_redisplay(); /* needed to get current_xs set up correctly */
	}
      if (env_editor_button_press(ss->enved, ev->x, ev->y, ev->time, active_env))
	env_redisplay();
      enved_display_point_label(ungrf_x(ss->enved->axis, ev->x), env_editor_ungrf_y_dB(ss->enved, ev->y));
      set_sensitive(save_button, true);
      set_sensitive(undo_button, true);
      set_sensitive(revert_button, true);
    }
}


static void drawer_button_release(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  if (ignore_button_release)
    ignore_button_release = false;
  else
    {
      if ((active_env) && (!showing_all_envs))
	{
	  env_editor_button_release(ss->enved, active_env);
	  env_redisplay();
	  clear_point_label();
	}
    }
}


static void drawer_resize(Widget w, XtPointer context, XtPointer info) 
{
  /* update display, can be either view of all envs or sequence of current envs */
  env_window_width = widget_width(w);
  env_window_height = widget_height(w);
  env_redisplay();
}


static void show_button_pressed(Widget w, XtPointer context, XtPointer info) 
{
  /* if show all (as opposed to show current), loop through loaded LV_LISTs */
  showing_all_envs = (!showing_all_envs);
  set_button_label(show_button, (showing_all_envs) ? "edit env" : "view envs");
  env_redisplay();
}


static void selection_button_pressed(Widget s, XtPointer context, XtPointer info) 
{
  we_turned_selection_off = false;
  apply_to_selection = (!apply_to_selection);
  XmChangeColor(selection_button, (apply_to_selection) ? ((Pixel)ss->yellow) : ((Pixel)ss->highlight_color));
  set_sensitive(apply2_button, true);
  if ((enved_with_wave(ss)) && 
      (!showing_all_envs))
    env_redisplay();
}


static void revert_button_pressed(Widget w, XtPointer context, XtPointer info) 
{
  revert_env_edit();
  if (active_env) active_env = free_env(active_env);
  active_env = enved_next_env();
  if (!active_env)
    text_field_activated();
  env_redisplay();
}


static void undo_button_pressed(Widget w, XtPointer context, XtPointer info) 
{
  undo_env_edit();
  if (active_env) active_env = free_env(active_env);
  active_env = enved_next_env();
  env_redisplay();
}


static void redo_button_pressed(Widget w, XtPointer context, XtPointer info) 
{
  redo_env_edit();
  if (active_env) active_env = free_env(active_env);
  active_env = enved_next_env();
  env_redisplay();
}


static void reflect_apply_state(void)
{
  set_label(nameL, env_names[enved_target(ss)]);
  XmChangeColor(amp_button, (enved_target(ss) == ENVED_AMPLITUDE) ? ((Pixel)ss->yellow) : ((Pixel)ss->highlight_color));
  XmChangeColor(flt_button, (enved_target(ss) == ENVED_SPECTRUM) ? ((Pixel)ss->yellow) : ((Pixel)ss->highlight_color));
  XmChangeColor(src_button, (enved_target(ss) == ENVED_SRATE) ? ((Pixel)ss->yellow) : ((Pixel)ss->highlight_color));
  if ((!showing_all_envs) && 
      (enved_with_wave(ss))) 
    env_redisplay();
}


static void freq_button_callback(Widget w, XtPointer context, XtPointer info) 
{
  in_set_enved_target(ENVED_SPECTRUM);
  old_clipping = enved_clipping(ss);
  set_enved_clipping(true);
  reflect_apply_state();
}


static void amp_button_callback(Widget w, XtPointer context, XtPointer info) 
{
  if (enved_target(ss) == ENVED_SPECTRUM)
    set_enved_clipping(old_clipping);
  in_set_enved_target(ENVED_AMPLITUDE);
  reflect_apply_state();
}


static void src_button_callback(Widget w, XtPointer context, XtPointer info) 
{
  if (enved_target(ss) == ENVED_SPECTRUM)
    set_enved_clipping(old_clipping);
  in_set_enved_target(ENVED_SRATE);
  reflect_apply_state();
}


static void reset_button_callback(Widget w, XtPointer context, XtPointer info) 
{
  enved_reset();
}


static void enved_print(char *name)
{
  print_enved(name, env_window_height);
}


static void env_browse_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmListCallbackStruct *cb = (XmListCallbackStruct *)info;
  select_or_edit_env(cb->item_position - 1);
}


static void graph_button_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  in_set_enved_with_wave(cb->set);
  env_redisplay();
}


static void dB_button_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  in_set_enved_in_dB(cb->set);
  env_redisplay();
}


static void clip_button_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info; 
  in_set_enved_clipping(cb->set);
}



#define BASE_MAX 400
#define BASE_MID 200
/* these two just set the granularity of the scale widget, not the user-visible bounds */

static void make_base_label(mus_float_t bval)
{
  char *sfs, *buf;
  int i, len, scale_len;
  len = (int)(enved_power(ss) * 4);
  if (len < 32) len = 32;
  sfs = (char *)calloc(len, sizeof(char));
  snprintf(sfs, len, "%.3f", bval);
  scale_len = (int)(enved_power(ss) + 3);
  if (scale_len < 32) scale_len = 32;
  buf = (char *)calloc(scale_len, sizeof(char));
  for (i = 0; i < scale_len - 1; i++) 
    buf[i] = sfs[i];
  set_button_label(baseValue, buf);
  free(sfs);
  free(buf);
  in_set_enved_base(bval);
  if ((active_env) && 
      (!(showing_all_envs))) 
    {
      active_env->base = enved_base(ss);
      if (active_env->base == 1.0)
	set_enved_style(ENVELOPE_LINEAR);
      else set_enved_style(ENVELOPE_EXPONENTIAL);
      env_redisplay();
    }
}


static void base_changed(int val)
{
  mus_float_t bval;
  if (val == 0) 
    bval = 0.0;
  else 
    {
      if (val == BASE_MID)
	bval = 1.0;
      else
	{
	  if (val > BASE_MID)
	    bval = pow(1.0 + (10.0 * ((mus_float_t)(val - BASE_MID) / (mus_float_t)BASE_MID)), enved_power(ss));  
	  else 
	    bval = pow(((mus_float_t)val / (mus_float_t)BASE_MID), enved_power(ss) - 1.0);
	}
    }
  make_base_label(bval);
  if (active_env)
    set_sensitive(save_button, true); /* what about undo/redo here? */
}


static void reflect_changed_base(mus_float_t val)
{
  int ival;
  if (val <= 0.0) 
    ival = 0;
  else
    {
      if (val == 1.0)
	ival = BASE_MID;
      else
	{
	  if (val <= 1.0)
	    ival = (int)(pow(val, 1.0 / (enved_power(ss) - 1.0)) * BASE_MID);
	  else ival = (int)(BASE_MID + ((BASE_MID * (pow(val, (1.0 / (enved_power(ss)))) - 1)) / 10.0));
	}
    }
  XtVaSetValues(baseScale, XmNvalue, mus_iclamp(0, ival, (int)(BASE_MAX * .9)), NULL);
  make_base_label(val);
}


static void base_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScrollBarCallbackStruct *sb = (XmScrollBarCallbackStruct *)info;
  base_changed(sb->value);
}


static int base_last_value = BASE_MID;

static void base_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScrollBarCallbackStruct *sb = (XmScrollBarCallbackStruct *)info;
  base_last_value = sb->value;
  base_changed(sb->value);
}


static void base_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmPushButtonCallbackStruct *cb = (XmPushButtonCallbackStruct *)info;
  XButtonEvent *ev;
  int val;
  ev = (XButtonEvent *)(cb->event);
  if (ev->state & (ControlMask | MetaMask)) 
    val = base_last_value; 
  else val = BASE_MID; /* this is supposedly 1.0 */
  base_changed(val);
  XtVaSetValues(baseScale, XmNvalue, val, NULL);
}


static void FIR_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  is_FIR = (!is_FIR);
  set_label(w, (is_FIR) ? "fir" : "fft");
  if (enved_with_wave(ss)) env_redisplay();
}


static void reflect_sound_state(void)
{
  bool file_on;
  file_on = (bool)(any_selected_sound());
  set_sensitive(apply_button, file_on);
  set_sensitive(apply2_button, file_on);
}


static Xen reflect_file_in_enved(Xen hook_or_reason)
{
  if (enved_dialog) reflect_sound_state();
  return(Xen_false);
}

static void add_reflect_enved_hook(void);

Widget create_envelope_editor(void)
{
  if (!enved_dialog)
    {
      int n;
      Arg args[32];
      Widget colE, colD, colB, colF;
      Widget spacer, spacer1, aform, mainform, screnvname, baseSep, baseLabel;
      XmString xhelp, xdismiss, xapply, titlestr, s1;
      XGCValues gv;
      XtCallbackList n1, n2;
      char str[LABEL_BUFFER_SIZE];

      /* -------- DIALOG -------- */
      xdismiss = XmStringCreateLocalized((char *)I_GO_AWAY);
      xhelp = XmStringCreateLocalized((char *)I_HELP);
      titlestr = XmStringCreateLocalized((char *)"Edit Envelope");
      xapply = XmStringCreateLocalized((char *)"Apply");

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNautoUnmanage, false); n++;
      XtSetArg(args[n], XmNcancelLabelString, xdismiss); n++;
      XtSetArg(args[n], XmNhelpLabelString, xhelp); n++;
      XtSetArg(args[n], XmNokLabelString, xapply); n++;
      XtSetArg(args[n], XmNdialogTitle, titlestr); n++;
      XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
      XtSetArg(args[n], XmNnoResize, false); n++;
      XtSetArg(args[n], XmNtransient, false); n++;
      enved_dialog = XmCreateTemplateDialog(main_shell(ss), (char *)"envelope editor", args, n);
  
      XtAddCallback(enved_dialog, XmNcancelCallback, dismiss_enved_callback, NULL);
      XtAddCallback(enved_dialog, XmNhelpCallback, help_enved_callback, NULL);
      XtAddCallback(enved_dialog, XmNokCallback, apply_enved_callback, NULL);

      XmStringFree(xhelp);
      XmStringFree(xdismiss);
      XmStringFree(titlestr);
      XmStringFree(xapply);

      XtVaSetValues(XmMessageBoxGetChild(enved_dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(enved_dialog, XmDIALOG_HELP_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(enved_dialog, XmDIALOG_OK_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(enved_dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(enved_dialog, XmDIALOG_HELP_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(enved_dialog, XmDIALOG_OK_BUTTON), XmNbackground, ss->highlight_color, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      apply2_button = XtCreateManagedWidget("Undo&Apply", xmPushButtonGadgetClass, enved_dialog, args, n);
      XtAddCallback(apply2_button, XmNactivateCallback, undo_and_apply_enved_callback, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNforeground, ss->black); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      reset_button = XtCreateManagedWidget("Clear graph", xmPushButtonGadgetClass, enved_dialog, args, n);
      XtAddCallback(reset_button, XmNactivateCallback, reset_button_callback, NULL);


      /* -------- MAIN WIDGET -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, XmMessageBoxGetChild(enved_dialog, XmDIALOG_SEPARATOR)); n++;
      mainform = XtCreateManagedWidget("formd", xmFormWidgetClass, enved_dialog, args, n);

      /* the order in which widgets are defined matters a lot here:
       * we need to build from the bottom up so that the graph portion expands
       * when the window is resized (if top-down, the slider at the bottom expands!)
       */

      /* -------- EXP SLIDER AT BOTTOM -------- */
      n = 0;      
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      baseLabel = make_pushbutton_widget("exp:", mainform, args, n);
      XtAddCallback(baseLabel, XmNactivateCallback, base_click_callback, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      s1 = XmStringCreateLocalized((char *)"1.000");
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, baseLabel); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, baseLabel); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      /*      XtSetArg(args[n], XmNrecomputeSize, false); n++; */
      XtSetArg(args[n], XmNlabelString, s1); n++;
      baseValue = XtCreateManagedWidget("base-label", xmLabelWidgetClass, mainform, args, n);
      XmStringFree(s1);

      /* -------- filter order -------- */
      n = 0;      
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNcolumns, 3); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNheight, 24); n++;
      XtSetArg(args[n], XmNresizeWidth, false); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNmarginHeight, 0); n++;
      XtSetArg(args[n], XmNmarginBottom, 0); n++;
      snprintf(str, LABEL_BUFFER_SIZE, "%d", enved_filter_order(ss));
      XtSetArg(args[n], XmNvalue, str); n++;
      orderL = make_textfield_widget("orderL", mainform, args, n, ACTIVATABLE, NO_COMPLETER);
      XtAddCallback(orderL, XmNactivateCallback, order_activate_callback, NULL);

      /* -------- fft/fir choice -------- */
      n = 0;      
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, orderL); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      fir_button = make_pushbutton_widget((char *)((is_FIR) ? "fir" : "fft"), mainform, args, n);
      XtAddCallback(fir_button, XmNactivateCallback, FIR_click_callback, NULL);

      /* -------- exp base scale -------- */
      n = 0;      
      XtSetArg(args[n], XmNbackground, ss->position_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, baseLabel); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, baseValue); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, fir_button); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, BASE_MAX); n++;
      XtSetArg(args[n], XmNvalue, BASE_MID); n++;
      XtSetArg(args[n], XmNincrement, 1); n++;
      XtSetArg(args[n], XmNpageIncrement, 1); n++;
      XtSetArg(args[n], XmNdragCallback, n1 = make_callback_list(base_drag_callback, NULL)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n2 = make_callback_list(base_valuechanged_callback, NULL)); n++;
      baseScale = XtCreateManagedWidget("expscl", xmScrollBarWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, baseScale); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNmargin, LINE_MARGIN); n++;
      XtSetArg(args[n], XmNheight, LINE_MARGIN); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      XtSetArg(args[n], XmNheight, 5); n++;
      baseSep = XtCreateManagedWidget("snd-rec-sep", xmSeparatorWidgetClass, mainform, args, n);

      /* -------- AMP ENV NAME -------- */
      n = 0;
      s1 = XmStringCreateLocalized((char *)"amp env:");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, baseSep); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      nameL = XtCreateManagedWidget("nameL", xmLabelWidgetClass, mainform, args, n);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, baseSep); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, nameL); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      textL = make_textfield_widget("textL", mainform, args, n, ACTIVATABLE, add_completer_func(env_name_completer, NULL));
      XtAddCallback(textL, XmNactivateCallback, enved_text_activate_callback, NULL);


      /* -------- dB, GRAPH ('wave') AND CLIP BUTTONS -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, baseSep); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      if (ss->toggle_size > 0) {XtSetArg(args[n], XmNindicatorSize, ss->toggle_size); n++;}
      dB_button = make_togglebutton_widget("dB", mainform, args, n);
      XtAddCallback(dB_button, XmNvalueChangedCallback, dB_button_callback, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, baseSep); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, dB_button); n++;
      if (ss->toggle_size > 0) {XtSetArg(args[n], XmNindicatorSize, ss->toggle_size); n++;}
      graph_button = make_togglebutton_widget("wave", mainform, args, n);
      XtAddCallback(graph_button, XmNvalueChangedCallback, graph_button_callback, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, baseSep); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, graph_button); n++;
      if (ss->toggle_size > 0) {XtSetArg(args[n], XmNindicatorSize, ss->toggle_size); n++;}
      clip_button = make_togglebutton_widget("clip", mainform, args, n);
      XtAddCallback(clip_button, XmNvalueChangedCallback, clip_button_callback, NULL);

      /* -------- BREAKPOINT DATA DISPLAY LABEL -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, baseSep); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, textL); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, clip_button); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNlabelType, XmSTRING); n++;
      brkptL = XtCreateManagedWidget("         ", xmLabelWidgetClass, mainform, args, n);

      /* -------- SPACERS TO DIVIDE WINDOW IN TWO -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, textL); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNheight, 4); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      spacer = XtCreateManagedWidget("spacer", xmSeparatorWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, spacer); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNseparatorType, XmSHADOW_ETCHED_IN); n++;
      spacer1 = XtCreateManagedWidget("spacer1", xmSeparatorWidgetClass, mainform, args, n);
      /* second separator needed because marginTop seems to be broken or non-existent for these widgets */

      /* -------- WINDOW LEFT WIDGET HOLDER -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, spacer1); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      aform = XtCreateManagedWidget("aform", xmFormWidgetClass, mainform, args, n);

      /* -------- BUTTON BOX AT TOP LEFT -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->zoom_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNshadowThickness, 4); n++;
      XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_IN); n++;
      colF = XtCreateManagedWidget("env-button-frame", xmFrameWidgetClass, aform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      colB = XtCreateManagedWidget("env-button-holder", xmFormWidgetClass, colF, args, n);

      /* VIEW ENVS */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;	
      show_button = XtCreateManagedWidget("view envs", xmPushButtonWidgetClass, colB, args, n);
      XtAddCallback(show_button, XmNactivateCallback, show_button_pressed, NULL);

      /* SAVE PRINT */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;	
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, show_button); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      save_button = XtCreateManagedWidget("define it", xmPushButtonWidgetClass, colB, args, n);

      XtAddCallback(save_button, XmNactivateCallback, save_button_pressed, NULL);

      /* REVERT */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;	
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, save_button); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      revert_button = XtCreateManagedWidget("revert", xmPushButtonWidgetClass, colB, args, n);

      XtAddCallback(revert_button, XmNactivateCallback, revert_button_pressed, NULL);


      /* UNDO REDO */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;	
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, revert_button); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 50); n++;
      undo_button = XtCreateManagedWidget("undo", xmPushButtonWidgetClass, colB, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;	
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, undo_button); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, undo_button); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      redo_button = XtCreateManagedWidget("redo", xmPushButtonWidgetClass, colB, args, n);

      XtAddCallback(undo_button, XmNactivateCallback, undo_button_pressed, NULL);
      XtAddCallback(redo_button, XmNactivateCallback, redo_button_pressed, NULL);


      /* AMP FLT SRC */
      /* enved_function (target) choice (a row of three push buttons that acts like a "radio box") */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->yellow); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;	
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, undo_button); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 33); n++;
      amp_button = XtCreateManagedWidget("amp", xmPushButtonWidgetClass, colB, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->yellow); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;	
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, amp_button); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, amp_button); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 67); n++;
      flt_button = XtCreateManagedWidget("flt", xmPushButtonWidgetClass, colB, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->yellow); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;	
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, flt_button); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, flt_button); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      src_button = XtCreateManagedWidget("src", xmPushButtonWidgetClass, colB, args, n);

      XtAddCallback(flt_button, XmNactivateCallback, freq_button_callback, NULL);
      XtAddCallback(amp_button, XmNactivateCallback, amp_button_callback, NULL);
      XtAddCallback(src_button, XmNactivateCallback, src_button_callback, NULL);

      /* SELECTION */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, amp_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      selection_button = make_pushbutton_widget("selection", colB, args, n);

      XtAddCallback(selection_button, XmNactivateCallback, selection_button_pressed, NULL);


      /* -------- ENV LIST AT LEFT UNDER BUTTONS -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, colF); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNshadowThickness, 4); n++;
      XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_IN); n++;
      colE = XtCreateManagedWidget("env-list-frame", xmFrameWidgetClass, aform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      colD = XtCreateManagedWidget("env-list-holder", xmFormWidgetClass, colE, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      screnvname = XtCreateManagedWidget("envs:", xmLabelWidgetClass, colD, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, screnvname); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      if (ss->listener_fontlist) 
	{
	  XtSetArg(args[n], XmNfontList, 0); n++;
	  XtSetArg(args[n], XM_FONT_RESOURCE, ss->listener_fontlist); n++;
	  use_listener_font = true;
	}
      screnvlst = XmCreateScrolledList(colD, (char *)"scrolled-env-list", args, n);
      XtManageChild(screnvlst); 
      XtAddCallback(screnvlst, XmNbrowseSelectionCallback, env_browse_callback, NULL);
      map_over_children(screnvlst, set_main_color_of_widget);
      if (enved_all_envs_top() > 0) make_scrolled_env_list();

      /* -------- MAIN GRAPH -------- */

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->graph_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, spacer1 /* textL */); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, aform); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNheight, 350); n++;
      XtSetArg(args[n], XmNallowResize, true); n++;
      drawer = XtCreateManagedWidget("drawer", xmDrawingAreaWidgetClass, mainform, args, n);

      gv.function = GXcopy;
      XtVaGetValues(drawer, XmNbackground, &gv.background, XmNforeground, &gv.foreground, NULL);
      gc1 = XtGetGC(drawer, GCForeground | GCFunction, &gv);
      gv.foreground = ss->red;
      rgc = XtGetGC(drawer, GCBackground | GCForeground | GCFunction, &gv);
      gv.foreground = ss->enved_waveform_color;
      ggc = XtGetGC(drawer, GCBackground | GCForeground | GCFunction, &gv);

      XtManageChild(enved_dialog); /* needed so that window is valid when resize callback is invoked */
      apply_button = XmMessageBoxGetChild(enved_dialog, XmDIALOG_OK_BUTTON);
      cancel_button = XmMessageBoxGetChild(enved_dialog, XmDIALOG_CANCEL_BUTTON);
      cancelling = true;

      XtAddCallback(drawer, XmNresizeCallback, drawer_resize, NULL);
      XtAddCallback(drawer, XmNexposeCallback, drawer_resize, NULL);

      XtAddEventHandler(drawer, ButtonPressMask, false, drawer_button_press, NULL);
      XtAddEventHandler(drawer, ButtonMotionMask, false, drawer_button_motion, NULL);
      XtAddEventHandler(drawer, ButtonReleaseMask, false, drawer_button_release, NULL);

      if (enved_all_envs_top() == 0)
	set_sensitive(show_button, false);
      set_sensitive(revert_button, false);
      set_sensitive(undo_button, false);
      set_sensitive(redo_button, false);
      set_sensitive(save_button, false);
      if (!(selection_is_active())) 
	set_sensitive(selection_button, false);

      XmToggleButtonSetState(clip_button, (Boolean)(enved_clipping(ss)), false);
      XmToggleButtonSetState(graph_button, (Boolean)(enved_with_wave(ss)), false);
      XmToggleButtonSetState(dB_button, (Boolean)(enved_in_dB(ss)), false);

      free(n1);
      free(n2);

      reflect_apply_state();
      reflect_segment_state();
      reflect_sound_state();

      set_dialog_widget(ENVED_DIALOG, enved_dialog);

      add_reflect_enved_hook();
    }
  else raise_dialog(enved_dialog);
  if (!XtIsManaged(enved_dialog)) 
    XtManageChild(enved_dialog);
  active_channel = current_channel();
  return(enved_dialog);
}


void set_enved_clipping(bool val) 
{
  in_set_enved_clipping(val); 
  if (enved_dialog) 
    XmToggleButtonSetState(clip_button, (Boolean)val, false);
}


void reflect_enved_style(void)
{
  reflect_segment_state();
}


void set_enved_target(enved_target_t val) 
{
  in_set_enved_target(val); 
  if (enved_dialog) 
    reflect_apply_state();
}


void set_enved_with_wave(bool val) 
{
  in_set_enved_with_wave(val); 
  if (enved_dialog) 
    XmToggleButtonSetState(graph_button, (Boolean)val, false);
}


void set_enved_in_dB(bool val) 
{
  in_set_enved_in_dB(val);
  if (enved_dialog) 
    XmToggleButtonSetState(dB_button, (Boolean)val, false);
}


void set_enved_base(mus_float_t val) 
{
  in_set_enved_base(val); 
  if (enved_dialog) 
    reflect_changed_base(val);
}


bool enved_dialog_is_active(void)
{
  return((enved_dialog) && 
	 (XtIsManaged(enved_dialog)));
}


void set_enved_filter_order(int order)
{
  if ((order > 0) && (order < 2000))
    {
      if (order & 1) 
	{in_set_enved_filter_order(order + 1);}
      else {in_set_enved_filter_order(order);}
      if (enved_dialog)
	{
	  widget_int_to_text(orderL, enved_filter_order(ss));
	  if ((enved_dialog) && 
	      (enved_target(ss) == ENVED_SPECTRUM) && 
	      (enved_with_wave(ss)) && (!showing_all_envs)) 
	    env_redisplay();
	}
    }
}


void enved_reflect_selection(bool on)
{
  if ((enved_dialog) && (!within_selection_src))
    {
      set_sensitive(selection_button, on);
      if ((apply_to_selection) && (!on))
	{
	  apply_to_selection = false;
	  we_turned_selection_off = true;
	}
      if ((on) && (we_turned_selection_off))
	{
	  apply_to_selection = true;
	}
      XmChangeColor(selection_button, (apply_to_selection) ? ((Pixel)ss->yellow) : ((Pixel)ss->highlight_color));
      if ((enved_target(ss) != ENVED_SPECTRUM) && 
	  (enved_with_wave(ss)) && 
	  (!showing_all_envs)) 
	env_redisplay();
    }
}



void color_enved_waveform(Pixel pix)
{
  if (enved_dialog)
    {
      XSetForeground(main_display(ss), ggc, pix);
      if ((enved_with_wave(ss)) && 
	  (enved_dialog)) 
	env_redisplay();
    }
}


static Xen g_enved_envelope(void)
{
  #define H_enved_envelope "(" S_enved_envelope "): current envelope editor displayed (active) envelope"
  return(env_to_xen(active_env));
}


static Xen g_set_enved_envelope(Xen e)
{
  Xen_check_type(Xen_is_list(e) || Xen_is_string(e) || Xen_is_symbol(e), e, 1, S_set S_enved_envelope, "a list, symbol, or string");
  if (active_env) active_env = free_env(active_env);
  if ((Xen_is_string(e)) || (Xen_is_symbol(e)))
    active_env = name_to_env((Xen_is_string(e)) ? Xen_string_to_C_string(e) : Xen_symbol_to_C_string(e)); /* xen_to_env in name_to_env, so no copy */
  else active_env = xen_to_env(e);
  if ((!active_env) && (!(Xen_is_list(e))))
    Xen_error(Xen_make_error_type("no-such-envelope"),
	      Xen_list_2(C_string_to_Xen_string(S_set S_enved_envelope ": bad envelope arg: ~A"),
			 e));
  if (enved_dialog) 
    env_redisplay();
  return(e);
}


static Xen g_enved_filter(void)
{
  #define H_enved_filter "(" S_enved_filter "): envelope editor FIR/FFT filter choice (" PROC_TRUE ": FIR)"
  return(C_bool_to_Xen_boolean(is_FIR));
}


static Xen g_set_enved_filter(Xen type)
{
  Xen_check_type(Xen_is_boolean(type), type, 1, S_set S_enved_filter, "boolean");
  is_FIR = Xen_boolean_to_C_bool(type);
  if (fir_button)
    set_label(fir_button, (is_FIR) ? "fir" : "fft");
  return(type);
}


/* Transform settings dialog */


static Widget transform_dialog = NULL; /* main dialog shell */
static Widget type_list, size_list, wavelet_list, window_list;
static Widget beta_scale, alpha_scale, start_scale, end_scale, alpha_number, beta_number, start_number, end_number;  
static Widget db_button, peaks_button, logfreq_button, sono_button, spectro_button, normo_button, normalize_button, selection_button1, phases_button;
static Widget graph_label, graph_drawer;
static Widget peak_txt, db_txt, freq_base_txt;
static Widget error_frame1, error_label1;

#define NUM_TRANSFORM_SIZES 15
static const char *transform_size_names[NUM_TRANSFORM_SIZES] = 
  {"32", "64", "128", "256", "512", "1024", "2048", "4096", "8192", "16384", "65536", "262144", "1048576", "4194304    ", "16777216"};
static mus_long_t transform_sizes[NUM_TRANSFORM_SIZES] = 
  {32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 65536, 262144, 1048576, 4194304, 16777216};



/* ---------------- fft window graph ---------------- */

static GC gc2, fgc;

#define GRAPH_SIZE 128
static mus_float_t graph_data[GRAPH_SIZE]; /* fft window graph in transform options dialog */
static mus_float_t graph_fftr[GRAPH_SIZE * 2];
static mus_float_t graph_ffti[GRAPH_SIZE * 2];
/* I goofed around with making the graph size dependent on the drawer's width, but there's really nothing gained */
/*   also tried linear/db+min-dB distinction, but linear looks dumb and min-dB is a bother */

static mus_float_t fp_dB(mus_float_t py)
{
  return((py <= ss->lin_dB) ? 0.0 : (1.0 - (20.0 * log10(py) / min_dB(ss))));
}


static int local_grf_x(double val, axis_info *ap)
{
  if (val >= ap->x1) return(ap->x_axis_x1);
  if (val <= ap->x0) return(ap->x_axis_x0);
  return((int)(ap->x_base + val * ap->x_scale));
}


static int local_grf_y(mus_float_t val, axis_info *ap)
{
  if (val >= ap->y1) return(ap->y_axis_y1);
  if (val <= ap->y0) return(ap->y_axis_y0);
  return((int)(ap->y_base + val * ap->y_scale));
}


static axis_info *axis_ap = NULL;

static void graph_redisplay(void)
{
  /* fft_window(ss) is the current choice */
  int ix0, iy0, ix1, iy1, i;
  mus_float_t xincr, x;
  graphics_context *ax;

  if (!axis_ap) 
    {
      axis_ap = (axis_info *)calloc(1, sizeof(axis_info));
      ax = (graphics_context *)calloc(1, sizeof(graphics_context));
      axis_ap->ax = ax;
      ax->dp = XtDisplay(graph_drawer);
      ax->wn = XtWindow(graph_drawer);
    }
  else ax = axis_ap->ax;

  axis_ap->xmin = 0.0;
  axis_ap->xmax = 1.0;
  axis_ap->x_ambit = 1.0;
  axis_ap->x0 = 0.0;
  axis_ap->x1 = 1.0;

  if (axis_ap->xlabel) free(axis_ap->xlabel);
  if (fft_beta_max(fft_window(ss)) != 1.0)
    axis_ap->xlabel = mus_format("(%d, beta: %.2f)", GRAPH_SIZE, fft_beta_max(fft_window(ss)) * fft_window_beta(ss));
  else axis_ap->xlabel = mus_format("(%d)", GRAPH_SIZE);

  if (fft_window(ss) == MUS_FLAT_TOP_WINDOW)
    {
      axis_ap->ymin = -0.1;
      axis_ap->ymax = 1.0;
      axis_ap->y_ambit = 1.1;
      axis_ap->y0 = -0.1;
      axis_ap->y1 = 1.0;
    }
  else 
    {
      axis_ap->ymin = 0.0;
      axis_ap->ymax = 1.0;
      axis_ap->y_ambit = 1.0;
      axis_ap->y0 = 0.0;
      axis_ap->y1 = 1.0;
    }

  axis_ap->width = widget_width(graph_drawer);
  axis_ap->window_width = axis_ap->width;
  axis_ap->y_offset = 0;
  axis_ap->height = widget_height(graph_drawer);
  axis_ap->graph_x0 = 0;

  clear_window(ax);
  ax->gc = gc2;
  make_axes_1(axis_ap, X_AXIS_IN_SECONDS, 1 /* "srate" */, SHOW_ALL_AXES, NOT_PRINTING, WITH_X_AXIS, NO_GRID, WITH_LINEAR_AXES, grid_density(ss));

  ix1 = local_grf_x(0.0, axis_ap);
  iy1 = local_grf_y(graph_data[0], axis_ap);
  xincr = 1.0 / (mus_float_t)GRAPH_SIZE;

  for (i = 1, x = xincr; i < GRAPH_SIZE; i++, x += xincr)
    {
      ix0 = ix1;
      iy0 = iy1;
      ix1 = local_grf_x(x, axis_ap);
      iy1 = local_grf_y(graph_data[i], axis_ap);
      XDrawLine(ax->dp, ax->wn, gc2, ix0, iy0, ix1, iy1);
    }

  ax->gc = fgc;
  ix1 = local_grf_x(0.0, axis_ap);
  iy1 = local_grf_y(graph_fftr[0], axis_ap);
  xincr = 1.0 / (mus_float_t)GRAPH_SIZE;

  for (i = 1, x = xincr; i < GRAPH_SIZE; i++, x += xincr)
    {
      ix0 = ix1;
      iy0 = iy1;
      ix1 = local_grf_x(x, axis_ap);
      if (fft_log_magnitude(ss))
	iy1 = local_grf_y(fp_dB(graph_fftr[i]), axis_ap);
      else iy1 = local_grf_y(graph_fftr[i], axis_ap);
      XDrawLine(ax->dp, ax->wn, fgc, ix0, iy0, ix1, iy1);
    }
}


static void get_fft_window_data(void)
{
  int i;
  mus_make_fft_window_with_window(fft_window(ss), GRAPH_SIZE, 
				  fft_window_beta(ss) * fft_beta_max(fft_window(ss)), 
				  fft_window_alpha(ss), graph_data);
  mus_clear_floats(graph_fftr, GRAPH_SIZE * 2);
  mus_clear_floats(graph_ffti, GRAPH_SIZE * 2);
  mus_copy_floats(graph_fftr, graph_data, GRAPH_SIZE);
  mus_spectrum(graph_fftr, graph_ffti, NULL, GRAPH_SIZE * 2, MUS_SPECTRUM_IN_DB);
  for (i = 0; i < GRAPH_SIZE; i++)
    graph_fftr[i] = (graph_fftr[i] + 80.0) / 80.0; /* min dB here is -80 */
}


static void widget_float_to_text(Widget w, mus_float_t val)
{
  char *str;
  str = (char *)calloc(16, sizeof(char));
  snprintf(str, 16, "%.2f", val);
  XmTextFieldSetString(w, str);
  free(str);
}



/* ---------------- errors ---------------- */

static void clear_fft_error(void)
{
  if ((error_frame1) && (XtIsManaged(error_frame1)))
    XtUnmanageChild(error_frame1);
}


static void unpost_fft_error(XtPointer data, XtIntervalId *id)
{
  clear_fft_error();
}


static void errors_to_fft_text(const char *msg, void *data)
{
  int lines = 0;
  XmString label;
  label = multi_line_label(msg, &lines);
  XtVaSetValues(error_label1, 
		XmNlabelString, label, 
		XmNheight, lines * 20,
		NULL);
  XtVaSetValues(error_frame1, XmNheight, lines * 20, NULL);
  XmStringFree(label);
  XtManageChild(error_frame1);
  /* since the offending text is automatically overwritten, we can't depend on subsequent text modify callbacks
   *   to clear things, so we'll just use a timer
   */
  XtAppAddTimeOut(main_app(ss),
		  5000,
		  (XtTimerCallbackProc)unpost_fft_error,
		  NULL);
}



/* ---------------- transform size ---------------- */

static void chans_transform_size(chan_info *cp, mus_long_t size)
{
  cp->transform_size = size;
  if (cp->fft) 
    cp->fft->size = size;
}


void set_transform_size(mus_long_t val)
{
  for_each_chan(force_fft_clear);
  in_set_transform_size(val);
  for_each_chan_with_mus_long_t(chans_transform_size, val);
  if (transform_dialog)
    {
      int i;
      for (i = 0; i < NUM_TRANSFORM_SIZES; i++)
	if (transform_sizes[i] == val)
	  {
	    XmListSelectPos(size_list, i + 1, false);
	    break;
	  }
    }
  if (!(ss->graph_hook_active)) for_each_chan(calculate_fft);
}


static void size_browse_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)info;
  for_each_chan(force_fft_clear);
  in_set_transform_size(transform_sizes[cbs->item_position - 1]);
  for_each_chan_with_mus_long_t(chans_transform_size, transform_size(ss));
  for_each_chan(calculate_fft);
  set_label(graph_label, mus_fft_window_name(fft_window(ss)));
}


/* ---------------- wavelet choice ---------------- */

static void chans_wavelet_type(chan_info *cp, int value)
{
  cp->wavelet_type = value;
}


void set_wavelet_type(int val)
{
  if (transform_dialog) XmListSelectPos(wavelet_list, val, false);
  in_set_wavelet_type(val);
  for_each_chan_with_int(chans_wavelet_type, val);
  if ((transform_type(ss) == WAVELET) && 
      (!(ss->graph_hook_active))) 
    for_each_chan(calculate_fft);
}


static void wavelet_browse_callback(Widget w, XtPointer context, XtPointer info) 
{
  int val;
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)info;
  in_set_wavelet_type(val = (cbs->item_position - 1)); /* make these numbers 0-based as in mus.lisp */
  for_each_chan_with_int(chans_wavelet_type, val);
  if (transform_type(ss) == WAVELET)
    for_each_chan(calculate_fft);
}


/* ---------------- fft window choice ---------------- */

static void highlight_alpha_beta_scales(mus_fft_window_t val)
{
  if (fft_window_beta_in_use(val))
    {
      XtVaSetValues(beta_scale, XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(beta_number, XmNbackground, ss->highlight_color, NULL);
    }
  else 
    {
      XtVaSetValues(beta_scale, XmNbackground, ss->basic_color, NULL);
      XtVaSetValues(beta_number, XmNbackground, ss->basic_color, NULL);
    }

  if (fft_window_alpha_in_use(val))
    {
      XtVaSetValues(alpha_scale, XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(alpha_number, XmNbackground, ss->highlight_color, NULL);
    }
  else 
    {
      XtVaSetValues(alpha_scale, XmNbackground, ss->basic_color, NULL);
      XtVaSetValues(alpha_number, XmNbackground, ss->basic_color, NULL);
    }
}


void set_fft_window(mus_fft_window_t val)
{
  in_set_fft_window(val);
  if (!(ss->graph_hook_active)) for_each_chan(calculate_fft);
  if (transform_dialog)
    {
      XmListSelectPos(window_list, (int)val + 1, false);
      set_label(graph_label, mus_fft_window_name(val));
      get_fft_window_data();
      if (XtIsManaged(transform_dialog))
	graph_redisplay();
      highlight_alpha_beta_scales(val);
    }
}


static void window_browse_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)info;
  mus_fft_window_t fft_window_choice;

  fft_window_choice = (mus_fft_window_t)(cbs->item_position - 1); /* make these numbers 0-based as in mus.lisp */

  in_set_fft_window(fft_window_choice);
  for_each_chan(calculate_fft);
  set_label(graph_label, mus_fft_window_name(fft_window(ss)));
  get_fft_window_data();
  graph_redisplay();
  highlight_alpha_beta_scales(fft_window_choice);
}



/* ---------------- transform choice ---------------- */

static void chans_transform_type(chan_info *cp, int value) 
{
  cp->transform_type = value;
}


void set_transform_type(int val)
{
  if (is_transform(val))
    {
      if (!(ss->graph_hook_active)) for_each_chan(force_fft_clear);
      in_set_transform_type(val);
      for_each_chan_with_int(chans_transform_type, val);
      if (!(ss->graph_hook_active)) 
	for_each_chan(calculate_fft);
      if (transform_dialog) XmListSelectPos(type_list, transform_type_to_position(val) + 1, false);
    }
}


static void transform_type_browse_callback(Widget w, XtPointer context, XtPointer info) 
{
  int type;
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)info;
  type = transform_position_to_type(cbs->item_position - 1);
  for_each_chan(force_fft_clear);
  in_set_transform_type(type);
  for_each_chan_with_int(chans_transform_type, type);
  for_each_chan(calculate_fft);
}


void make_transform_type_list(void)
{
  int num;
  num = max_transform_type();
  if (transform_dialog)
    {
      XmString *types;
      int i, j;
      types = (XmString *)calloc(num, sizeof(XmString));
      for (i = 0, j = 0; i < num; i++) 
	if (is_transform(i))
	  {
	    set_transform_position(i, j);
	    types[j++] = XmStringCreateLocalized((char *)transform_name(i)); 
	  }
      XtVaSetValues(type_list, 
		    XmNitems, types, 
		    XmNitemCount, j,
		    XmNvisibleItemCount, 6, 
		    NULL);
      for (i = 0; i < j; i++) 
	XmStringFree(types[i]);
      free(types);
    }
}



/* ---------------- transform "graph type" (i.e. sonogram etc) ---------------- */

void set_transform_graph_type(graph_type_t val)
{
  in_set_transform_graph_type(val);
  if (transform_dialog)
    switch (val)
      {
      case GRAPH_ONCE:
	XmToggleButtonSetState(normo_button, true, false);
	XmToggleButtonSetState(spectro_button, false, false);
	XmToggleButtonSetState(sono_button, false, false);
	break;
      case GRAPH_AS_SONOGRAM:
	XmToggleButtonSetState(normo_button, false, false);
	XmToggleButtonSetState(spectro_button, false, false);
	XmToggleButtonSetState(sono_button, true, false);
	break;
      case GRAPH_AS_SPECTROGRAM:
	XmToggleButtonSetState(normo_button, false, false);
	XmToggleButtonSetState(spectro_button, true, false);
	XmToggleButtonSetState(sono_button, false, false);
	break;
      case GRAPH_AS_WAVOGRAM:
	break;
      }
  if (!(ss->graph_hook_active)) 
    for_each_chan(calculate_fft);
}


static void graph_transform_once_callback(Widget w, XtPointer context, XtPointer info)
{
  graph_type_t old_type;
  old_type = transform_graph_type(ss);
  XmToggleButtonSetState(normo_button, true, false);
  XmToggleButtonSetState(sono_button, false, false);
  XmToggleButtonSetState(spectro_button, false, false);
  in_set_transform_graph_type(GRAPH_ONCE);
  if (old_type != GRAPH_ONCE)
    for_each_chan(calculate_fft);
}


static void sonogram_callback(Widget w, XtPointer context, XtPointer info)
{
  graph_type_t old_type;
  old_type = transform_graph_type(ss);
  XmToggleButtonSetState(sono_button, true, false);
  XmToggleButtonSetState(normo_button, false, false);
  XmToggleButtonSetState(spectro_button, false, false);
  in_set_transform_graph_type(GRAPH_AS_SONOGRAM);
  if (old_type != GRAPH_AS_SONOGRAM)
    for_each_chan(calculate_fft);
}


static void spectrogram_callback(Widget w, XtPointer context, XtPointer info)
{
  graph_type_t old_type;
  old_type = transform_graph_type(ss);
  XmToggleButtonSetState(spectro_button, true, false);
  XmToggleButtonSetState(normo_button, false, false);
  XmToggleButtonSetState(sono_button, false, false);
  in_set_transform_graph_type(GRAPH_AS_SPECTROGRAM);
  if (old_type != GRAPH_AS_SPECTROGRAM)
    for_each_chan(calculate_fft);
}



/* ---------------- show peaks ---------------- */

static void map_show_transform_peaks(chan_info *cp, bool value) 
{
  cp->show_transform_peaks = value;
}


static void peaks_callback(Widget w, XtPointer context, XtPointer info)
{
  bool val;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  val = (cb->set);
  in_set_show_transform_peaks(val);
  for_each_chan_with_bool(map_show_transform_peaks, val);
  for_each_chan(calculate_fft);
}


void set_show_transform_peaks(bool val)
{
  in_set_show_transform_peaks(val);
  for_each_chan_with_bool(map_show_transform_peaks, val);
  if (transform_dialog) 
    set_toggle_button(peaks_button, val, false, NULL);
  if (!(ss->graph_hook_active)) 
    for_each_chan(calculate_fft);
}


void reflect_peaks_in_transform_dialog(void)
{
  if (transform_dialog)
    widget_int_to_text(peak_txt, max_transform_peaks(ss));
}


static void peaks_activate_callback(Widget w, XtPointer context, XtPointer info)
{
  char *str;
  str = XmTextFieldGetString(w);
  if ((str) && (*str))
    {
      int new_peaks;
      redirect_errors_to(errors_to_fft_text, NULL);
      new_peaks = string_to_int(str, 1, "peaks");
      redirect_errors_to(NULL, NULL);
      if (new_peaks >= 1)
	{
	  set_max_transform_peaks(new_peaks);
	  for_each_chan(calculate_fft);
	}
      else widget_int_to_text(w, max_transform_peaks(ss));
      XtFree(str);
    }
}



/* ---------------- log magnitude ---------------- */

static void chans_fft_log_magnitude(chan_info *cp, bool value)
{
  cp->fft_log_magnitude = value;
  cp->fft_changed = FFT_CHANGE_LOCKED;
}


void set_fft_log_magnitude(bool val)
{
  in_set_fft_log_magnitude(val);
  for_each_chan_with_bool(chans_fft_log_magnitude, val);
  if (transform_dialog) 
    set_toggle_button(db_button, val, false, NULL);
  if (!(ss->graph_hook_active)) 
    for_each_chan(calculate_fft);
}



/* ---------------- dB ---------------- */

static void fft_db_callback(Widget w, XtPointer context, XtPointer info)
{
  bool val;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  val = cb->set;
  in_set_fft_log_magnitude(val);
  graph_redisplay();
  for_each_chan_with_bool(chans_fft_log_magnitude, val);
  for_each_chan(calculate_fft);
}


void reflect_min_db_in_transform_dialog(void)
{
  if (transform_dialog)
    widget_float_to_text(db_txt, min_dB(ss));
}


static void min_db_activate_callback(Widget w, XtPointer context, XtPointer info)
{
  char *str;
  str = XmTextFieldGetString(w);
  if ((str) && (*str))
    {
      mus_float_t new_db;
      redirect_errors_to(errors_to_fft_text, NULL);
      new_db = string_to_mus_float_t(str, -10000.0, "dB");
      redirect_errors_to(NULL, NULL);
      if (new_db < 0.0)
	set_min_db(new_db);
      else widget_float_to_text(w, min_dB(ss));
      XtFree(str);
    }
}



/* ---------------- log frequency ---------------- */

static void chans_fft_log_frequency(chan_info *cp, bool value)
{
  cp->fft_log_frequency = value;
  cp->fft_changed = FFT_CHANGE_LOCKED;
}


static void logfreq_callback(Widget w, XtPointer context, XtPointer info)
{
  bool val;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  val = cb->set;
  in_set_fft_log_frequency(val);
  for_each_chan_with_bool(chans_fft_log_frequency, val);
  for_each_chan(calculate_fft);
}


void set_fft_log_frequency(bool val)
{
  in_set_fft_log_frequency(val);
  for_each_chan_with_bool(chans_fft_log_frequency, val);
  if (transform_dialog)
    set_toggle_button(logfreq_button, val, false, NULL);
  if (!(ss->graph_hook_active)) 
    for_each_chan(calculate_fft);
}


void reflect_log_freq_start_in_transform_dialog(void)
{
  if (transform_dialog)
    widget_float_to_text(freq_base_txt, log_freq_start(ss));
}


static void log_freq_start_activate_callback(Widget w, XtPointer context, XtPointer info)
{
  char *str;
  str = XmTextFieldGetString(w);
  if ((str) && (*str))
    {
      mus_float_t new_lfb;
      redirect_errors_to(errors_to_fft_text, NULL);
      new_lfb = string_to_mus_float_t(str, 0.0, "log freq start");
      redirect_errors_to(NULL, NULL);
      if (new_lfb > 0.0)
	set_log_freq_start(new_lfb);
      else widget_float_to_text(w, log_freq_start(ss));
      XtFree(str);
    }
}




/* ---------------- normalization choice ---------------- */

static void chans_transform_normalization(chan_info *cp, int value)
{
  cp->transform_normalization = (fft_normalize_t)value;
  cp->fft_changed = FFT_CHANGE_LOCKED;
}


static void normalize_callback(Widget w, XtPointer context, XtPointer info)
{
  fft_normalize_t choice;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  choice = (cb->set) ? NORMALIZE_BY_CHANNEL : DONT_NORMALIZE;
  in_set_transform_normalization(choice);
  for_each_chan_with_int(chans_transform_normalization, (int)choice);
  for_each_chan(calculate_fft);
}


void set_transform_normalization(fft_normalize_t val)
{
  in_set_transform_normalization(val);
  for_each_chan_with_int(chans_transform_normalization, (int)val);
  if (transform_dialog) 
    set_toggle_button(normalize_button, (val != DONT_NORMALIZE), false, NULL);
  if (!(ss->graph_hook_active)) 
    for_each_chan(calculate_fft);
}



/* ---------------- show selection transform ---------------- */

static void selection_callback(Widget w, XtPointer context, XtPointer info)
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  in_set_show_selection_transform(cb->set);
  for_each_chan(calculate_fft);
}


void set_show_selection_transform(bool show)
{
  in_set_show_selection_transform(show);
  if (transform_dialog)
    set_toggle_button(selection_button1, show, false, NULL); 
  if (!(ss->graph_hook_active)) 
    for_each_chan(calculate_fft);
}



/* ---------------- show phases (via color) ---------------- */

static void chans_fft_with_phases(chan_info *cp, bool value)
{
  cp->fft_with_phases = value;
  cp->fft_changed = FFT_CHANGE_LOCKED;
}


static void phases_callback(Widget w, XtPointer context, XtPointer info)
{
  bool val;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  val = cb->set;
  in_set_fft_with_phases(val);
  graph_redisplay();
  for_each_chan_with_bool(chans_fft_with_phases, val);
  for_each_chan(calculate_fft);
}


void set_fft_with_phases(bool val)
{
  in_set_fft_with_phases(val);
  for_each_chan_with_bool(chans_fft_with_phases, val);
  if (!(ss->graph_hook_active)) 
    for_each_chan(calculate_fft);
}



/* ---------------- window alpha parameter ---------------- */

static void alpha_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  char alpha_number_buffer[512]; /* 11 before gcc 7.1 */
  mus_float_t alpha;
  
  alpha = (((XmScrollBarCallbackStruct *)info)->value) / 90.0;
  in_set_fft_window_alpha(alpha);
  chans_field(FCP_ALPHA, alpha);

  snprintf(alpha_number_buffer, 512, "alpha:%.3f", alpha);
  set_label(alpha_number, alpha_number_buffer);

  if (fft_window_alpha_in_use(fft_window(ss)))
    {
      get_fft_window_data();
      graph_redisplay();
      if (transform_type(ss) == FOURIER) 
	for_each_chan(calculate_fft);
    }
}

static void set_alpha_scale(mus_float_t val)
{
  char alpha_number_buffer[512];
  XtVaSetValues(alpha_scale, XmNvalue, (int)(val * 90), NULL);
  snprintf(alpha_number_buffer, 512, "alpha:%.3f", val);
  set_label(alpha_number, alpha_number_buffer);
}


void set_fft_window_alpha(mus_float_t val)
{
  in_set_fft_window_alpha(val);
  chans_field(FCP_ALPHA, val);
  if (transform_dialog) 
    {
      set_alpha_scale(val);
      get_fft_window_data();
      if (XtIsManaged(transform_dialog))
	graph_redisplay();
    }
  if (!(ss->graph_hook_active)) 
    for_each_chan(calculate_fft);
}



/* ---------------- window beta parameter ---------------- */

static void beta_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  char beta_number_buffer[12];
  mus_float_t beta;
  
  beta = (((XmScrollBarCallbackStruct *)info)->value) / 90.0;
  in_set_fft_window_beta(beta);
  chans_field(FCP_BETA, beta);

  snprintf(beta_number_buffer, 12, "beta: %.3f", beta);
  set_label(beta_number, beta_number_buffer);

  if (fft_window_beta_in_use(fft_window(ss)))
    {
      get_fft_window_data();
      graph_redisplay();
      if (transform_type(ss) == FOURIER) 
	for_each_chan(calculate_fft);
    }
}


static void set_beta_scale(mus_float_t val)
{
  char beta_number_buffer[12];
  XtVaSetValues(beta_scale, XmNvalue, (int)(val * 90), NULL);
  snprintf(beta_number_buffer, 12, "beta: %.3f", val);
  set_label(beta_number, beta_number_buffer);
}


void set_fft_window_beta(mus_float_t val)
{
  in_set_fft_window_beta(val);
  chans_field(FCP_BETA, val);
  if (transform_dialog) 
    {
      set_beta_scale(val);
      get_fft_window_data();
      if (XtIsManaged(transform_dialog))
	graph_redisplay();
    }
  if (!(ss->graph_hook_active)) 
    for_each_chan(calculate_fft);
}



/* ---------------- spectrum start/end ---------------- */

static void chans_spectrum_changed(chan_info *cp) 
{
  cp->fft_changed = FFT_CHANGE_LOCKED;
  update_graph(cp);
}

static void set_spectrum_start_scale(mus_float_t val)
{
  char start_number_buffer[12];
  XtVaSetValues(start_scale, XmNvalue, (int)(val * 90), NULL);
  snprintf(start_number_buffer, 12, "start:%.3f", val);
  set_label(start_number, start_number_buffer);
}


static void check_spectrum_start(mus_float_t end)
{
  /* don't display chans, but do reset if necessary */
  if (spectrum_start(ss) > end)
    {
      in_set_spectrum_start(end);
      if (transform_dialog)
	set_spectrum_start_scale(end);
      chans_field(FCP_SPECTRUM_START, end);
    }
}

static void check_spectrum_end(mus_float_t start);

void set_spectrum_start(mus_float_t val) 
{
  if (transform_dialog)
    set_spectrum_start_scale(val);
  in_set_spectrum_start(val);
  check_spectrum_end(val);
  chans_field(FCP_SPECTRUM_START, val);
  for_each_chan(chans_spectrum_changed);
}


static void start_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  char start_number_buffer[12];
  mus_float_t start;
  
  start = (((XmScrollBarCallbackStruct *)info)->value) / 90.0;
  snprintf(start_number_buffer, 12, "start:%.3f", start);
  set_label(start_number, start_number_buffer);

  in_set_spectrum_start(start);
  check_spectrum_end(start);
  chans_field(FCP_SPECTRUM_START, start);
  for_each_chan(chans_spectrum_changed);
}


static void set_spectrum_end_scale(mus_float_t val)
{
  char end_number_buffer[12];
  XtVaSetValues(end_scale, XmNvalue, (int)(val * 90), NULL);
  snprintf(end_number_buffer, 12, "end:  %.3f", val);
  set_label(end_number, end_number_buffer);
}

static void check_spectrum_end(mus_float_t start)
{
  /* don't display chans, but do reset if necessary */
  if (spectrum_end(ss) < start)
    {
      in_set_spectrum_end(start);
      if (transform_dialog)
	set_spectrum_end_scale(start);
      chans_field(FCP_SPECTRUM_END, start);
    }
}


void set_spectrum_end(mus_float_t val)
{
  if (transform_dialog)
    set_spectrum_end_scale(val);
  in_set_spectrum_end(val);
  check_spectrum_start(val);
  chans_field(FCP_SPECTRUM_END, val);
  for_each_chan(chans_spectrum_changed);
}


static void end_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  char end_number_buffer[12];
  mus_float_t end;

  end = (((XmScrollBarCallbackStruct *)info)->value) / 90.0;
  snprintf(end_number_buffer, 12, "end:  %.3f", end);
  set_label(end_number, end_number_buffer);

  in_set_spectrum_end(end);
  check_spectrum_start(end);
  chans_field(FCP_SPECTRUM_END, end);
  for_each_chan(chans_spectrum_changed);
}



/* ---------------- dialog buttons etc ---------------- */

static void graph_resize_callback(Widget w, XtPointer context, XtPointer info)
{
  graph_redisplay();
}


static void dismiss_transform_callback(Widget w, XtPointer context, XtPointer info)
{
  if (XmGetFocusWidget(transform_dialog) == XmMessageBoxGetChild(transform_dialog, XmDIALOG_CANCEL_BUTTON))
    XtUnmanageChild(transform_dialog);
}


static void color_orientation_callback(Widget w, XtPointer context, XtPointer info)
{
  make_color_orientation_dialog(true);
}


static void help_transform_callback(Widget w, XtPointer context, XtPointer info)
{
  transform_dialog_help();
}


static void fft_blue_textfield_unfocus_callback(Widget w, XtPointer context, XtPointer info)
{
  XtVaSetValues(w, XmNbackground, ss->lighter_blue, NULL);
  XtVaSetValues(w, XmNcursorPositionVisible, false, NULL);
}


static void fft_blue_mouse_leave_text_callback(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  XtVaSetValues(w, XmNbackground, ss->lighter_blue, NULL);
  XtVaSetValues(w, XmNcursorPositionVisible, false, NULL);
}


static void fft_white_mouse_enter_text_callback(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  XtVaSetValues(w, XmNbackground, ss->text_focus_color, NULL);
  XtVaSetValues(w, XmNcursorPositionVisible, true, NULL);
}



/* ---------------- transform options dialog ---------------- */

#define FRAME_BORDER_WIDTH 6

static bool need_callback = true;

Widget make_transform_dialog(bool managed)
{
  if (!transform_dialog)
    {
      Widget mainform, type_frame, type_form, type_label, size_frame, size_form, size_label, display_frame, display_form, display_label;
      Widget window_frame, window_form, window_label, wavelet_frame, wavelet_form, wavelet_label, graph_frame, graph_form, gsep;
      Widget ab_form, ab_frame, ab_title, ab_sep;
      Widget se_form, se_frame, se_title, se_sep, ok_button;
      XmString s1;
      XmString xhelp, xgo_away, xtitle, bstr, xorient;
      Arg args[32];
      XmString sizes[NUM_TRANSFORM_SIZES];
      XmString wavelets[NUM_WAVELETS];
      XmString windows[MUS_NUM_FFT_WINDOWS];
      XGCValues gv;
      XtCallbackList n1, n2, n3, n4;
      int size_pos = 1;
      int n, i;

      for (i = 0; i < NUM_TRANSFORM_SIZES; i++)
	if (transform_sizes[i] == transform_size(ss))
	  {
	    size_pos = i + 1;
	    break;
	  }
      xgo_away = XmStringCreateLocalized((char *)I_GO_AWAY); /* needed by template dialog */
      xhelp = XmStringCreateLocalized((char *)I_HELP);
      xtitle = XmStringCreateLocalized((char *)"Transform Options");
      xorient = XmStringCreateLocalized((char *)"Color/Orientation");

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNcancelLabelString, xgo_away); n++;
      XtSetArg(args[n], XmNokLabelString, xorient); n++;
      XtSetArg(args[n], XmNhelpLabelString, xhelp); n++;
      XtSetArg(args[n], XmNautoUnmanage, false); n++;
      XtSetArg(args[n], XmNdialogTitle, xtitle); n++;
      XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
      XtSetArg(args[n], XmNnoResize, false); n++;
      XtSetArg(args[n], XmNtransient, false); n++;
      transform_dialog = XmCreateTemplateDialog(main_shell(ss), (char *)"Transform Options", args, n);
      ok_button = XmMessageBoxGetChild(transform_dialog, XmDIALOG_OK_BUTTON);

      XtAddCallback(transform_dialog, XmNcancelCallback, dismiss_transform_callback, NULL);
      /* XtAddCallback(transform_dialog, XmNokCallback, color_orientation_callback, NULL); */ /* <cr> in dialog calls this! */
      XtAddCallback(ok_button, XmNactivateCallback, color_orientation_callback, NULL);
      XtAddCallback(transform_dialog, XmNhelpCallback, help_transform_callback, NULL);
      XmStringFree(xhelp);
      XmStringFree(xgo_away);
      XmStringFree(xtitle);
      XmStringFree(xorient);

      XtVaSetValues(XmMessageBoxGetChild(transform_dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(transform_dialog, XmDIALOG_HELP_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(transform_dialog, XmDIALOG_OK_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(transform_dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(transform_dialog, XmDIALOG_HELP_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(transform_dialog, XmDIALOG_OK_BUTTON), XmNbackground, ss->highlight_color, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, XmMessageBoxGetChild(transform_dialog, XmDIALOG_SEPARATOR)); n++;
      mainform = XtCreateManagedWidget("mainform", xmFormWidgetClass, transform_dialog, args, n);


      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNallowResize, true); n++;
      XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_IN); n++;
      XtSetArg(args[n], XmNshadowThickness, 2); n++;
      error_frame1 = XtCreateManagedWidget("error-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      error_label1 = XtCreateManagedWidget("", xmLabelWidgetClass, error_frame1, args, n);


      /* now 7 or 8 boxes within the main box:
	 
	 type (list)    |  size (list)        |  display (button column)
	 wavelet (list) |  window (list)      |  graph (fft?) of current window
         alpha/beta ------------------------  |
         start/end -------------------------  |
	 
	 each box has a frame, label, and contents
      */

      /* -------- SPECTRUM START/END -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 60); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNborderWidth, FRAME_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      se_frame = XtCreateManagedWidget("se-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      n = attach_all_sides(args, n);
      se_form = XtCreateManagedWidget("se-form", xmFormWidgetClass, se_frame, args, n);
      /* needed because XmFrame only accepts one child */

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;
      se_title = XtCreateManagedWidget("spectrum start/end", xmLabelWidgetClass, se_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, se_title); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNseparatorType, XmSHADOW_ETCHED_IN); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      se_sep = XtCreateManagedWidget("se_sep", xmSeparatorWidgetClass, se_form, args, n);

      n = 0;
      s1 = XmStringCreateLocalized((char *)"start:0.0  ");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, se_sep); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      start_number = XtCreateManagedWidget("start-number", xmLabelWidgetClass, se_form, args, n);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, start_number); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, start_number); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, 100); n++;
      XtSetArg(args[n], XmNvalue, 0); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNdragCallback, n3 = make_callback_list(start_drag_callback, NULL)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n3); n++;
      start_scale = XtCreateManagedWidget("start-scale", xmScrollBarWidgetClass, se_form, args, n);

      n = 0;
      s1 = XmStringCreateLocalized((char *)"end:  1.0  ");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, start_number); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      end_number = XtCreateManagedWidget("end-number", xmLabelWidgetClass, se_form, args, n);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, end_number); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, end_number); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, 100); n++;
      XtSetArg(args[n], XmNvalue, 90); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNdragCallback, n4 = make_callback_list(end_drag_callback, NULL)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n4); n++;
      end_scale = XtCreateManagedWidget("end-scale", xmScrollBarWidgetClass, se_form, args, n);



      /* -------- WINDOW ALPHA/BETA -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 60); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, se_frame); n++;
      XtSetArg(args[n], XmNborderWidth, FRAME_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      ab_frame = XtCreateManagedWidget("ab-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      n = attach_all_sides(args, n);
      ab_form = XtCreateManagedWidget("ab-form", xmFormWidgetClass, ab_frame, args, n);
      /* needed because XmFrame only accepts one child */

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;
      ab_title = XtCreateManagedWidget("window parameter", xmLabelWidgetClass, ab_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, ab_title); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNseparatorType, XmSHADOW_ETCHED_IN); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      ab_sep = XtCreateManagedWidget("ab_sep", xmSeparatorWidgetClass, ab_form, args, n);

      n = 0;
      s1 = XmStringCreateLocalized((char *)"alpha:0.0  ");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, ab_sep); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      alpha_number = XtCreateManagedWidget("alpha-number", xmLabelWidgetClass, ab_form, args, n);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, alpha_number); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, alpha_number); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, 100); n++;
      XtSetArg(args[n], XmNvalue, 0); n++;
      XtSetArg(args[n], XmNheight, 16); n++;

      XtSetArg(args[n], XmNdragCallback, n1 = make_callback_list(alpha_drag_callback, NULL)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n1); n++;
      alpha_scale = XtCreateManagedWidget("alpha-scale", xmScrollBarWidgetClass, ab_form, args, n);


      n = 0;
      s1 = XmStringCreateLocalized((char *)"beta: 0.0  ");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, alpha_number); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      beta_number = XtCreateManagedWidget("beta-number", xmLabelWidgetClass, ab_form, args, n);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, beta_number); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, beta_number); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, 100); n++;
      XtSetArg(args[n], XmNvalue, 0); n++;
      XtSetArg(args[n], XmNheight, 16); n++;

      XtSetArg(args[n], XmNdragCallback, n2 = make_callback_list(beta_drag_callback, NULL)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n2); n++;
      beta_scale = XtCreateManagedWidget("beta-scale", xmScrollBarWidgetClass, ab_form, args, n);


      /* -------- WINDOW -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 30); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNtopPosition, 35); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, ab_frame); n++;
      XtSetArg(args[n], XmNborderWidth, FRAME_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      window_frame = XtCreateManagedWidget("window-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      n = attach_all_sides(args, n);
      window_form = XtCreateManagedWidget("window-form", xmFormWidgetClass, window_frame, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;
      window_label = XtCreateManagedWidget("window", xmLabelWidgetClass, window_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomWidget, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, window_label); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopItemPosition, ((int)fft_window(ss) > 2) ? ((int)fft_window(ss) - 1) : ((int)fft_window(ss) + 1)); n++;
      window_list = XmCreateScrolledList(window_form, (char *)"window-list", args, n);

      XtVaSetValues(window_list, XmNbackground, ss->white, XmNforeground, ss->black, NULL);
      for (i = 0; i < MUS_NUM_FFT_WINDOWS; i++)
	windows[i] = XmStringCreateLocalized((char *)mus_fft_window_name((mus_fft_window_t)i));

      XtVaSetValues(window_list, 
		    XmNitems, windows, 
		    XmNitemCount, MUS_NUM_FFT_WINDOWS, 
		    XmNvisibleItemCount, 8, 
		    NULL);
      for (i = 0; i < MUS_NUM_FFT_WINDOWS; i++) 
	XmStringFree(windows[i]);

      XtManageChild(window_list); 
      XtAddCallback(window_list, XmNbrowseSelectionCallback, window_browse_callback, NULL);


      /* -------- WAVELET -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, window_frame); n++;

      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 60); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNtopPosition, 35); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, ab_frame); n++;
      XtSetArg(args[n], XmNborderWidth, FRAME_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      wavelet_frame = XtCreateManagedWidget("wavelet-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      n = attach_all_sides(args, n);
      wavelet_form = XtCreateManagedWidget("wavelet-form", xmFormWidgetClass, wavelet_frame, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;
      wavelet_label = XtCreateManagedWidget("wavelet", xmLabelWidgetClass, wavelet_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomWidget, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, wavelet_label); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      wavelet_list = XmCreateScrolledList(wavelet_form, (char *)"wavelet-list", args, n);

      XtVaSetValues(wavelet_list, XmNbackground, ss->white, XmNforeground, ss->black, NULL);
      for (i = 0; i < NUM_WAVELETS; i++) 
	wavelets[i] = XmStringCreateLocalized((char *)wavelet_name(i));

      XtVaSetValues(wavelet_list, 
		    XmNitems, wavelets, 
		    XmNitemCount, NUM_WAVELETS, 
		    XmNvisibleItemCount, 8, 
		    NULL);
      for (i = 0; i < NUM_WAVELETS; i++) 
	XmStringFree(wavelets[i]);

      XtManageChild(wavelet_list); 
      XtAddCallback(wavelet_list, XmNbrowseSelectionCallback, wavelet_browse_callback, NULL);


      /* -------- TRANSFORM TYPE -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 30); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNbottomPosition, 35); n++;
      XtSetArg(args[n], XmNborderWidth, FRAME_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      type_frame = XtCreateManagedWidget("type-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      n = attach_all_sides(args, n);
      type_form = XtCreateManagedWidget("type-form", xmFormWidgetClass, type_frame, args, n);
      /* needed because XmFrame only accepts one child */

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;
      type_label = XtCreateManagedWidget("type", xmLabelWidgetClass, type_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, type_label); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      type_list = XmCreateScrolledList(type_form, (char *)"type-list", args, n);

      XtVaSetValues(type_list, XmNbackground, ss->white, XmNforeground, ss->black, NULL);
      make_transform_type_list();

      XtManageChild(type_list); 
      XtAddCallback(type_list, XmNbrowseSelectionCallback, transform_type_browse_callback, NULL);


      /* -------- SIZE -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, type_frame); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 60); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, type_frame); n++;
      XtSetArg(args[n], XmNborderWidth, FRAME_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      size_frame = XtCreateManagedWidget("size-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      n = attach_all_sides(args, n);
      size_form = XtCreateManagedWidget("size-form", xmFormWidgetClass, size_frame, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;
      size_label = XtCreateManagedWidget("size", xmLabelWidgetClass, size_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, size_label); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopItemPosition, (size_pos > 2) ? (size_pos - 2) : size_pos); n++;
      size_list = XmCreateScrolledList(size_form, (char *)"size-list", args, n);

      XtVaSetValues(size_list, XmNbackground, ss->white, XmNforeground, ss->black, NULL);
      for (i = 0; i < NUM_TRANSFORM_SIZES; i++) 
	sizes[i] = XmStringCreateLocalized((char *)transform_size_names[i]);

      XtVaSetValues(size_list, 
		    XmNitems, sizes, 
		    XmNitemCount, NUM_TRANSFORM_SIZES, 
		    XmNvisibleItemCount, 6, 
		    NULL);
      for (i = 0; i < NUM_TRANSFORM_SIZES; i++) 
	XmStringFree(sizes[i]);

      XtManageChild(size_list); 
      XtAddCallback(size_list, XmNbrowseSelectionCallback, size_browse_callback, NULL);


      /* -------- DISPLAY BOX BUTTONS -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, size_frame); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNborderWidth, FRAME_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      display_frame = XtCreateManagedWidget("display-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->zoom_color); n++;
      n = attach_all_sides(args, n);
      display_form = XtCreateManagedWidget("display-form", xmFormWidgetClass, display_frame, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;
      display_label = XtCreateManagedWidget("display", xmLabelWidgetClass, display_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"single transform");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, display_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY); n++;
      normo_button = make_togglebutton_widget("normo-button", display_form, args, n);
      XtAddCallback(normo_button, XmNdisarmCallback, graph_transform_once_callback, NULL);
      XmStringFree(bstr);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"sonogram");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, normo_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY); n++;
      sono_button = make_togglebutton_widget("sono-button", display_form, args, n);
      XtAddCallback(sono_button, XmNdisarmCallback, sonogram_callback, NULL);
      XmStringFree(bstr);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"spectrogram");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sono_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY); n++;
      spectro_button = make_togglebutton_widget("spectro-button", display_form, args, n);
      XtAddCallback(spectro_button, XmNdisarmCallback, spectrogram_callback, NULL);
      XmStringFree(bstr);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"peaks");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 67); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, spectro_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      peaks_button = make_togglebutton_widget("peaks-button", display_form, args, n);
      XtAddCallback(peaks_button, XmNvalueChangedCallback, peaks_callback, NULL);
      XmStringFree(bstr);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNresizeWidth, false); n++;
      XtSetArg(args[n], XmNcolumns, 6); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      /* XtSetArg(args[n], XmNmarginHeight, 1); n++; */
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, peaks_button); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, peaks_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, peaks_button); n++;
      XtSetArg(args[n], XmNborderWidth, 0); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      peak_txt = make_textfield_widget("max-peaks", display_form, args, n, ACTIVATABLE, NO_COMPLETER);
      XtRemoveCallback(peak_txt, XmNlosingFocusCallback, textfield_unfocus_callback, NULL);
      XtAddCallback(peak_txt, XmNlosingFocusCallback, fft_blue_textfield_unfocus_callback, NULL);
      XtAddEventHandler(peak_txt, LeaveWindowMask, false, fft_blue_mouse_leave_text_callback, NULL);
      XtAddEventHandler(peak_txt, EnterWindowMask, false, fft_white_mouse_enter_text_callback, NULL);
      widget_int_to_text(peak_txt, max_transform_peaks(ss));
      XtAddCallback(peak_txt, XmNactivateCallback, peaks_activate_callback, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"dB");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 67); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, peaks_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      db_button = make_togglebutton_widget("db-button", display_form, args, n);
      XtAddCallback(db_button, XmNvalueChangedCallback, fft_db_callback, NULL);
      XmStringFree(bstr);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNresizeWidth, false); n++;
      XtSetArg(args[n], XmNcolumns, 6); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      /* XtSetArg(args[n], XmNmarginHeight, 1); n++; */
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, db_button); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, db_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, db_button); n++;
      XtSetArg(args[n], XmNborderWidth, 0); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      db_txt = make_textfield_widget("db", display_form, args, n, ACTIVATABLE, NO_COMPLETER);
      XtRemoveCallback(db_txt, XmNlosingFocusCallback, textfield_unfocus_callback, NULL);
      XtAddCallback(db_txt, XmNlosingFocusCallback, fft_blue_textfield_unfocus_callback, NULL);
      XtAddEventHandler(db_txt, LeaveWindowMask, false, fft_blue_mouse_leave_text_callback, NULL);
      XtAddEventHandler(db_txt, EnterWindowMask, false, fft_white_mouse_enter_text_callback, NULL);
      widget_float_to_text(db_txt, min_dB(ss));
      XtAddCallback(db_txt, XmNactivateCallback, min_db_activate_callback, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"log freq");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 67); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, db_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      logfreq_button = make_togglebutton_widget("logfreq-button", display_form, args, n);
      XtAddCallback(logfreq_button, XmNvalueChangedCallback, logfreq_callback, NULL);
      XmStringFree(bstr);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNresizeWidth, false); n++;
      XtSetArg(args[n], XmNcolumns, 6); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      /* XtSetArg(args[n], XmNmarginHeight, 1); n++; */
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, logfreq_button); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, logfreq_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, logfreq_button); n++;
      XtSetArg(args[n], XmNborderWidth, 0); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      freq_base_txt = make_textfield_widget("lfb", display_form, args, n, ACTIVATABLE, NO_COMPLETER);
      XtRemoveCallback(freq_base_txt, XmNlosingFocusCallback, textfield_unfocus_callback, NULL);
      XtAddCallback(freq_base_txt, XmNlosingFocusCallback, fft_blue_textfield_unfocus_callback, NULL);
      XtAddEventHandler(freq_base_txt, LeaveWindowMask, false, fft_blue_mouse_leave_text_callback, NULL);
      XtAddEventHandler(freq_base_txt, EnterWindowMask, false, fft_white_mouse_enter_text_callback, NULL);
      widget_float_to_text(freq_base_txt, log_freq_start(ss));
      XtAddCallback(freq_base_txt, XmNactivateCallback, log_freq_start_activate_callback, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"normalize");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, logfreq_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      normalize_button = make_togglebutton_widget("normalize-button", display_form, args, n);
      XtAddCallback(normalize_button, XmNvalueChangedCallback, normalize_callback, NULL);
      XmStringFree(bstr);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"selection");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, normalize_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      selection_button1 = make_togglebutton_widget("selection-button", display_form, args, n);
      XtAddCallback(selection_button1, XmNvalueChangedCallback, selection_callback, NULL);
      XmStringFree(bstr);


      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"with phases");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, selection_button1); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      phases_button = make_togglebutton_widget("phases-button", display_form, args, n);
      XtAddCallback(phases_button, XmNvalueChangedCallback, phases_callback, NULL);
      XmStringFree(bstr);


      
      /* -------- GRAPH -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, wavelet_frame); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, display_frame); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNborderWidth, FRAME_BORDER_WIDTH); n++;
      XtSetArg(args[n], XmNborderColor, ss->basic_color); n++;
      graph_frame = XtCreateManagedWidget("graph-frame", xmFrameWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      n = attach_all_sides(args, n);
      graph_form = XtCreateManagedWidget("graph-form", xmFormWidgetClass, graph_frame, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;
      graph_label = XtCreateManagedWidget("window", xmLabelWidgetClass, graph_form, args, n);
      /* label should change according to what is being displayed */

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, graph_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNseparatorType, XmSHADOW_ETCHED_IN); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      gsep = XtCreateManagedWidget("gsep", xmSeparatorWidgetClass, graph_form, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->graph_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, gsep); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNallowResize, true); n++;
      graph_drawer = XtCreateManagedWidget("graph-drawer", xmDrawingAreaWidgetClass, graph_form, args, n);

      gv.function = GXcopy;
      XtVaGetValues(graph_drawer, XmNbackground, &gv.background, XmNforeground, &gv.foreground, NULL);
      gc2 = XtGetGC(graph_drawer, GCForeground | GCFunction, &gv);

      gv.foreground = ss->enved_waveform_color;
      fgc = XtGetGC(graph_drawer, GCForeground | GCFunction, &gv);

      XmToggleButtonSetState(normo_button, (Boolean)(transform_graph_type(ss) == GRAPH_ONCE), false);
      XmToggleButtonSetState(sono_button, (Boolean)(transform_graph_type(ss) == GRAPH_AS_SONOGRAM), false);
      XmToggleButtonSetState(spectro_button, (Boolean)(transform_graph_type(ss) == GRAPH_AS_SPECTROGRAM), false);
      XmToggleButtonSetState(peaks_button, (Boolean)(show_transform_peaks(ss)), false);
      XmToggleButtonSetState(db_button, (Boolean)(fft_log_magnitude(ss)), false);
      XmToggleButtonSetState(logfreq_button, (Boolean)(fft_log_frequency(ss)), false);
      XmToggleButtonSetState(normalize_button, (Boolean)(transform_normalization(ss) != DONT_NORMALIZE), false);
      XmToggleButtonSetState(selection_button1, (Boolean)(show_selection_transform(ss)), false);
      XmToggleButtonSetState(phases_button, (Boolean)(fft_with_phases(ss)), false);

      /* select current list choices */
      /* display current windowing choice unless wavelet in force */

      XmListSelectPos(type_list, transform_type_to_position(transform_type(ss)) + 1, false);
      XmListSelectPos(wavelet_list, wavelet_type(ss) + 1, false);
      XmListSelectPos(size_list, size_pos, false);
      XmListSelectPos(window_list, (int)fft_window(ss) + 1, false);

      if (spectrum_start(ss) != 0.0) set_spectrum_start_scale(spectrum_start(ss));
      if (spectrum_end(ss) != 1.0) set_spectrum_end_scale(spectrum_end(ss));
      if (fft_window_alpha(ss) != 0.0) set_alpha_scale(fft_window_alpha(ss));
      if (fft_window_beta(ss) != 0.0) set_beta_scale(fft_window_beta(ss));

      highlight_alpha_beta_scales(fft_window(ss));

      free(n1);
      free(n2);
      free(n3);
      free(n4);

      set_dialog_widget(TRANSFORM_DIALOG, transform_dialog);

      XtUnmanageChild(error_frame1);
    }
  else
    {
      if (managed)
	raise_dialog(transform_dialog);
    }
  if (managed)
    {
      if (!XtIsManaged(transform_dialog)) 
	XtManageChild(transform_dialog);
    }
  else XtUnmanageChild(transform_dialog);
  if ((need_callback) && (XtIsManaged(transform_dialog)))
    {
      set_label(graph_label, mus_fft_window_name(fft_window(ss)));
      get_fft_window_data();
      XtAddCallback(graph_drawer, XmNresizeCallback, graph_resize_callback, NULL);
      XtAddCallback(graph_drawer, XmNexposeCallback, graph_resize_callback, NULL);
      need_callback = false;
    }
  return(transform_dialog);
}


bool transform_dialog_is_active(void)
{
  return((transform_dialog) && 
	 (XtIsManaged(transform_dialog)));
}


/* -------- region browser -------- */

typedef struct {
  Widget rw, nm, pl;
  int pos;
} regrow;

static Widget region_dialog = NULL, region_list, region_grf;
static regrow **region_rows = NULL;
static int region_rows_size = 0;
static snd_info *rsp = NULL;
static int current_region = -1;
static Widget reg_srtxt, reg_lentxt, reg_chntxt, reg_maxtxt;
static Widget region_ww = NULL;
static Widget mix_button = NULL, save_as_button = NULL, insert_button = NULL;
static regrow *region_row(int n);


static void set_current_region(int rg)
{
  bool reg_ok = false;
  current_region = rg;
  reflect_region_in_save_as_dialog();
  if (rg >= 0)
    reg_ok = region_ok(region_list_position_to_id(rg));
  if (save_as_button) XtSetSensitive(save_as_button, reg_ok);
  if (mix_button) XtSetSensitive(mix_button, reg_ok);
  if (insert_button) XtSetSensitive(insert_button, reg_ok);
}


void reflect_regions_in_region_browser(void)
{
  if (rsp)
    {
      uint32_t i;
      rsp->active = true;
      if (rsp->chans)
	for (i = 0; i < rsp->nchans; i++)
	  rsp->chans[i]->active = CHANNEL_HAS_AXES;
    }
}


void reflect_no_regions_in_region_browser(void)
{
  if (rsp)
    {
      uint32_t i;
      rsp->active = false;
      if (rsp->chans)
	for (i = 0; i < rsp->nchans; i++)
	  rsp->chans[i]->active = CHANNEL_INACTIVE;
    }
}


static void region_update_graph(chan_info *cp)
{
  if (current_region == -1) return;
  rsp->nchans = region_chans(region_list_position_to_id(current_region));
  if (rsp->nchans == 0) return;
  update_graph(cp);
  rsp->nchans = 1;
}


void reflect_region_graph_style(void)
{
  if (current_region == -1) return;
  if ((rsp) &&
      (rsp->chans) &&
      (rsp->chans[0]) &&
      (region_dialog_is_active()))
    {
      rsp->chans[0]->time_graph_style = region_graph_style(ss);
      rsp->chans[0]->dot_size = dot_size(ss);
      /* update_graph(rsp->chans[0]); */
      update_region_browser(true);
    }
}


static void unhighlight_region(void)
{
  if (current_region != -1)
    {
      regrow *oldr;
      oldr = region_row(current_region);
      XtVaSetValues(oldr->rw, XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(oldr->nm, XmNbackground, ss->highlight_color, NULL);
    }
}


static void highlight_region(void)
{
  if (current_region != -1)
    {
      regrow *oldr;
      oldr = region_row(current_region);
      XtVaSetValues(oldr->rw, XmNbackground, ss->zoom_color, NULL);
      XtVaSetValues(oldr->nm, XmNbackground, ss->zoom_color, NULL);
    }
}


static void make_region_labels(file_info *hdr)
{
  char *str;
  if (!hdr) return;
  str = (char *)calloc(PRINT_BUFFER_SIZE, sizeof(char));
  snprintf(str, PRINT_BUFFER_SIZE, "srate: %d", hdr->srate);
  set_label(reg_srtxt, str);
  snprintf(str, PRINT_BUFFER_SIZE, "chans: %d", hdr->chans);
  set_label(reg_chntxt, str);
  snprintf(str, PRINT_BUFFER_SIZE, "length: %.3f", ((double)(hdr->samples) / (double)(hdr->chans * hdr->srate)));
  set_label(reg_lentxt, str);
  snprintf(str, PRINT_BUFFER_SIZE, "maxamp: %.3f", region_maxamp(region_list_position_to_id(current_region)));
  set_label(reg_maxtxt, str);
  free(str);
}


int update_region_browser(bool grf_too)
{
  int i, len;
  region_state *rs;

  rs = region_report();
  len = rs->len;

  for (i = 0; i < len; i++)
    {
      regrow *r;
      r = region_row(i);
      set_button_label(r->nm, rs->name[i]);
#if WITH_AUDIO
      XmToggleButtonSetState(r->pl, false, false);
#endif
      XtManageChild(r->rw);
    }

  for (i = len; i < max_regions(ss); i++) 
    if (region_rows[i])
      XtUnmanageChild(region_rows[i]->rw);

  free_region_state(rs);
  if (len == 0) return(0);

  XtManageChild(region_list);
  if (grf_too)
    {
      chan_info *cp;
      unhighlight_region();
      set_current_region(0);
      highlight_region();
      goto_window(region_rows[0]->nm);
      cp = rsp->chans[0];
      if (cp) 
	{
	  cp->sound = rsp;
	  cp->chan = 0;
	  set_sensitive(channel_f(cp), false);
	  set_sensitive(channel_w(cp), (region_chans(region_list_position_to_id(0)) > 1));
	  rsp->hdr = fixup_region_data(cp, 0, 0);
	  make_region_labels(rsp->hdr);
	  region_update_graph(cp);
	}
    }
  return(len);
}


static void region_quit_callback(Widget w, XtPointer context, XtPointer info) 
{
  XtUnmanageChild(region_dialog);
}


bool region_browser_is_active(void)
{
  return((region_dialog) && (XtIsRealized(region_dialog)));
}


static void region_resize_callback(Widget w, XtPointer context, XtPointer info)
{
  region_update_graph((chan_info *)context);
}


void delete_region_and_update_browser(int pos)
{
  int act;
  unhighlight_region();
  act = remove_region_from_list(pos);
  if (act == INVALID_REGION) return;
  if (region_dialog)
    {
      if (act != NO_REGIONS)
	{
	  set_current_region(0);
	  highlight_region();
	  goto_window(region_rows[0]->nm);
	}
      else set_current_region(-1);
      update_region_browser(1);
    }
}


static void region_help_callback(Widget w, XtPointer context, XtPointer info) 
{
  region_dialog_help();
}


static void region_insert_callback(Widget w, XtPointer context, XtPointer info) 
{
  if ((current_region != -1) &&
      (selected_channel()))
    paste_region(region_list_position_to_id(current_region), selected_channel());
}


static void region_mix_callback(Widget w, XtPointer context, XtPointer info) 
{
  if ((current_region != -1) &&
      (selected_channel()))
    add_region(region_list_position_to_id(current_region), selected_channel());
}


static void region_save_callback(Widget w, XtPointer context, XtPointer info) 
{
  if ((current_region != -1) &&
      (XmGetFocusWidget(region_dialog) == XmMessageBoxGetChild(region_dialog, XmDIALOG_OK_BUTTON)))
    make_region_save_as_dialog(true);
}


static void region_up_arrow_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp;
  cp = rsp->chans[0];
  cp->sound = rsp;
  if (cp->chan > 0)
    {
      cp->chan--;
      set_sensitive(channel_f(cp), (cp->chan > 0));
      set_sensitive(channel_w(cp), true);
      fixup_region_data(cp, cp->chan, current_region);
      region_update_graph(cp);
    }
}


static void region_down_arrow_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp;
  cp = rsp->chans[0];
  cp->sound = rsp;
  if ((cp->chan + 1) < region_chans(region_list_position_to_id(current_region)))
    {
      cp->chan++;
      set_sensitive(channel_f(cp), true);
      set_sensitive(channel_w(cp), (region_chans(region_list_position_to_id(current_region)) > (cp->chan + 1)));
      fixup_region_data(cp, cp->chan, current_region);
      region_update_graph(cp);
    }
}


static void region_focus_callback(Widget w, XtPointer context, XtPointer info) 
{
  static oclock_t mouse_down_time = 0;
  XmPushButtonCallbackStruct *cb = (XmPushButtonCallbackStruct *)info;
  XButtonEvent *ev;
  chan_info *cp;
  regrow *r = (regrow *)context;

  ev = (XButtonEvent *)(cb->event);
  if (mouse_down_time != 0)
    {
      if ((ev->time - mouse_down_time) < ss->click_time) /* edit region if double clicked */
	{
	  mouse_down_time = ev->time;
	  if (current_region != -1) 
	    region_edit(current_region);
	  return;
	}
    }
  mouse_down_time = ev->time;

  unhighlight_region();
  if (region_list_position_to_id(r->pos) == INVALID_REGION) return; /* needed by auto-tester */
  set_current_region(r->pos);
  cp = rsp->chans[0];
  cp->sound = rsp;
  cp->chan  = 0;
  highlight_region();
  set_sensitive(channel_f(cp), false);
  set_sensitive(channel_w(cp), (region_chans(region_list_position_to_id(current_region)) > 1));
  rsp->hdr = fixup_region_data(cp, 0, current_region);
  if (!rsp->hdr) return;
  make_region_labels(rsp->hdr);
  region_update_graph(cp);
}


void reflect_play_region_stop(int n)
{
#if WITH_AUDIO
  if (region_rows)
    {
      regrow *rg;
      rg = region_row(region_id_to_list_position(n));
      if (rg) XmToggleButtonSetState(rg->pl, false, false);
    }
#endif
}


static void region_play_callback(Widget w, XtPointer context, XtPointer info) 
{
#if WITH_AUDIO
  regrow *r = (regrow *)context;
  if (XmToggleButtonGetState(r->pl))
    play_region(region_list_position_to_id(r->pos), IN_BACKGROUND);
  else stop_playing_region(region_list_position_to_id(r->pos), PLAY_BUTTON_UNSET);
#endif
}


static Xen reflect_file_in_region_browser(Xen hook_or_reason)
{
  if (region_dialog)
    {
      bool file_on;
      file_on = (bool)(any_selected_sound());
      set_sensitive(mix_button, file_on);
      set_sensitive(insert_button, file_on);
    }
  return(Xen_false);
}


static char *regrow_get_label(void *ur)
{
  regrow *r = (regrow *)ur;
  return(get_label(r->nm));
}


static int regrow_get_pos(void *ur)
{
  regrow *r = (regrow *)ur;
  return(r->pos);
}


static void regrow_mouse_enter_label(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  mouse_enter_label(context, REGION_VIEWER);
}


static void regrow_mouse_leave_label(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  mouse_leave_label(context, REGION_VIEWER);
}


static regrow *make_regrow(Widget ww, Widget last_row, XtCallbackProc play_callback, XtCallbackProc name_callback)
{
  int n;
  Arg args[32];
  regrow *r;
  XmString s1;
#if WITH_AUDIO
  XtCallbackList n1;
#endif
  XtCallbackList n3;

  s1 = XmStringCreateLocalized((char *)"");
  r = (regrow *)calloc(1, sizeof(regrow));

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, (last_row) ? XmATTACH_WIDGET : XmATTACH_FORM); n++;
  if (last_row) {XtSetArg(args[n], XmNtopWidget, last_row); n++;}
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNheight, 18); n++; 
  r->rw = XtCreateWidget("rw", xmFormWidgetClass, ww, args, n);

#if WITH_AUDIO
  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
  XtSetArg(args[n], XmNlabelString, s1); n++;
  XtSetArg(args[n], XmNvalueChangedCallback, n1 = make_callback_list(play_callback, (XtPointer)r)); n++;
  if (ss->toggle_size > 0) {XtSetArg(args[n], XmNindicatorSize, ss->toggle_size); n++;}
  XtSetArg(args[n], XmNmarginWidth, 8); n++;
  r->pl = make_togglebutton_widget("pl", r->rw, args, n);
#endif

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
#if WITH_AUDIO
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, r->pl); n++;
#else
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
#endif
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNshadowThickness, 0); n++;
  XtSetArg(args[n], XmNhighlightThickness, 0); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
  XtSetArg(args[n], XmNfillOnArm, false); n++;
  XtSetArg(args[n], XmNrecomputeSize, false); n++;
  XtSetArg(args[n], XmNwidth, 300); n++;
  XtSetArg(args[n], XmNactivateCallback, n3 = make_callback_list(name_callback, (XtPointer)r)); n++;
  r->nm = XtCreateManagedWidget("nm", xmPushButtonWidgetClass, r->rw, args, n);
  XmStringFree(s1);

  XtAddEventHandler(r->nm, EnterWindowMask, false, regrow_mouse_enter_label, (XtPointer)r);
  XtAddEventHandler(r->nm, LeaveWindowMask, false, regrow_mouse_leave_label, (XtPointer)r);

#if WITH_AUDIO
  free(n1);
#endif
  free(n3);
  return(r);
}

static void add_reflect_region_hook(void);

static void make_region_dialog(void)
{
  int n, i, id;
  Arg args[32];
  Widget formw, last_row, infosep;
  Widget panes, toppane, sep1 = NULL, sep2;
#if WITH_AUDIO
  Widget plw;
#endif
  XmString xgo_away, xhelp, titlestr, xsave_as;
  regrow *r;
  chan_info *cp;

  xgo_away = XmStringCreateLocalized((char *)I_GO_AWAY);
  xhelp = XmStringCreateLocalized((char *)I_HELP);
  titlestr = XmStringCreateLocalized((char *)"Regions");
  xsave_as = XmStringCreateLocalized((char *)"Save as");

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNcancelLabelString, xgo_away); n++;
  XtSetArg(args[n], XmNhelpLabelString, xhelp); n++;
  XtSetArg(args[n], XmNokLabelString, xsave_as); n++;
  XtSetArg(args[n], XmNautoUnmanage, false); n++;
  XtSetArg(args[n], XmNdialogTitle, titlestr); n++;
  XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
  XtSetArg(args[n], XmNnoResize, false); n++;
  XtSetArg(args[n], XmNtransient, false); n++;
  region_dialog = XmCreateTemplateDialog(main_shell(ss), (char *)"Regions", args, n);
  save_as_button = XmMessageBoxGetChild(region_dialog, XmDIALOG_OK_BUTTON);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
  insert_button = XtCreateManagedWidget("Insert", xmPushButtonGadgetClass, region_dialog, args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
  mix_button = XtCreateManagedWidget("Mix", xmPushButtonGadgetClass, region_dialog, args, n);

  /* XtAddCallback(region_dialog,  XmNokCallback,       region_save_callback,   NULL); */
  XtAddCallback(save_as_button, XmNactivateCallback, region_save_callback,   NULL);
  XtAddCallback(region_dialog,  XmNcancelCallback,   region_quit_callback,   NULL);
  XtAddCallback(region_dialog,  XmNhelpCallback,     region_help_callback,   NULL);
  XtAddCallback(mix_button,     XmNactivateCallback, region_mix_callback,    NULL);
  XtAddCallback(insert_button,  XmNactivateCallback, region_insert_callback, NULL);

  XmStringFree(xhelp);
  XmStringFree(xgo_away);
  XmStringFree(xsave_as);
  XmStringFree(titlestr);

  XtVaSetValues(XmMessageBoxGetChild(region_dialog, XmDIALOG_OK_BUTTON), XmNarmColor, ss->selection_color, NULL);
  XtVaSetValues(XmMessageBoxGetChild(region_dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor, ss->selection_color, NULL);
  XtVaSetValues(XmMessageBoxGetChild(region_dialog, XmDIALOG_HELP_BUTTON), XmNarmColor, ss->selection_color, NULL);
  XtVaSetValues(XmMessageBoxGetChild(region_dialog, XmDIALOG_OK_BUTTON), XmNbackground, ss->highlight_color, NULL);
  XtVaSetValues(XmMessageBoxGetChild(region_dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color, NULL);
  XtVaSetValues(XmMessageBoxGetChild(region_dialog, XmDIALOG_HELP_BUTTON), XmNbackground, ss->highlight_color, NULL);

  insert_button = XmMessageBoxGetChild(region_dialog, XmDIALOG_CANCEL_BUTTON);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNbottomWidget, XmMessageBoxGetChild(region_dialog, XmDIALOG_SEPARATOR)); n++;
  formw = XtCreateManagedWidget("formw", xmFormWidgetClass, region_dialog, args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, sep1); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNallowResize, true); n++;
  XtSetArg(args[n], XmNpaneMaximum, LOTSA_PIXELS); n++; 
  panes = XtCreateManagedWidget("panes", xmPanedWindowWidgetClass, formw, args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  n = attach_all_sides(args, n);
  XtSetArg(args[n], XmNpaneMinimum, 40); n++;
  toppane = XtCreateManagedWidget("toppane", xmFormWidgetClass, panes, args, n);

#if WITH_AUDIO
  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
  plw = XtCreateManagedWidget("play", xmLabelWidgetClass, toppane, args, n);
#endif
  
  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
  XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
  XtSetArg(args[n], XmNheight, 8); n++;
  sep2 = XtCreateManagedWidget("sep2", xmSeparatorWidgetClass, toppane, args, n);

  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
  XtSetArg(args[n], XmNrightPosition, 70); n++;
#if WITH_AUDIO
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, plw); n++;
#else
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
#endif
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNbottomWidget, sep2); n++;
  XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC); n++;
  XtSetArg(args[n], XmNscrollBarDisplayPolicy, XmSTATIC); n++;
  region_list = XmCreateScrolledWindow(toppane, (char *)"reglist", args, n);

  n = attach_all_sides(args, 0);
  region_ww = XtCreateManagedWidget("ww", xmFormWidgetClass, region_list, args, n);
  XtVaSetValues(region_list, 
		XmNworkWindow, region_ww, 
		NULL);

  map_over_children(region_list, set_main_color_of_widget);
  last_row = NULL;
  
  region_rows = (regrow **)calloc(max_regions(ss), sizeof(regrow *));
  region_rows_size = max_regions(ss);
  for (i = 0; i < max_regions(ss); i++)
    {
      r = make_regrow(region_ww, last_row, region_play_callback, region_focus_callback);
      region_rows[i] = r;
      r->pos = i;
      last_row = r->rw;
    }

  update_region_browser(0);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, region_list); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
#if WITH_AUDIO
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, plw); n++;
#else
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
#endif
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
  XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
  XtSetArg(args[n], XmNwidth, 8); n++;
  infosep = XtCreateManagedWidget("infosep", xmSeparatorWidgetClass, toppane, args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, infosep); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
#if WITH_AUDIO
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, plw); n++;
#else
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
#endif
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
  reg_srtxt = XtCreateManagedWidget("srate:", xmLabelWidgetClass, toppane, args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, infosep); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, reg_srtxt); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
  reg_chntxt = XtCreateManagedWidget("chans:", xmLabelWidgetClass, toppane, args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, infosep); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, reg_chntxt); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
  reg_lentxt = XtCreateManagedWidget("length:", xmLabelWidgetClass, toppane, args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, infosep); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, reg_lentxt); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
  reg_maxtxt = XtCreateManagedWidget("maxamp:", xmLabelWidgetClass, toppane, args, n);


  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  n = attach_all_sides(args, n);
  XtSetArg(args[n], XmNpaneMinimum, 150); n++;
  region_grf = XtCreateManagedWidget("grf", xmFormWidgetClass, panes, args, n);

  XtManageChild(region_dialog);
  if (widget_width(region_dialog) < 400) set_widget_width(region_dialog, 400);

  id = region_list_position_to_id(0);
  rsp = make_simple_channel_display(region_srate(id), region_len(id), WITH_ARROWS, region_graph_style(ss), region_grf, WITHOUT_EVENTS);
  rsp->inuse = SOUND_REGION;
  set_current_region(0);
  cp = rsp->chans[0];
  XtVaSetValues(region_rows[0]->nm, XmNbackground, ss->white, XmNforeground, ss->black, NULL);
  map_over_children(panes, color_sashes);
  XtVaSetValues(toppane, XmNpaneMinimum, 1, NULL);
  XtVaSetValues(region_grf, XmNpaneMinimum, 1, NULL);

  XtAddCallback(channel_graph(cp), XmNresizeCallback, region_resize_callback, (XtPointer)cp);
  XtAddCallback(channel_graph(cp), XmNexposeCallback, region_resize_callback, (XtPointer)cp);

  /* channel_f is up arrow, channel_w is down arrow */
  XtAddCallback(channel_f(cp), XmNactivateCallback, region_up_arrow_callback, NULL);
  XtAddCallback(channel_w(cp), XmNactivateCallback, region_down_arrow_callback, NULL);

  set_sensitive(channel_f(cp), false);
  set_sensitive(channel_w(cp), (region_chans(region_list_position_to_id(0)) > 1));

  cp->chan = 0;
  rsp->hdr = fixup_region_data(cp, 0, 0);
  make_region_labels(rsp->hdr);
  highlight_region();
  region_update_graph(cp);

  add_reflect_region_hook();

  set_dialog_widget(REGION_DIALOG, region_dialog);
}


static void view_region_callback(Widget w, XtPointer context, XtPointer info)
{
  /* put up scrollable dialog describing/playing/editing the region list */
  if (!region_dialog)
    make_region_dialog();
  else raise_dialog(region_dialog);
  if (!XtIsManaged(region_dialog)) 
    {
      set_current_region(0); 
      XtManageChild(region_dialog);
    }
}


bool region_dialog_is_active(void)
{
  return((region_dialog) && 
	 (XtIsManaged(region_dialog)));
}


void allocate_region_rows(int n)
{
  if ((region_dialog) && 
      (n > region_rows_size))
    {
      int i;
      region_rows = (regrow **)realloc(region_rows, n * sizeof(regrow *));
      for (i = region_rows_size; i < n; i++) region_rows[i] = NULL;
      region_rows_size = n;
    }
}


static regrow *region_row(int n)
{
  if (n < region_rows_size)
    {
      regrow *r;
      if (!region_rows[n])
	{
	  r = make_regrow(region_ww, 
			  (n > 0) ? (region_rows[n - 1]->rw) : NULL, 
			  region_play_callback, region_focus_callback);
	  region_rows[n] = r;
	  r->pos = n;
	}
      return(region_rows[n]);
    }
  return(NULL);
}


static int region_dialog_region(void)
{
  return(region_list_position_to_id(current_region));
}


static Xen g_view_regions_dialog(void) 
{
  #define H_view_regions_dialog "(" S_view_regions_dialog "): start the region dialog"
  if (snd_regions() > 0) 
    view_region_callback(main_pane(ss), NULL, NULL);
  return(Xen_wrap_widget(region_dialog));
}


#include "snd-file.h"

/* various file-related dialogs:
   File|Edit:Save-as
   File:Open|View
   File|Edit:Mix
   File:Insert
   File:Edit-Header
   File:New
   Info and Raw
   View:Files
*/

static void snd_sort(int sorter, sort_info **data, int len);


/* -------------------------------- sorters -------------------------------- */


static mus_long_t file_bytes(const char *filename)
{
#ifndef _MSC_VER
  struct stat statbuf;
  if (stat(filename, &statbuf) >= 0) 
    return(statbuf.st_size);
  return(0);
#else
  int chan;
  mus_long_t bytes;
  chan = mus_file_open_read(filename);
  if (chan == -1) return(0);
  bytes = lseek(chan, 0L, SEEK_END);
  snd_close(chan, filename);
  return(bytes);
#endif
}

/* sort files list by name (aphabetical), or some number (date written, size), or by xen proc */

static int sort_a_to_z(const void *a, const void *b)
{
  sort_info *d1 = *(sort_info **)a;
  sort_info *d2 = *(sort_info **)b;
  return(strcmp(d1->filename, d2->filename));
}


static int sort_z_to_a(const void *a, const void *b)
{
  return(-sort_a_to_z(a, b));
}


static int sort_small_to_big(const void *a, const void *b)
{
  sort_info *d1 = *(sort_info **)a;
  sort_info *d2 = *(sort_info **)b;
  if (d1->samps > d2->samps) 
    return(1); 
  else 
    {
      if (d1->samps == d2->samps) 
	return(0); 
      else return(-1);
    }
}


static int sort_big_to_small(const void *a, const void *b)
{
  return(-sort_small_to_big(a, b));
}


static int sort_new_to_old(const void *a, const void *b)
{
  sort_info *d1 = *(sort_info **)a;
  sort_info *d2 = *(sort_info **)b;
  if (d1->time < d2->time) 
    return(1); 
  else 
    {
      if (d1->time == d2->time) 
	return(0); 
      else return(-1);
    }
}


static int sort_old_to_new(const void *a, const void *b)
{
  return(-sort_new_to_old(a, b));
}


static Xen sorter_func;

static int sort_xen(const void *a, const void *b)
{
  /* sorter function gets two names, returns -1, 0, or 1 just like the other comparators */
  sort_info *d1 = *(sort_info **)a;
  sort_info *d2 = *(sort_info **)b;
  return(Xen_integer_to_C_int(Xen_call_with_2_args(sorter_func, C_string_to_Xen_string(d1->full_filename), C_string_to_Xen_string(d2->full_filename), "sort func")));
}


static void snd_sort(int sorter, sort_info **data, int len)
{
  int i, sorter_pos;
  switch (sorter)
    {
    case SORT_A_TO_Z: 
      qsort((void *)data, len, sizeof(sort_info *), sort_a_to_z);
      break;

    case SORT_Z_TO_A: 
      qsort((void *)data, len, sizeof(sort_info *), sort_z_to_a);
      break;

    case SORT_NEW_TO_OLD:
      for (i = 0; i < len; i++) 
	data[i]->time = file_write_date(data[i]->full_filename);
      qsort((void *)data, len, sizeof(sort_info *), sort_new_to_old);
      break;

    case SORT_OLD_TO_NEW:
      for (i = 0; i < len; i++) 
	data[i]->time = file_write_date(data[i]->full_filename);
      qsort((void *)data, len, sizeof(sort_info *), sort_old_to_new);
      break;

    case SORT_SMALL_TO_BIG:
      for (i = 0; i < len; i++)
	data[i]->samps = file_bytes(data[i]->full_filename);
      qsort((void *)data, len, sizeof(sort_info *), sort_small_to_big);
      break;

    case SORT_BIG_TO_SMALL:
      for (i = 0; i < len; i++)
	data[i]->samps = file_bytes(data[i]->full_filename);
      qsort((void *)data, len, sizeof(sort_info *), sort_big_to_small);
      break;

    default:
    case SORT_XEN:
      /* sorter is SORT_XEN + index into file_sorters list */
      /*   that list is a vector of pairs (name proc) */
      sorter_pos = sorter - SORT_XEN;
      if ((sorter_pos >= 0) &&
	  (sorter_pos < ss->file_sorters_size))
	{
	  if (Xen_is_list(Xen_vector_ref(ss->file_sorters, sorter_pos)))
	    {
	      sorter_func = Xen_cadr(Xen_vector_ref(ss->file_sorters, sorter_pos));
	      qsort((void *)data, len, sizeof(sort_info *), sort_xen);
	      return;
	    }
	}
      snd_warning("no such file-sorter (%d)", sorter_pos);
      break;
    }
}



static void dialog_set_title(widget_t dialog, const char *titlestr)
{
  XmString title;
  title = XmStringCreateLocalized((char *)titlestr);
  XtVaSetValues(dialog, XmNdialogTitle, title, NULL);
  XmStringFree(title);
}


void cleanup_file_monitor(void) {}
static bool initialize_file_monitor(void) {return(false);}
void *unmonitor_file(void *watcher) {return(NULL);}
void monitor_sound(snd_info *sp) {}

/* -------------------------------------------------------------------------------- */



#define FSB_BOX(Dialog, Child) XmFileSelectionBoxGetChild(Dialog, Child)
#define MSG_BOX(Dialog, Child) XmMessageBoxGetChild(Dialog, Child)


/* ---------------- open/mix/insert/save-as dialogs ---------------- */

static void color_file_selection_box(Widget w)
{
  /* overwrite most Motif-default colors */
  Widget wtmp;
  
  map_over_children(w, set_main_color_of_widget);
  XtVaSetValues(FSB_BOX(w, XmDIALOG_DIR_LIST), 
		XmNbackground, ss->white, 
		XmNforeground, ss->black, 
		NULL);
  XtVaSetValues(FSB_BOX(w, XmDIALOG_LIST), 
		XmNbackground, ss->white, 
		XmNforeground, ss->black, 
		NULL);
  
  XtVaSetValues(FSB_BOX(w, XmDIALOG_CANCEL_BUTTON), XmNarmColor,   ss->selection_color, NULL);
  XtVaSetValues(FSB_BOX(w, XmDIALOG_HELP_BUTTON),   XmNarmColor,   ss->selection_color, NULL);
  XtVaSetValues(FSB_BOX(w, XmDIALOG_OK_BUTTON),     XmNarmColor,   ss->selection_color, NULL);
  XtVaSetValues(FSB_BOX(w, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color, NULL);
  XtVaSetValues(FSB_BOX(w, XmDIALOG_HELP_BUTTON),   XmNbackground, ss->highlight_color, NULL);
  XtVaSetValues(FSB_BOX(w, XmDIALOG_OK_BUTTON),     XmNbackground, ss->highlight_color, NULL);
  
  wtmp = FSB_BOX(w, XmDIALOG_TEXT);
  if (wtmp)
    {
      XtVaSetValues(wtmp,     XmNhighlightThickness,  1,                          NULL);
      XtAddCallback(wtmp,     XmNfocusCallback,       textfield_focus_callback,   NULL);
      XtAddCallback(wtmp,     XmNlosingFocusCallback, textfield_unfocus_callback, NULL);
      XtAddEventHandler(wtmp, EnterWindowMask, false, mouse_enter_text_callback,  NULL);
      XtAddEventHandler(wtmp, LeaveWindowMask, false, mouse_leave_text_callback,  NULL);
    }
  
  wtmp = FSB_BOX(w, XmDIALOG_FILTER_TEXT);	
  if (wtmp)
    {
      XtVaSetValues(wtmp,     XmNhighlightThickness,  1,                          NULL);
      XtAddCallback(wtmp,     XmNfocusCallback,       textfield_focus_callback,   NULL);
      XtAddCallback(wtmp,     XmNlosingFocusCallback, textfield_unfocus_callback, NULL);
      XtAddEventHandler(wtmp, EnterWindowMask, false, mouse_enter_text_callback,  NULL);
      XtAddEventHandler(wtmp, LeaveWindowMask, false, mouse_leave_text_callback,  NULL);
    }
}


static void force_directory_reread(Widget dialog)
{
  /* force update, but make sure the filename is not reset to its (dumb) default */
  XmString dirmask;
  Widget name_field;
  char *filename = NULL;
  name_field = FSB_BOX(dialog, XmDIALOG_TEXT);
  filename = XmTextGetString(name_field);
  XtVaGetValues(dialog, XmNdirMask, &dirmask, NULL);
  XmFileSelectionDoSearch(dialog, dirmask);
  XmStringFree(dirmask);
  XmTextSetString(name_field, filename);
  if (filename) 
    {
      XmTextSetCursorPosition(name_field, mus_strlen(filename));
      XtFree(filename);
    }
}


static void force_directory_reread_and_let_filename_change(Widget dialog)
{
  XmString dirmask;
  XtVaGetValues(dialog, XmNdirMask, &dirmask, NULL);
  XmFileSelectionDoSearch(dialog, dirmask);
  XmStringFree(dirmask);
}




/* -------------------------------- file list positioning -------------------------------- */

typedef struct {
  char *directory_name;
  position_t list_top;
} dirpos_info;

typedef struct {
  dirpos_info **dirs;
  int size, top;
} dirpos_list;

void dirpos_update(dirpos_list *dl, const char *dir, position_t pos);
position_t dirpos_list_top(dirpos_list *dl, const char *dirname);
dirpos_list *make_dirpos_list(void);


static dirpos_info *make_dirpos_info(const char *dir, position_t pos)
{
  dirpos_info *dp;
  dp = (dirpos_info *)calloc(1, sizeof(dirpos_info));
  dp->directory_name = mus_strdup(dir);
  dp->list_top = pos;
  return(dp);
}


dirpos_list *make_dirpos_list(void)
{
  dirpos_list *dl;
  dl = (dirpos_list *)calloc(1, sizeof(dirpos_list));
  dl->size = 8;
  dl->top = 0;
  dl->dirs = (dirpos_info **)calloc(dl->size, sizeof(dirpos_info *));
  return(dl);
}


void dirpos_update(dirpos_list *dl, const char *dir, position_t pos)
{
  int i;
  if (!dl) return;
  for (i = 0; i < dl->top; i++)
    {
      if ((dl->dirs[i]) && 
	  (mus_strcmp(dir, dl->dirs[i]->directory_name)))
	{
	  dirpos_info *dp;
	  dp = dl->dirs[i];
	  dp->list_top = pos;
	  return;
	}
    }
  if (dl->top >= dl->size)
    {
      int old_size;
      old_size = dl->size;
      dl->size += 8;
      dl->dirs = (dirpos_info **)realloc(dl->dirs, dl->size * sizeof(dirpos_info *));
      for (i = old_size; i < dl->size; i++) dl->dirs[i] = NULL;
    }
  dl->dirs[dl->top++] = make_dirpos_info(dir, pos);
}


position_t dirpos_list_top(dirpos_list *dl, const char *dirname)
{
  int i;
  if (dl)
    for (i = 0; i < dl->top; i++)
      if ((dl->dirs[i]) && 
	  (mus_strcmp(dirname, dl->dirs[i]->directory_name)))
	return(dl->dirs[i]->list_top);
  return(POSITION_UNKNOWN);
}


/* -------- popups -------- */

/* I think there is no way to get a key action to popup one of these menus -- Xm/RowColumn.c
 *   appears to insist on a button event, and any change to that via XmNmenuPost gets an
 *   error.  Perhaps we should notice the POPUP_BUTTON setting however?
 */

typedef struct file_pattern_info {
  /* just-sounds file lists */
  bool reread_directory;
  bool in_just_sounds_update;
  Widget dialog, just_sounds_button;
  char *last_dir;
  dir_info *current_files;
  void *directory_watcher;
  int filter_choice, sorter_choice;
  dirpos_list *dir_list;
} file_pattern_info;

/* popups:
 *   text:    history of previous choices,
 *   list:    sort and filter choices
 *   dir:     higher level dir choices
 *   filter:  history of previous choices
 */

typedef struct file_popup_info {
  Widget dialog;
  Widget file_text_popup, file_list_popup, file_dir_popup, file_filter_popup;
  Widget file_text_popup_label, file_filter_popup_label, file_dir_popup_label, file_list_popup_label;
  /* file_filter here refers to the dialog filter field, not file-filters */
  char **file_text_names, **file_filter_names;                   /* history of choices as array of strings */
  Widget *file_text_items, *file_filter_items, *file_dir_items, *file_list_items;  /* menu items */
  int file_list_items_size;
  file_pattern_info *fp;
} file_popup_info;


/* file popup */
static void file_text_item_activate_callback(Widget w, XtPointer context, XtPointer info)
{
  file_popup_info *fd = (file_popup_info *)context;
  char *filename;
  snd_info *sp;
  filename = get_label(w);
  XmTextFieldSetString(FSB_BOX(fd->dialog, XmDIALOG_TEXT), filename);

  ss->open_requestor = FROM_OPEN_DIALOG_POPUP;
  ss->open_requestor_data = NULL;
  sp = snd_open_file(filename, FILE_READ_WRITE);
  if (sp) select_channel(sp, 0);

  XtUnmanageChild(fd->dialog);
  if (filename) XtFree(filename);
}


#define FILE_TEXT_POPUP_LABEL "previous files"

static void file_text_popup_callback(Widget w, XtPointer context, XtPointer info)
{
  file_popup_info *fd = (file_popup_info *)context;
  XmPopupHandlerCallbackStruct *cb = (XmPopupHandlerCallbackStruct *)info;
  XEvent *e;
  e = cb->event;
  if (e->type == ButtonPress)
    {
      /* position menu to match current text widget, show previous choices, if any else "[no previous choices]" */
      /*     XmMenuPosition(popup_menu, event) happens automatically */

      char *current_filename;
      int i, filenames_to_display = 0;

      if (!fd->file_text_items)
	{
	  int n = 0;
	  Arg args[12];
	  fd->file_text_items = (Widget *)calloc(FILENAME_LIST_SIZE, sizeof(Widget));
	  XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
	  for (i = 0; i < FILENAME_LIST_SIZE; i++)
	    {
	      fd->file_text_items[i] = XtCreateWidget("", xmPushButtonWidgetClass, fd->file_text_popup, args, n);
	      XtAddCallback(fd->file_text_items[i], XmNactivateCallback, file_text_item_activate_callback, (void *)fd);
	    }
	}

      current_filename = XmTextFieldGetString(FSB_BOX(fd->dialog, XmDIALOG_TEXT)); 
      /* w is probably ok here (assumes only text triggers this) */

      for (i = 0; i < FILENAME_LIST_SIZE; i++)
	if ((fd->file_text_names[i]) &&
	    (mus_file_probe(fd->file_text_names[i])) &&
	    (!(mus_strcmp(fd->file_text_names[i], current_filename))))
	  {
	    set_label(fd->file_text_items[filenames_to_display], fd->file_text_names[i]);
	    XtManageChild(fd->file_text_items[filenames_to_display]);
	    filenames_to_display++;
	  }

      for (i = filenames_to_display; i < FILENAME_LIST_SIZE; i++)
	if ((fd->file_text_items[i]) &&
	    (XtIsManaged(fd->file_text_items[i])))
	  XtUnmanageChild(fd->file_text_items[i]);
      XtFree(current_filename);

      /* why was this commented out? */
      if (filenames_to_display == 0)
	set_label(fd->file_text_popup_label, "no " FILE_TEXT_POPUP_LABEL);
      else set_label(fd->file_text_popup_label, FILE_TEXT_POPUP_LABEL);

      cb->menuToPost = fd->file_text_popup;
    }
}


/* filter popup */
static void file_filter_text_activate_callback(Widget w, XtPointer context, XtPointer info)
{
  file_popup_info *fd = (file_popup_info *)context;
  char *filter;
  filter = XmTextFieldGetString(w);
  if (filter)
    {
      remember_filename(filter, fd->file_filter_names);
      XtFree(filter);
      force_directory_reread_and_let_filename_change(fd->dialog);
    }
}


static void file_filter_item_activate_callback(Widget w, XtPointer context, XtPointer info)
{
  file_popup_info *fd = (file_popup_info *)context;
  Widget text;
  char *filtername;
  filtername = get_label(w);
  text = FSB_BOX(fd->dialog, XmDIALOG_FILTER_TEXT);
  XmTextFieldSetString(text, filtername);
  force_directory_reread(fd->dialog);
  if (filtername) XtFree(filtername);
}


#define FILE_FILTER_POPUP_LABEL "previous filters"

static void file_filter_popup_callback(Widget w, XtPointer context, XtPointer info)
{
  file_popup_info *fd = (file_popup_info *)context;
  XmPopupHandlerCallbackStruct *cb = (XmPopupHandlerCallbackStruct *)info;
  XEvent *e;
  e = cb->event;
  if (e->type == ButtonPress)
    {
      char *current_filtername;
      int i, filternames_to_display = 0;

      if (!fd->file_filter_items)
	{
	  int n = 0;
	  Arg args[12];
	  fd->file_filter_items = (Widget *)calloc(FILENAME_LIST_SIZE, sizeof(Widget));
	  XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
	  for (i = 0; i < FILENAME_LIST_SIZE; i++)
	    {
	      fd->file_filter_items[i] = XtCreateWidget("", xmPushButtonWidgetClass, fd->file_filter_popup, args, n);
	      XtAddCallback(fd->file_filter_items[i], XmNactivateCallback, file_filter_item_activate_callback, (void *)fd);
	    }
	}

      current_filtername = XmTextFieldGetString(FSB_BOX(fd->dialog, XmDIALOG_FILTER_TEXT)); 

      for (i = 0; i < FILENAME_LIST_SIZE; i++)
	if ((fd->file_filter_names[i]) &&
	    (!(mus_strcmp(fd->file_filter_names[i], current_filtername))))
	  {
	    set_label(fd->file_filter_items[filternames_to_display], fd->file_filter_names[i]);
	    XtManageChild(fd->file_filter_items[filternames_to_display]);
	    filternames_to_display++;
	  }

      for (i = filternames_to_display; i < FILENAME_LIST_SIZE; i++)
	if ((fd->file_filter_items[i]) &&
	    (XtIsManaged(fd->file_filter_items[i])))
	  XtUnmanageChild(fd->file_filter_items[i]);
      XtFree(current_filtername);
      /*
      if (filternames_to_display == 0)
	set_label(fd->file_filter_popup_label, "no " FILE_FILTER_POPUP_LABEL);
      else set_label(fd->file_filter_popup_label, FILE_FILTER_POPUP_LABEL);
      */
      cb->menuToPost = fd->file_filter_popup;
    }
}


/* dir list popup */

static void update_dir_list(Widget dialog, char *filter)
{
  Widget text;
  text = FSB_BOX(dialog, XmDIALOG_FILTER_TEXT);
  XmTextFieldSetString(text, filter);
  force_directory_reread(dialog);
}


static void file_dir_item_activate_callback(Widget w, XtPointer context, XtPointer info)
{
  char *name;
  name = get_label(w);
  if (name)
    {
      file_popup_info *fd = (file_popup_info *)context;
      char *filter;
      filter = mus_format("%s/*", name);
      update_dir_list(fd->dialog, filter);
      XtFree(name);
      free(filter);
    }
}


#define FILE_DIR_POPUP_LABEL "dirs"

/* dir_items, but strs generated on the fly, current in filter text */

static void file_dir_popup_callback(Widget w, XtPointer context, XtPointer info)
{
  file_popup_info *fd = (file_popup_info *)context;
  XmPopupHandlerCallbackStruct *cb = (XmPopupHandlerCallbackStruct *)info;
  XEvent *e;
  e = cb->event;
  if (e->type == ButtonPress)
    {
      char *current_filename = NULL;
      int i, dirs_to_display = 0;

      if (!fd->file_dir_items)
	{
	  int n = 0;
	  Arg args[12];
	  fd->file_dir_items = (Widget *)calloc(FILENAME_LIST_SIZE, sizeof(Widget));
	  XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
	  for (i = 0; i < FILENAME_LIST_SIZE; i++)
	    {
	      fd->file_dir_items[i] = XtCreateWidget("", xmPushButtonWidgetClass, fd->file_dir_popup, args, n);
	      XtAddCallback(fd->file_dir_items[i], XmNactivateCallback, file_dir_item_activate_callback, (void *)fd);
	    }
	}

      {
	XmStringTable items;
	int num_dirs;
	XtVaGetValues(fd->dialog, XmNdirListItems, &items, XmNdirListItemCount, &num_dirs, NULL);
	if (num_dirs > 0)
	  current_filename = (char *)XmStringUnparse(items[0], NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
      }
      if (!current_filename)
	{
	  current_filename = XmTextFieldGetString(FSB_BOX(fd->dialog, XmDIALOG_FILTER_TEXT));
	  if (!current_filename) 
	    current_filename = XmTextFieldGetString(FSB_BOX(fd->dialog, XmDIALOG_TEXT));
	}

      if (current_filename)
	{
	  int len;
	  len = strlen(current_filename);
	  for (i = 0; i < len; i++)
	    if (current_filename[i] == '/')
	      dirs_to_display++;

	  if (dirs_to_display > FILENAME_LIST_SIZE)
	    dirs_to_display = FILENAME_LIST_SIZE;

	  if (dirs_to_display > 0)
	    {
	      char **dirs;
	      int j = 1;
	      dirs = (char **)calloc(dirs_to_display, sizeof(char *));
	      dirs[0] = mus_strdup("/");
	      for (i = 1; i < len; i++)
		if (current_filename[i] == '/')
		  {
		    dirs[j] = (char *)calloc(i + 1, sizeof(char));
		    memcpy(dirs[j], (const char *)current_filename, i);
		    j++;
		  }

	      for (i = 0; i < dirs_to_display; i++)
		{
		  set_label(fd->file_dir_items[i], dirs[i]);
		  XtManageChild(fd->file_dir_items[i]);
		  free(dirs[i]);
		}
	      free(dirs);
	    }
	}

      for (i = dirs_to_display; i < FILENAME_LIST_SIZE; i++)
	if ((fd->file_dir_items[i]) &&
	    (XtIsManaged(fd->file_dir_items[i])))
	  XtUnmanageChild(fd->file_dir_items[i]);
      XtFree(current_filename);

      cb->menuToPost = fd->file_dir_popup;
    }
}


#define FILE_LIST_POPUP_LABEL "sort/filter"
#define NO_FILTER_LABEL "no filter"

#define FILE_FILTER_OFFSET 1024
#define NO_FILE_FILTER_OFFSET 2048

static void sort_files_and_redisplay(file_pattern_info *fp);


static void file_list_item_activate_callback(Widget w, XtPointer context, XtPointer info)
{
  file_popup_info *fd = (file_popup_info *)context;
  intptr_t data;
  int choice;
  XtVaGetValues(w, XmNuserData, &data, NULL);
  choice = (int)data;
  if (choice >= FILE_FILTER_OFFSET)
    {
      XmToggleButtonSetState(fd->fp->just_sounds_button, false, false);
      if (choice == NO_FILE_FILTER_OFFSET)
	fd->fp->filter_choice = NO_FILE_FILTER;
      else fd->fp->filter_choice = choice - FILE_FILTER_OFFSET + 2;
      fd->fp->in_just_sounds_update = true;
      force_directory_reread(fd->fp->dialog);
      fd->fp->in_just_sounds_update = false;
    }
  else
    {
      fd->fp->sorter_choice = choice;
      sort_files_and_redisplay(fd->fp);
    }
}


static Widget make_file_list_item(file_popup_info *fd, int choice)
{
  int n;
  Arg args[12];
  const char *item_label;
  Widget w;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;

  switch (choice)
    {
    case 0: item_label = "a..z";       break;
    case 1: item_label = "z..a";       break;
    case 2: item_label = "new..old";   break;
    case 3: item_label = "old..new";   break;
    case 4: item_label = "small..big"; break;
    case 5: item_label = "big..small"; break;
    default: item_label = "unused";    break;
    }

  XtSetArg(args[n], XmNuserData, choice);           /* userData is index into sorters list */
  w = XtCreateManagedWidget(item_label, xmPushButtonWidgetClass, fd->file_list_popup, args, n + 1);
  XtAddCallback(w, XmNactivateCallback, file_list_item_activate_callback, (void *)fd);
  return(w);
}


static void file_list_popup_callback(Widget w, XtPointer context, XtPointer info)
{
  file_popup_info *fd = (file_popup_info *)context;
  XmPopupHandlerCallbackStruct *cb = (XmPopupHandlerCallbackStruct *)info;
  XEvent *e;
  e = cb->event;
  if (e->type == ButtonPress)
    {
      int i;
      if (!fd->file_list_items)
	{
	  /* set up the default menu items */

	  fd->file_list_items = (Widget *)calloc(SORT_XEN, sizeof(Widget));
	  fd->file_list_items_size = SORT_XEN;

	  for (i = 0; i < SORT_XEN; i++)
	    fd->file_list_items[i] = make_file_list_item(fd, i);
	}

      /* clear any trailers just in case */
      if (fd->file_list_items_size > SORT_XEN)
	for (i = SORT_XEN; i < fd->file_list_items_size; i++)
	  XtUnmanageChild(fd->file_list_items[i]);

      /* check for added sort and filter functions (allocate more items if needed) */
      {
	int extra_sorters = 0, extra_filters = 0, items_len;
	for (i = 0; i < ss->file_sorters_size; i++)
	  if (!(Xen_is_false(Xen_vector_ref(ss->file_sorters, i))))
	    extra_sorters++;
	for (i = 0; i < ss->file_filters_size; i++)
	  if (!(Xen_is_false(Xen_vector_ref(ss->file_filters, i))))
	    extra_filters++;

	items_len = SORT_XEN + extra_sorters + extra_filters;
	if (fd->fp->filter_choice != NO_FILE_FILTER) items_len++;

	if (items_len > fd->file_list_items_size)
	  {
	    fd->file_list_items = (Widget *)realloc(fd->file_list_items, items_len * sizeof(Widget));
	    for (i = fd->file_list_items_size; i < items_len; i++)
	      fd->file_list_items[i] = make_file_list_item(fd, i);
	    fd->file_list_items_size = items_len;
	  }
      }

      /* make sure all the added sorter labels are correct, bg blue, and items active */
      if (fd->file_list_items_size > SORT_XEN)
	{
	  int k = SORT_XEN;

	  /* sorters */
	  for (i = 0; i < ss->file_sorters_size; i++)
	    {
	      if (!(Xen_is_false(Xen_vector_ref(ss->file_sorters, i))))
		{
		  set_label(fd->file_list_items[k], Xen_string_to_C_string(Xen_car(Xen_vector_ref(ss->file_sorters, i))));
		  XtVaSetValues(fd->file_list_items[k], 
				XmNbackground, ss->lighter_blue,
				XmNuserData, SORT_XEN + i,
				NULL);
		  if (!(XtIsManaged(fd->file_list_items[k])))
		    XtManageChild(fd->file_list_items[k]);
		  k++;
		}
	    }
	  
	  for (i = 0; i < ss->file_filters_size; i++)
	    {
	      if (!(Xen_is_false(Xen_vector_ref(ss->file_filters, i))))
		{
		  set_label(fd->file_list_items[k], Xen_string_to_C_string(Xen_car(Xen_vector_ref(ss->file_filters, i))));
		  XtVaSetValues(fd->file_list_items[k], XmNbackground, ss->light_blue, 
				XmNuserData, i + FILE_FILTER_OFFSET,
				NULL);
		  if (!(XtIsManaged(fd->file_list_items[k])))
		    XtManageChild(fd->file_list_items[k]);
		  k++;
		}
	    }

	  /* add "no filter" item if currently filtered */
	  if (fd->fp->filter_choice != NO_FILE_FILTER)
	    {
	      set_label(fd->file_list_items[k], NO_FILTER_LABEL);
	      XtVaSetValues(fd->file_list_items[k], XmNbackground, ss->light_blue, 
			    XmNuserData, NO_FILE_FILTER_OFFSET,
			    NULL);
	      if (!(XtIsManaged(fd->file_list_items[k])))
		XtManageChild(fd->file_list_items[k]);
	    }
	  
	}
      cb->menuToPost = fd->file_list_popup;
    }
}


static void add_file_popups(file_popup_info *fd)
{
  int n;
  Arg args[20];

  /* from lib/Xm.RCPopup.c:
   * When a user creates a new popup menu then we will install a particular
   * event handler on the menu's widget parent. Along with this we install
   * a grab on the button specified in XmNmenuPost or XmNwhichButton.   [XmNmenuPost is a string = translation table syntax, <Btn3Down> is default]
   *                                                                    [XmNwhichButton is obsolete]
   * The posting algorithm is as follows: 
   * 
   * 1. On receipt of a posting event, the handler will search the child
   * list for a candidate widget or gadget, and track the most specific
   * popup menu available (these can be found in the popup list). The
   * criteria for a match includes matching the XmNmenuPost information.
   * 
   * 2. Matching criteria include: 
   * 
   *    * The menu must have XmNpopupEnabled set to either
   *      XmPOPUP_AUTOMATIC or XmPOPUP_AUTOMATIC_RECURSIVE.  
   * 
   *    * The popup menu is chosen according to creation order. If there is
   *      more than one, the first correct match is chosen.  
   * 
   *    * If the popup menu is found in a parent of the target widget, and
   *      the popup menu must also have XmNpopupEnabled set to 
   *      XmPOPUP_AUTOMATIC_RECURSIVE to match.                         [sigh -- no one actually reads comments...]
   * 
   * 3. Once a selection is made, if the menu's parent widget has a
   * popupHandlerCallback, it is invoked. The callback allows the user to
   * determine if a more specific menu is necessary, such as would be the
   * case in a graphical manipulation environment, and includes all the
   * necessary information.  
   * 
   */

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNpopupEnabled, XmPOPUP_AUTOMATIC); n++;

  /* file text */
  XtAddCallback(FSB_BOX(fd->dialog, XmDIALOG_TEXT), XmNpopupHandlerCallback, file_text_popup_callback, (void *)fd);
  fd->file_text_popup = XmCreatePopupMenu(FSB_BOX(fd->dialog, XmDIALOG_TEXT), (char *)"file-text-popup", args, n);
  fd->file_text_names = make_filename_list();
  fd->file_text_popup_label = XtCreateManagedWidget(FILE_TEXT_POPUP_LABEL, xmLabelWidgetClass, fd->file_text_popup, args, n);
  XtCreateManagedWidget("sep", xmSeparatorWidgetClass, fd->file_text_popup, args, n);

  /* filter text */
  XtAddCallback(FSB_BOX(fd->dialog, XmDIALOG_FILTER_TEXT), XmNpopupHandlerCallback, file_filter_popup_callback, (void *)fd);
  fd->file_filter_popup = XmCreatePopupMenu(FSB_BOX(fd->dialog, XmDIALOG_FILTER_TEXT), (char *)"file-filter-popup", args, n);
  fd->file_filter_names = make_filename_list();
  fd->file_filter_popup_label = XtCreateManagedWidget(FILE_FILTER_POPUP_LABEL, xmLabelWidgetClass, fd->file_filter_popup, args, n);
  XtCreateManagedWidget("sep", xmSeparatorWidgetClass, fd->file_filter_popup, args, n);
  {
    char *startup_filter;
    startup_filter = XmTextFieldGetString(FSB_BOX(fd->dialog, XmDIALOG_FILTER_TEXT));
    if (startup_filter) 
      {
	remember_filename(startup_filter, fd->file_filter_names);
	XtFree(startup_filter);
      }
  }
  XtAddCallback(FSB_BOX(fd->dialog, XmDIALOG_FILTER_TEXT), XmNactivateCallback, file_filter_text_activate_callback, (void *)fd);

  /* file directory */
  XtAddCallback(FSB_BOX(fd->dialog, XmDIALOG_DIR_LIST), XmNpopupHandlerCallback, file_dir_popup_callback, (void *)fd);
  fd->file_dir_popup = XmCreatePopupMenu(FSB_BOX(fd->dialog, XmDIALOG_DIR_LIST), (char *)"file-dir-popup", args, n);
  fd->file_dir_popup_label = XtCreateManagedWidget(FILE_DIR_POPUP_LABEL, xmLabelWidgetClass, fd->file_dir_popup, args, n);
  XtCreateManagedWidget("sep", xmSeparatorWidgetClass, fd->file_dir_popup, args, n);

  /* file list */
  XtAddCallback(FSB_BOX(fd->dialog, XmDIALOG_LIST), XmNpopupHandlerCallback, file_list_popup_callback, (void *)fd);
  fd->file_list_popup = XmCreatePopupMenu(FSB_BOX(fd->dialog, XmDIALOG_LIST), (char *)"file-list-popup", args, n);
  fd->file_list_popup_label = XtCreateManagedWidget(FILE_LIST_POPUP_LABEL, xmLabelWidgetClass, fd->file_list_popup, args, n);
  XtCreateManagedWidget("sep", xmSeparatorWidgetClass, fd->file_list_popup, args, n);
}



/* ---------------- just-sounds (file-filters) ---------------- */

static void file_change_directory_callback(Widget w, XtPointer context, XtPointer info) 
{
  /* click in directory list */
  file_pattern_info *fp = (file_pattern_info *)context;
  char *leaving_dir;

  {
    /* save current directory list position */
    position_t position = 0;
    XmString *strs = NULL;
    XtVaGetValues(w, 
		  XmNtopItemPosition, &position,
		  XmNselectedItems, &strs, 
		  NULL);
    if ((strs) && (position > 1)) /* 1 = .. */
      {
	char *filename = NULL;
	filename = (char *)XmStringUnparse(strs[0], NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
	dirpos_update(fp->dir_list, filename, position);
	XtFree(filename);
      }
  }

  leaving_dir = mus_strdup(fp->last_dir);
  if ((leaving_dir) &&
      (leaving_dir[strlen(leaving_dir) - 1] == '/'))
    leaving_dir[strlen(leaving_dir) - 1] = 0;
  
  fp->reread_directory = true;
  force_directory_reread_and_let_filename_change(fp->dialog);
  fp->reread_directory = false;

  if (leaving_dir)
    {
      position_t pos;
      pos = dirpos_list_top(fp->dir_list, leaving_dir);
      if (pos != POSITION_UNKNOWN)
	XmListSetPos(w, pos);
      free(leaving_dir);
    }
}


static void sort_files_and_redisplay(file_pattern_info *fp)
{
  /* if just sorting, no need to read the directory */
  dir_info *cur_dir;

  cur_dir = fp->current_files;
  if (cur_dir->len > 0)
    {
      XmString *names;
      int i, new_selected_position = -1;
      char *selected_filename = NULL;

      {
	XmString *strs = NULL;
	int selections = 0;
	XtVaGetValues(XmFileSelectionBoxGetChild(fp->dialog, XmDIALOG_LIST), 
		      XmNselectedItems, &strs, 
		      XmNselectedItemCount, &selections,
		      NULL);
	if ((selections > 0) && (strs) && (strs[0]))
	  selected_filename = (char *)XmStringUnparse(strs[0], NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
      }

      snd_sort(fp->sorter_choice, cur_dir->files, cur_dir->len);

      /* here we could use colored text to mark sound files, perhaps different colors for
       *   different chans (as in install-searcher-with-colors), but I would rather have
       *   used different background colors (less intrusive I think).  As far as I can tell,
       *   this is impossible given the current XmList widget -- each item is an internal
       *   "Element", not a label widget or whatever, and the selection color, for example,
       *   is done by hand.
       */

      names = (XmString *)calloc(cur_dir->len, sizeof(XmString));
      for (i = 0; i < cur_dir->len; i++)
	{
	  names[i] = XmStringCreateLocalized(cur_dir->files[i]->full_filename);
	  if ((new_selected_position == -1) &&
	      (mus_strcmp(selected_filename, cur_dir->files[i]->full_filename)))
	    new_selected_position = i;
	}

      XtVaSetValues(fp->dialog, 
		    XmNfileListItems, names, 
		    XmNfileListItemCount, cur_dir->len, 
		    XmNlistUpdated, true, 
		    NULL);

      if (new_selected_position >= 0)
	ensure_list_row_visible(XmFileSelectionBoxGetChild(fp->dialog, XmDIALOG_LIST), new_selected_position);

      for (i = 0; i < cur_dir->len; i++) 
	if (names[i]) 
	  XmStringFree(names[i]);
      free(names);
    }
  else
    {
      /* nothing to sort, but make sure the files list is actually empty */
      XtVaSetValues(fp->dialog, 
		    XmNfileListItems, NULL, 
		    XmNfileListItemCount, 0, 
		    XmNlistUpdated, true, 
		    NULL);
    }
}


static void snd_directory_reader(Widget dialog, XmFileSelectionBoxCallbackStruct *info)
{
  /* replaces the FSB searchProc */
  file_pattern_info *fp;
  dir_info *cur_dir = NULL;
  char *pattern = NULL, *our_dir = NULL;

  XtVaGetValues(dialog, XmNuserData, &fp, NULL);
  if (!(fp->dialog)) fp->dialog = dialog; /* can be null at initialization */

  pattern = (char *)XmStringUnparse(info->pattern, NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
  our_dir = (char *)XmStringUnparse(info->dir,     NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);

  /* get current directory contents, given filter and pattern */
  if (mus_strcmp(pattern, "*"))
    {
      if (fp->filter_choice == NO_FILE_FILTER)
	cur_dir = find_files_in_dir(our_dir);
      else cur_dir = find_filtered_files_in_dir(our_dir, fp->filter_choice);
    }
  else cur_dir = find_filtered_files_in_dir_with_pattern(our_dir, fp->filter_choice, pattern);

  if (fp->current_files) free_dir_info(fp->current_files);
  fp->current_files = cur_dir;
  if (pattern) XtFree(pattern);

  /* set file_pattern_info->selected_filename list_slider_position from history */
  {
    position_t list_pos;
    Widget file_list;
    file_list = XmFileSelectionBoxGetChild(dialog, XmDIALOG_LIST);
    list_pos = dirpos_list_top(fp->dir_list, our_dir);

    /* post the sorted list in the dialog -- alphabetize by default */
    sort_files_and_redisplay(fp);

    if (list_pos != POSITION_UNKNOWN)
      XmListSetPos(file_list, list_pos);
  }

  if ((!fp->last_dir) ||
      (!mus_strcmp(our_dir, fp->last_dir)))
    {
      if (fp->directory_watcher)
	unmonitor_file(fp->directory_watcher);
      fp->directory_watcher = NULL;
      if (fp->last_dir) free(fp->last_dir);
      fp->last_dir = mus_strdup(our_dir);
      fp->reread_directory = false;
    }

  if (our_dir) XtFree(our_dir);
}


static void just_sounds_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  file_pattern_info *fp = (file_pattern_info *)context;
  if (cb->set)
    fp->filter_choice = JUST_SOUNDS_FILTER;
  else fp->filter_choice = NO_FILE_FILTER;
  fp->in_just_sounds_update = true;
  force_directory_reread(fp->dialog);
  fp->in_just_sounds_update = false;
}


/* -------- play selected file handlers -------- */

typedef struct {
  Widget dialog, play_button;
  snd_info *player;
} dialog_play_info;


static void file_dialog_stop_playing(dialog_play_info *dp)
{
#if WITH_AUDIO
  if ((dp->player) && 
      (dp->player->playing)) 
    {
      stop_playing_sound(dp->player, PLAY_BUTTON_UNSET);
      dp->player = NULL;
    }
#endif
}


void clear_deleted_snd_info(void *udp)
{
  dialog_play_info *dp = (dialog_play_info *)udp;
#if WITH_AUDIO
  dp->player = NULL;
#endif
}


#if WITH_AUDIO
static void play_selected_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  dialog_play_info *dp = (dialog_play_info *)context;
  if (cb->set)
    {
      Widget wtmp;
      char *filename = NULL;
      if ((dp->player) && 
	  (dp->player->playing)) 
	stop_playing_sound(dp->player, PLAY_BUTTON_UNSET);
      wtmp = FSB_BOX(dp->dialog, XmDIALOG_TEXT);
      filename = XmTextGetString(wtmp);
      if (filename)
	{
	  if (mus_file_probe(filename))
	    {
	      dp->player = make_sound_readable(filename, false);
	      dp->player->delete_me = (void *)dp;
	      if (dp->player)
		play_sound(dp->player, 0, NO_END_SPECIFIED);
	    }
	  XtFree(filename);
	}
    }
  else file_dialog_stop_playing(dp);
}
#endif


static void add_play_and_just_sounds_buttons(Widget dialog, Widget parent, file_pattern_info *fp, dialog_play_info *dp)
{
  Widget rc;
  int n;
  Arg args[12];

  n = 0;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
  rc = XtCreateManagedWidget("filebuttons-rc", xmRowColumnWidgetClass, parent, args, n);

  n = 0;
  XtSetArg(args[n], XmNset, just_sounds(ss)); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
  fp->just_sounds_button = XtCreateManagedWidget("sound files only", xmToggleButtonWidgetClass, rc, args, n);
  XtAddCallback(fp->just_sounds_button, XmNvalueChangedCallback, just_sounds_callback, (XtPointer)fp);

#if WITH_AUDIO
  n = 0;
  XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
  XtSetArg(args[n], XmNwidth, 20); n++;
  XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
  XtCreateManagedWidget("sep1", xmSeparatorWidgetClass, rc, args, n);

  n = 0;
  /* XmNmarginLeft here refers to the space between the button and its label! */
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_END); n++;
  dp->play_button = XtCreateWidget("play selected sound", xmToggleButtonWidgetClass, rc, args, n);

  XtAddCallback(dp->play_button, XmNvalueChangedCallback, play_selected_callback, (XtPointer)dp);
#endif
}



/* -------- File Open/View/Mix Dialogs -------- */

typedef struct file_dialog_info {
  read_only_t file_dialog_read_only;
  Widget dialog;
  Widget info_frame, info1, info2;     /* labels giving info on selected file, or an error message */
  file_pattern_info *fp;
  dialog_play_info *dp;
  void *unsound_directory_watcher; /* started if file doesn't exist, not a sound file, bogus header, etc (clears error msg if problem changed) */
  void *info_filename_watcher;     /* watch for change in selected file and repost info */
  char *unsound_dirname, *unsound_filename;
  char *info_filename;
  file_popup_info *fpop;
} file_dialog_info;


static void open_file_help_callback(Widget w, XtPointer context, XtPointer info) 
{
  open_file_dialog_help();
}


static void file_cancel_callback(Widget w, XtPointer context, XtPointer info) 
{
  file_dialog_stop_playing((dialog_play_info *)context);
  XtUnmanageChild (w);
}


static void file_wm_delete_callback(Widget w, XtPointer context, XtPointer info) 
{
  file_dialog_stop_playing((dialog_play_info *)context);
}


static void post_sound_info(Widget info1, Widget info2, const char *filename, bool with_filename)
{
  /* filename is known[strongly believed] to be a sound file, etc */
  XmString label;
  char *buf;
  buf = (char *)calloc(LABEL_BUFFER_SIZE, sizeof(char));
  snprintf(buf, LABEL_BUFFER_SIZE, "%s%s%d chan%s, %d Hz, %.3f secs",
	       (with_filename) ? filename_without_directory(filename) : "",
	       (with_filename) ? ": " : "",
	       mus_sound_chans(filename),
	       (mus_sound_chans(filename) > 1) ? "s" : "",
	       mus_sound_srate(filename),
	       (double)mus_sound_duration(filename));
  label = XmStringCreateLocalized(buf);
  XtVaSetValues(info1, 
		XmNlabelString, label, 
		NULL);
  XmStringFree(label);
  snprintf(buf, LABEL_BUFFER_SIZE, "%s, %s%s",
	       mus_header_type_name(mus_sound_header_type(filename)),
	       short_sample_type_name(mus_sound_sample_type(filename), filename),
	       snd_strftime(", %d-%b-%Y", mus_sound_write_date(filename)));
  label = XmStringCreateLocalized(buf);
  XtVaSetValues(info2, XmNlabelString, label, NULL);
  XmStringFree(label);
  free(buf);
}


static void post_file_info(file_dialog_info *fd, const char *filename)
{
#if WITH_AUDIO
  XtManageChild(fd->dp->play_button);
#endif
  post_sound_info(fd->info1, fd->info2, filename, true);
  if (!(XtIsManaged(fd->info1))) 
    XtManageChild(fd->info1);
  if (!(XtIsManaged(fd->info2))) 
    XtManageChild(fd->info2);
  if (!(XtIsManaged(fd->info_frame)))
    XtManageChild(fd->info_frame);
}


static void unpost_file_info(file_dialog_info *fd)
{
#if WITH_AUDIO
  if (XtIsManaged(fd->dp->play_button)) 
    XtUnmanageChild(fd->dp->play_button);
#endif
  if (XtIsManaged(fd->info_frame))
    XtUnmanageChild(fd->info_frame);

  if (fd->info_filename_watcher)
    {
      fd->info_filename_watcher = unmonitor_file(fd->info_filename_watcher);
      if (fd->info_filename) {free(fd->info_filename); fd->info_filename = NULL;}
    }
}


static bool is_empty_file(const char *filename)
{
#ifndef _MSC_VER
  struct stat statbuf;
  if (stat(filename, &statbuf) >= 0) 
    return(statbuf.st_size == (mus_long_t)0);
#endif
  return(false);
}


static int local_error = MUS_NO_ERROR;
static char *local_error_msg = NULL;
static mus_error_handler_t *old_error_handler;

static void local_error2snd(int type, char *msg) 
{
  local_error = type;
  if (local_error_msg) free(local_error_msg);
  if (msg)
    local_error_msg = mus_strdup(msg);
  else local_error_msg = NULL;
}


static bool is_plausible_sound_file(const char *name)
{
  int err = MUS_NO_ERROR;
  if (is_empty_file(name)) return(false);
  old_error_handler = mus_error_set_handler(local_error2snd);
  err = mus_header_read(name);
  mus_error_set_handler(old_error_handler);
  return((err == MUS_NO_ERROR) &&
	 (mus_header_type() != MUS_RAW));
}


static void file_dialog_select_callback(Widget w, XtPointer context, XtPointer info)
{
  file_dialog_info *fd = (file_dialog_info *)context;
  XmString *strs = NULL;
  XtVaGetValues(w, XmNselectedItems, &strs, NULL);
  if (strs) /* can be null if click in empty space */
    {
      char *filename;
      position_t position = 0;
      filename = (char *)XmStringUnparse(strs[0], NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
      if (filename)
	{
	  if (is_plausible_sound_file(filename)) /* forces header read to avoid later unwanted error possibility */
	    post_file_info(fd, filename);
	  XtFree(filename);      
	}
      else unpost_file_info(fd);

      /* save current list position */
      XtVaGetValues(w, XmNtopItemPosition, &position, NULL);
      dirpos_update(fd->fp->dir_list, fd->fp->current_files->dir_name, position);
    }
}


static void unpost_if_filter_changed(Widget w, XtPointer context, XtPointer info)
{
  unpost_file_info((file_dialog_info *)context);
}


static void watch_filename_change(Widget w, XtPointer context, XtPointer info)
{
  /* try to move file list to show possible matches,
   *   if a sound file, show info
   */
  file_dialog_info *fd = (file_dialog_info *)context;
  char *filename = NULL;

  filename = XmTextGetString(w);
  if ((filename) && (*filename))
    {
      XmStringTable files;
      Widget file_list;
      int num_files = 0, pos = -1, l, u;

      file_list = FSB_BOX(fd->dialog, XmDIALOG_LIST);
      XtVaGetValues(fd->dialog,
		    XmNfileListItemCount, &num_files,
		    XmNfileListItems, &files, /* do not free */
		    NULL);
      l = 0; /* hooray for Knuth... */
      u = num_files - 1;
      while (true)
	{
	  int i, comp;
	  char *file_list_file = NULL;

	  if (u < l) break;
	  i = (l + u) / 2;
	  file_list_file = (char *)XmStringUnparse(files[i], NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL); /* p453 */
	  comp = strcmp(file_list_file, filename);
	  XtFree(file_list_file);
	  pos = i + 1;
	  if (comp == 0)
	    break;
	  if (comp < 0) /* files[i] less than filename */
	    l = i + 1;
	  else u = i - 1;
	}
      if (pos > 0)
	ensure_list_row_visible(file_list, pos);

      if ((mus_file_probe(filename)) && 
	  (!is_directory(filename)))
	{
	  if (is_sound_file(filename))
	    post_file_info(fd, filename);
	}
    }
  if (filename) XtFree(filename);
}


static void focus_filename_text_callback(Widget w, XtPointer context, XtPointer info)
{
  XtAddCallback(w, XmNvalueChangedCallback, watch_filename_change, context);
}


static void unfocus_filename_text_callback(Widget w, XtPointer context, XtPointer info)
{
  XtRemoveCallback(w, XmNvalueChangedCallback, watch_filename_change, context);
}


static bool file_is_directory(Widget dialog)
{
  char *filename = NULL;
  bool is_dir = false;
  filename = XmTextGetString(FSB_BOX(dialog, XmDIALOG_TEXT));
  if (filename)
    {
      is_dir = is_directory(filename);
      XtFree(filename);
    }
  return(is_dir);
}


static bool file_is_nonexistent_directory(Widget dialog)
{
  char *filename = NULL;
  bool is_nonexistent_dir = false;
  filename = XmTextGetString(FSB_BOX(dialog, XmDIALOG_TEXT));
  if (filename)
    {
      int len;
      len = strlen(filename);
      if ((!mus_file_probe(filename)) && 
	  (filename[len - 1] == '/'))
	{
	  int i;
	  /* check that there's some hope of making this directory */
	  for (i = len - 2; i > 0; i--)
	    if (filename[i] == '/')
	      {
		filename[i] = '\0';
		is_nonexistent_dir = is_directory(filename);
		break;
	      }
	}
      XtFree(filename);
    }
  return(is_nonexistent_dir);
}


static void reflect_text_in_open_button(Widget w, XtPointer context, XtPointer info)
{
  file_dialog_info *fd = (file_dialog_info *)context;
  /* w here is the text widget, not the button */
  XtSetSensitive(FSB_BOX(fd->dialog, XmDIALOG_OK_BUTTON), (!(file_is_directory(fd->dialog))));
}


static void multifile_completer(widget_t w, void *data)
{
  watch_filename_change(w, (XtPointer)data, NULL);
}


#define FILE_DIALOG_WIDTH 500
#define FILE_DIALOG_HEIGHT 500

static file_dialog_info *make_file_dialog(read_only_t read_only, char *title, char *select_title, 
					  XtCallbackProc file_ok_proc, XtCallbackProc file_help_proc)
{
  /* file selection dialog box with added "Just Sound Files" and "Play selected" toggle buttons and info area,
   *   popups, and so on.  This applies to the Open, Mix, and Insert dialogs.  The save-as
   *   dialogs are handled by make_save_as_dialog below
   */
  Widget w;
  file_dialog_info *fd;
  Arg args[32];
  int n;
  XmString s1, s2, ok_label, filter_list_label, cancel_label;
  Widget wtmp = NULL, rc1, rc2;

  fd = (file_dialog_info *)calloc(1, sizeof(file_dialog_info));
  fd->fp = (file_pattern_info *)calloc(1, sizeof(file_pattern_info));
  fd->fp->in_just_sounds_update = false;
  if (just_sounds(ss))
    fd->fp->filter_choice = JUST_SOUNDS_FILTER;
  else fd->fp->filter_choice = NO_FILE_FILTER;

  fd->dp = (dialog_play_info *)calloc(1, sizeof(dialog_play_info));
  fd->file_dialog_read_only = read_only;
  fd->fpop = (file_popup_info *)calloc(1, sizeof(file_popup_info));
  fd->fpop->fp = fd->fp;

  fd->fp->dir_list = make_dirpos_list();

  w = main_shell(ss);

  s1 = XmStringCreateLocalized(select_title);
  s2 = XmStringCreateLocalized(title);
  ok_label = XmStringCreateLocalized(title);
  filter_list_label = XmStringCreateLocalized((char *)"files listed:");
  cancel_label = XmStringCreateLocalized((char *)I_GO_AWAY);

  n = 0;
  if (open_file_dialog_directory(ss))
    {
      XmString dirstr;
      dirstr = XmStringCreateLocalized(open_file_dialog_directory(ss));
      XtSetArg(args[n], XmNdirectory, dirstr); n++;
    }
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNokLabelString, ok_label); n++;
  XtSetArg(args[n], XmNselectionLabelString, s1); n++;                    /* "open", "mix", "insert", "open read-only:" */
  XtSetArg(args[n], XmNdialogTitle, s2); n++;
  XtSetArg(args[n], XmNfilterLabelString, filter_list_label); n++;        /* default label 'Filter' is confusing in this context */
  XtSetArg(args[n], XmNfileFilterStyle, XmFILTER_HIDDEN_FILES); n++;      /* the dot files mostly just get in the way */
  XtSetArg(args[n], XmNcancelLabelString, cancel_label); n++;
  XtSetArg(args[n], XmNuserData, (XtPointer)(fd->fp)); n++;
  XtSetArg(args[n], XmNfileSearchProc, snd_directory_reader); n++;        /* over-ride Motif's directory reader altogether */  
  XtSetArg(args[n], XmNwidth, FILE_DIALOG_WIDTH); n++;

  XtSetArg(args[n], XmNheight, FILE_DIALOG_HEIGHT); n++;
  XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
  XtSetArg(args[n], XmNnoResize, false); n++;

  fd->dialog = XmCreateFileSelectionDialog(w, title, args, n);
  fd->fp->dialog = fd->dialog;
  fd->dp->dialog = fd->dialog;
  fd->fpop->dialog = fd->dialog;

  XtUnmanageChild(FSB_BOX(fd->dialog, XmDIALOG_DIR_LIST_LABEL)); /* these are obvious */
  XtUnmanageChild(FSB_BOX(fd->dialog, XmDIALOG_LIST_LABEL));
  XtUnmanageChild(FSB_BOX(fd->dialog, XmDIALOG_APPLY_BUTTON));   /* "Filter" button is useless */

  XtVaSetValues(FSB_BOX(fd->dialog, XmDIALOG_FILTER_LABEL), XmNbackground, ss->basic_color, NULL);
  XtVaSetValues(FSB_BOX(fd->dialog, XmDIALOG_SELECTION_LABEL), XmNbackground, ss->basic_color, NULL);

  XmStringFree(s1);
  XmStringFree(s2);
  XmStringFree(ok_label);
  XmStringFree(filter_list_label);
  XmStringFree(cancel_label);

  /* -------- play and just-sounds buttons and info area */
  rc1 = XtVaCreateManagedWidget("filebuttons-rc1", 
				xmRowColumnWidgetClass, fd->dialog,
				XmNorientation, XmVERTICAL,
				NULL);

  add_play_and_just_sounds_buttons(fd->dialog, rc1, fd->fp, fd->dp);

  fd->info_frame = XtVaCreateWidget("", xmFrameWidgetClass, rc1, NULL);
  rc2 = XtVaCreateManagedWidget("info-rc2", 
				xmRowColumnWidgetClass, fd->info_frame,
				XmNorientation, XmVERTICAL,
				XmNbackground, ss->highlight_color,
				NULL);
  fd->info1 = XtVaCreateManagedWidget("", xmLabelWidgetClass, rc2, XmNbackground, ss->highlight_color, NULL);
  fd->info2 = XtVaCreateManagedWidget("", xmLabelWidgetClass, rc2, XmNbackground, ss->highlight_color, NULL);


  /* -------- Snd-like color schemes */
  color_file_selection_box(fd->dialog);
  XtVaSetValues(fd->fp->just_sounds_button, XmNselectColor, ss->selection_color, NULL);
#if WITH_AUDIO
  XtVaSetValues(fd->dp->play_button, XmNselectColor, ss->selection_color, NULL);
#endif

  /* -------- completions */

  wtmp = FSB_BOX(fd->dialog, XmDIALOG_TEXT);
  add_completer_to_builtin_textfield(wtmp, add_completer_func_with_multicompleter(sound_filename_completer, (void *)fd, multifile_completer));

  XtAddCallback(wtmp, XmNfocusCallback, focus_filename_text_callback, (XtPointer)fd);
  XtAddCallback(wtmp, XmNlosingFocusCallback, unfocus_filename_text_callback, (XtPointer)fd);

  wtmp = FSB_BOX(fd->dialog, XmDIALOG_FILTER_TEXT);
  add_completer_to_builtin_textfield(wtmp, add_completer_func(filename_completer, NULL));

  XtAddCallback(wtmp, XmNvalueChangedCallback, unpost_if_filter_changed, (XtPointer)fd);

  /* -------- base button callbacks */
  XtAddCallback(fd->dialog, XmNokCallback, file_ok_proc, (XtPointer)fd);
  XtAddCallback(fd->dialog, XmNcancelCallback, file_cancel_callback, (XtPointer)(fd->dp));
  XtAddCallback(fd->dialog, XmNhelpCallback, file_help_proc, NULL);
  XtAddCallback(FSB_BOX(fd->dialog, XmDIALOG_LIST), XmNbrowseSelectionCallback, file_dialog_select_callback, (XtPointer)fd);

  /* -------- single click in directory list */
  XtAddCallback(FSB_BOX(fd->dialog, XmDIALOG_DIR_LIST), XmNbrowseSelectionCallback, file_change_directory_callback, (XtPointer)(fd->fp));

  /* -------- the WM 'close' button */
  {
    Atom wm_delete_window;
    wm_delete_window = XmInternAtom(main_display(ss), (char *)"WM_DELETE_WINDOW", false);
    XmAddWMProtocolCallback(XtParent(fd->dialog), wm_delete_window, file_wm_delete_callback, (XtPointer)(fd->dp));
  }

  /* -------- special popups */
  add_file_popups(fd->fpop);

  XtSetSensitive(FSB_BOX(fd->dialog, XmDIALOG_OK_BUTTON), (!(file_is_directory(fd->dialog))));
  XtAddCallback(FSB_BOX(fd->dialog, XmDIALOG_TEXT), XmNvalueChangedCallback, reflect_text_in_open_button, (void *)fd);

  return(fd);
}


/* -------- File:Open/View dialogs -------- */


static void file_open_error(const char *error_msg, file_dialog_info *fd)
{
  XmString msg;
  msg = XmStringCreateLocalized((char *)error_msg);
  XtVaSetValues(fd->info1, 
		XmNlabelString, msg, 
		NULL);
  XmStringFree(msg);

  if (XtIsManaged(fd->info2))
    XtUnmanageChild(fd->info2);
  if (!(XtIsManaged(fd->info_frame))) 
    XtManageChild(fd->info_frame);
}


static void redirect_file_open_error(const char *error_msg, void *ufd)
{
  /* called from snd_error, redirecting error handling to the dialog */
  file_open_error(error_msg, (file_dialog_info *)ufd);
}


static void open_modify_callback(Widget w, XtPointer context, XtPointer info);

static void unpost_open_modify_error(file_dialog_info *fd)
{
  Widget dialog_filename_text;
  if (XtIsManaged(fd->info_frame))
    XtUnmanageChild(fd->info_frame);
  dialog_filename_text = FSB_BOX(fd->dialog, XmDIALOG_TEXT);
  if (dialog_filename_text) 
    XtRemoveCallback(dialog_filename_text, XmNmodifyVerifyCallback, open_modify_callback, (XtPointer)fd);

  if (fd->unsound_directory_watcher)
    {
      fd->unsound_directory_watcher = unmonitor_file(fd->unsound_directory_watcher);
      if (fd->unsound_dirname) {free(fd->unsound_dirname); fd->unsound_dirname = NULL;}
      if (fd->unsound_filename) {free(fd->unsound_filename); fd->unsound_filename = NULL;}
    }
}


static void open_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  file_dialog_info *fd = (file_dialog_info *)context;
  XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)info;
  if (!(fd->fp->in_just_sounds_update)) /* auto trigger from just_sounds button -- unwanted! */
    unpost_open_modify_error(fd);
  cbs->doit = true; /* fixup filename elsewhere -- returning false here makes the thing beep! */
}


static void clear_error_if_open_changes(Widget dialog, file_dialog_info *data)
{
  Widget dialog_filename_text;
  dialog_filename_text = FSB_BOX(dialog, XmDIALOG_TEXT);
  if (dialog_filename_text) 
    XtAddCallback(dialog_filename_text, XmNmodifyVerifyCallback, open_modify_callback, (XtPointer)data);
}


static void file_open_ok_callback(Widget w, XtPointer context, XtPointer info) 
{
  file_dialog_info *fd = (file_dialog_info *)context;
  XmFileSelectionBoxCallbackStruct *cbs = (XmFileSelectionBoxCallbackStruct *)info;
  char *filename = NULL;
  if (XmGetFocusWidget(fd->dialog) == FSB_BOX(fd->dialog, XmDIALOG_FILTER_TEXT)) return;

  filename = (char *)XmStringUnparse(cbs->value, NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
  if ((!filename) || (!(*filename)))
    {
      file_open_error("no filename given", fd);
      clear_error_if_open_changes(fd->dialog, fd);
    }
  else
    {
      file_dialog_stop_playing(fd->dp);
      if (!(is_directory(filename)))               /* this can be a directory name if the user clicked 'ok' when he meant 'cancel' */
	{
	  snd_info *sp;
	  redirect_snd_error_to(redirect_file_open_error, (void *)fd);
	  ss->requestor_dialog = w;
	  ss->open_requestor = FROM_OPEN_DIALOG;
	  ss->open_requestor_data = NULL;
	  sp = snd_open_file(filename, fd->file_dialog_read_only);
	  redirect_snd_error_to(NULL, NULL);
	  if (sp) 
	    {
	      XtUnmanageChild(w);
	      remember_filename(filename, fd->fpop->file_text_names);
	      select_channel(sp, 0);
	    }
	  else
	    {
	      if (ss->open_requestor != FROM_RAW_DATA_DIALOG)
		{
		  clear_error_if_open_changes(fd->dialog, fd);
		  /* whatever the error was, I think it is correct here to unpost the error
		   *   if the underlying file is either changed or created.
		   */
		}
	    }
	  if (filename) XtFree(filename);
	}
      else 
	{
	  char *str;
	  str = mus_format("%s is a directory", filename);
	  file_open_error(str, fd);
	  clear_error_if_open_changes(fd->dialog, fd);
	  free(str);
	}
    }
}


static file_dialog_info *odat = NULL;

widget_t make_open_file_dialog(read_only_t read_only, bool managed)
{
  char *title, *select_title;
  if (read_only == FILE_READ_ONLY)  
    {
      title = (char *)"View";
      select_title = (char *)"open read-only:";
    }
  else
    {
      title = (char *)"Open";
      select_title = (char *)"open:";
    }
  if (!odat)
    {
      XmString cancel_label;
      odat = make_file_dialog(read_only, title, select_title, file_open_ok_callback, open_file_help_callback);
      set_dialog_widget(FILE_OPEN_DIALOG, odat->dialog);

      /* now preload last n files opened before this point */
      preload_filenames(odat->fpop->file_text_names);

      cancel_label = XmStringCreateLocalized((char *)I_GO_AWAY);
      XtVaSetValues(odat->dialog, XmNcancelLabelString, cancel_label, NULL);
      XmStringFree(cancel_label);
    }
  else
    {
      if (odat->file_dialog_read_only != read_only)
	{
	  XmString s1, s2;
	  s1 = XmStringCreateLocalized(select_title);
	  s2 = XmStringCreateLocalized(title);
	  XtVaSetValues(odat->dialog, 
			XmNselectionLabelString, s1, 
			XmNdialogTitle, s2, 
			XmNokLabelString, s2, /* "ok" button label can be either "View" or "Open" */
			NULL);
	  XmStringFree(s1);
	  XmStringFree(s2);
	}
      odat->file_dialog_read_only = read_only;
      if (odat->fp->reread_directory) 
	{
	  force_directory_reread(odat->dialog);
	  odat->fp->reread_directory = false;
	}
    }
  if ((managed) && (!(XtIsManaged(odat->dialog))))
    XtManageChild(odat->dialog);

  return(odat->dialog);
}



/* -------- File:Mix dialog -------- */

static void file_mix_ok_callback(Widget w, XtPointer context, XtPointer info)
{
  XmFileSelectionBoxCallbackStruct *cbs = (XmFileSelectionBoxCallbackStruct *)info;
  file_dialog_info *fd = (file_dialog_info *)context;
  char *filename = NULL;
  
  filename = (char *)XmStringUnparse(cbs->value, NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
  if ((!filename) || (!(*filename)))
    {
      file_open_error("no filename given", fd);
      clear_error_if_open_changes(fd->dialog, fd);
    }
  else
    {
      file_dialog_stop_playing(fd->dp);
      if (!(is_directory(filename)))               /* this can be a directory name if the user clicked 'ok' when he meant 'cancel' */
	{
	  int id_or_error;
	  snd_info *sp;
	  sp = any_selected_sound();
	  redirect_snd_error_to(redirect_file_open_error, (void *)fd);
	  ss->requestor_dialog = w;
	  ss->open_requestor = FROM_MIX_DIALOG;
	  ss->open_requestor_data = NULL;
	  id_or_error = mix_complete_file_at_cursor(sp, filename);
	  /* "id_or_error" here is either one of the mix id's or an error indication such as MIX_FILE_NO_MIX */
	  /*    the possible error conditions have been checked already, or go through snd_error */
	  redirect_snd_error_to(NULL, NULL);
	  if (id_or_error < 0) /* actually -1 .. -3 */
	    {
	      if (ss->open_requestor != FROM_RAW_DATA_DIALOG)
		{
		  clear_error_if_open_changes(fd->dialog, fd);
		}
	    }
	  else 
	    {
	      status_report(sp, "%s mixed in at cursor", filename);
	      remember_filename(filename, fd->fpop->file_text_names);
	    }
	  if (filename) XtFree(filename);
	}
      else 
	{
	  char *str;
	  str = mus_format("%s is a directory", filename);
	  file_open_error(str, fd);
	  clear_error_if_open_changes(fd->dialog, fd);
	  free(str);
	}
    }
}
  

static void mix_file_help_callback(Widget w, XtPointer context, XtPointer info) 
{
  mix_file_dialog_help();
}


static file_dialog_info *mdat = NULL;

static Xen mix_open_file_watcher(Xen hook_or_reason)
{
  if ((mdat->dialog) &&
      (XtIsManaged(mdat->dialog)))
    set_sensitive(FSB_BOX(mdat->dialog, XmDIALOG_OK_BUTTON), (bool)any_selected_sound());
  return(Xen_false);
}

static void add_reflect_mix_hook(void);

widget_t make_mix_file_dialog(bool managed)
{
  /* called from the menu */
  if (!mdat)
    {
      mdat = make_file_dialog(FILE_READ_ONLY, (char *)"Mix Sound", (char *)"mix in:", file_mix_ok_callback, mix_file_help_callback);
      set_dialog_widget(FILE_MIX_DIALOG, mdat->dialog);
      add_reflect_mix_hook();
    }
  else
    {
      if (mdat->fp->reread_directory) 
	{
	  force_directory_reread(mdat->dialog);
	  mdat->fp->reread_directory = false;
	}
    }
  if ((managed) && (!XtIsManaged(mdat->dialog)))
    XtManageChild(mdat->dialog);
  return(mdat->dialog);
}


/* -------- File:Insert dialog -------- */

static void file_insert_ok_callback(Widget w, XtPointer context, XtPointer info)
{
  XmFileSelectionBoxCallbackStruct *cbs = (XmFileSelectionBoxCallbackStruct *)info;
  file_dialog_info *fd = (file_dialog_info *)context;
  char *filename = NULL;
  
  filename = (char *)XmStringUnparse(cbs->value, NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
  if ((!filename) || (!(*filename)))
    {
      file_open_error("no filename given", fd);
      clear_error_if_open_changes(fd->dialog, fd);
    }
  else
    {
      file_dialog_stop_playing(fd->dp);
      if (!(is_directory(filename)))               /* this can be a directory name if the user clicked 'ok' when he meant 'cancel' */
	{
	  bool ok;
	  snd_info *sp;
	  sp = any_selected_sound();
	  ss->requestor_dialog = w;
	  ss->open_requestor = FROM_INSERT_DIALOG;
	  ss->open_requestor_data = NULL;
	  redirect_snd_error_to(redirect_file_open_error, (void *)fd);
	  ok = insert_complete_file_at_cursor(sp, filename);
	  redirect_snd_error_to(NULL, NULL);
	  if (!ok)
	    {
	      if (ss->open_requestor != FROM_RAW_DATA_DIALOG)
		{
		  clear_error_if_open_changes(fd->dialog, fd);
		  /* ideally insert_complete_file would return an indication of what the error was... */
		}
	    }
	  else 
	    {
	      status_report(sp, "%s inserted at cursor", filename);
	      remember_filename(filename, fd->fpop->file_text_names);
	    }
	  if (filename) XtFree(filename);
	}
      else 
	{
	  char *str;
	  str = mus_format("%s is a directory", filename);
	  file_open_error(str, fd);
	  clear_error_if_open_changes(fd->dialog, fd);
	  free(str);
	}
    }
}
  

static void insert_file_help_callback(Widget w, XtPointer context, XtPointer info) 
{
  insert_file_dialog_help();
}


static file_dialog_info *idat = NULL;

static Xen insert_open_file_watcher(Xen hook_or_reason)
{
  if ((idat->dialog) &&
      (XtIsManaged(idat->dialog)))
    set_sensitive(FSB_BOX(idat->dialog, XmDIALOG_OK_BUTTON), (bool)any_selected_sound());
  return(Xen_false);
}

static void add_reflect_insert_hook(void);

widget_t make_insert_file_dialog(bool managed)
{
  if (!idat)
    {
      idat = make_file_dialog(FILE_READ_ONLY, (char *)"Insert Sound", (char *)"insert:", file_insert_ok_callback, insert_file_help_callback);
      set_dialog_widget(FILE_INSERT_DIALOG, idat->dialog);
      add_reflect_insert_hook();
    }
  else
    {
      if (idat->fp->reread_directory) 
	{
	  force_directory_reread(idat->dialog);
	  idat->fp->reread_directory = false;
	}
    }
  if ((managed) && (!XtIsManaged(idat->dialog)))
    XtManageChild(idat->dialog);
  return(idat->dialog);
}


/* -------- reflect outside changes -------- */

void set_open_file_play_button(bool val)
{
#if WITH_AUDIO
  if ((odat) && (odat->dp->play_button))
    XmToggleButtonSetState(odat->dp->play_button, (Boolean)val, false);
  if ((mdat) && (mdat->dp->play_button))
    XmToggleButtonSetState(mdat->dp->play_button, (Boolean)val, false);
  if ((idat) && (idat->dp->play_button))
    XmToggleButtonSetState(idat->dp->play_button, (Boolean)val, false);
#endif
}


void alert_new_file(void) 
{
  if (ss->file_monitor_ok) return;
  /* ideally this would include the save-as dialogs */
  if (odat)
    {
      odat->fp->reread_directory = true;
      if (XtIsManaged(odat->dialog))
	{
	  force_directory_reread(odat->dialog);
	  odat->fp->reread_directory = false;
	}
    }
  if (mdat)
    {
      mdat->fp->reread_directory = true;
      if (XtIsManaged(mdat->dialog))
	{
	  force_directory_reread(mdat->dialog);
	  mdat->fp->reread_directory = false;
	}
    }
  if (idat)
    {
      idat->fp->reread_directory = true;
      if (XtIsManaged(idat->dialog))
	{
	  force_directory_reread(idat->dialog);
	  idat->fp->reread_directory = false;
	}
    }
}


void reflect_just_sounds(void)
{
  if ((odat) && (odat->fp->just_sounds_button))
    XmToggleButtonSetState(odat->fp->just_sounds_button, just_sounds(ss), true);
  if ((mdat) && (mdat->fp->just_sounds_button))
    XmToggleButtonSetState(mdat->fp->just_sounds_button, just_sounds(ss), true);
  if ((idat) && (idat->fp->just_sounds_button))
    XmToggleButtonSetState(idat->fp->just_sounds_button, just_sounds(ss), true);
}



/* ---------------- file data panel ---------------- */

#define NUM_VISIBLE_HEADERS 5

char *get_file_dialog_sound_attributes(file_data *fdat, int *srate, int *chans, mus_header_t *header_type, 
				       mus_sample_t *sample_type, mus_long_t *location, mus_long_t *samples, int min_chan)
{
  char *str;
  int n;
  int res;
  int *ns = NULL;
  fdat->error_widget = NOT_A_SCANF_WIDGET;
  fdat->scanf_widget = NOT_A_SCANF_WIDGET;

  if ((srate) && (fdat->srate_text))
    {
      str = XmTextGetString(fdat->srate_text); 
      fdat->scanf_widget = SRATE_WIDGET;
      if ((str) && (*str))
	{
	  (*srate) = string_to_int(str, 1, "srate"); 
	  XtFree(str);
	}
      else snd_error_without_format("no srate?");
    }

  if ((chans) && (fdat->chans_text))
    {
      str = XmTextGetString(fdat->chans_text); 
      fdat->scanf_widget = CHANS_WIDGET;
      if ((str) && (*str))
	{
	  (*chans) = string_to_int(str, min_chan, "chans"); 
	  XtFree(str);
	}
      else
	{
	  if (min_chan > 0)
	    snd_error_without_format("no chans?");
	}
    }
  
  if ((location) && (fdat->location_text))
    {
      str = XmTextGetString(fdat->location_text); 
      fdat->scanf_widget = DATA_LOCATION_WIDGET;
      if ((str) && (*str))
	{
	  (*location) = string_to_mus_long_t(str, 0, "data location"); 
	  XtFree(str);
	}
      else snd_error_without_format("no data location?");
    }

  if ((samples) && (fdat->samples_text))
    {
      str = XmTextGetString(fdat->samples_text); 
      fdat->scanf_widget = SAMPLES_WIDGET;
      if ((str) && (*str))
	{
	  (*samples) = string_to_mus_long_t(str, 0, "samples"); 
	  XtFree(str);
	}
      else snd_error_without_format("no samples?");
    }
  fdat->scanf_widget = SAMPLES_WIDGET;

  if ((header_type) && (fdat->header_type_list))
    {
      res = XmListGetSelectedPos(fdat->header_type_list, &ns, &n);
      if (res)
	{
	  (*header_type) = position_to_header_type(ns[0] - 1);
	  fdat->current_header_type = (*header_type);
	  free(ns); 
	  ns = NULL;
	}
    }

  if ((sample_type) && (fdat->sample_type_list))
    {
      res = XmListGetSelectedPos(fdat->sample_type_list, &ns, &n);
      if (res)
	{
	  (*sample_type) = position_to_sample_type(fdat->current_header_type, ns[0] - 1);
	  fdat->current_sample_type = (*sample_type);
	  free(ns); 
	  ns = NULL;
	}
    }

  if (fdat->comment_text) 
    {
      char *comment = NULL;
      comment = XmTextGetString(fdat->comment_text);
      if (comment)
	{
	  str = mus_strdup(comment);
	  XtFree(comment);
	  return(str);
	}
    }

  return(NULL);
}


#define IGNORE_DATA_LOCATION -1
#define IGNORE_SAMPLES MUS_UNKNOWN_SAMPLE
#define IGNORE_CHANS -1
#define IGNORE_SRATE -1
#define IGNORE_HEADER_TYPE MUS_UNKNOWN_HEADER

static void set_file_dialog_sound_attributes(file_data *fdat, mus_header_t header_type, mus_sample_t sample_type, 
					     int srate, int chans, mus_long_t location, mus_long_t samples, char *comment)
{
  int i;
  const char **fl = NULL;
  XmString *strs;

  if (header_type != IGNORE_HEADER_TYPE)
    fdat->current_header_type = header_type;
  else fdat->current_header_type = MUS_RAW;
  fdat->current_sample_type = sample_type;
  fl = header_type_and_sample_type_to_position(fdat, fdat->current_header_type, fdat->current_sample_type);
  if (!fl) return;
  
  if ((header_type != IGNORE_HEADER_TYPE) &&
      (fdat->header_type_list))
    {
      XmListSelectPos(fdat->header_type_list, fdat->header_type_pos + 1, false);
      ensure_list_row_visible(fdat->header_type_list, fdat->header_type_pos + 1);
    }

  strs = (XmString *)malloc(fdat->sample_types * sizeof(XmString)); 
  for (i = 0; i < fdat->sample_types; i++) 
    strs[i] = XmStringCreateLocalized((char *)fl[i]);
  XtVaSetValues(fdat->sample_type_list, 
		XmNitems, strs, 
		XmNitemCount, fdat->sample_types, 
		NULL);
  for (i = 0; i < fdat->sample_types; i++)
    XmStringFree(strs[i]);
  free(strs); 
  XmListSelectPos(fdat->sample_type_list, fdat->sample_type_pos + 1, false);
  ensure_list_row_visible(fdat->sample_type_list, fdat->sample_type_pos + 1);

  if ((srate != IGNORE_SRATE) && 
      (fdat->srate_text))
    widget_int_to_text(fdat->srate_text, srate);

  if ((chans != IGNORE_CHANS) && 
      (fdat->chans_text))
    widget_int_to_text(fdat->chans_text, chans);

  if (fdat->comment_text) 
    XmTextSetString(fdat->comment_text, comment);

  if ((location != IGNORE_DATA_LOCATION) && 
      (fdat->location_text))
    widget_mus_long_t_to_text(fdat->location_text, location);

  if ((samples != IGNORE_SAMPLES) && 
      (fdat->samples_text))
    widget_mus_long_t_to_text(fdat->samples_text, samples);
}


/* -------- error handling -------- */

/* if an error occurs, a callback is added to the offending text widget, and an error is
 *   posted in the error_text label.  When the user modifies the bad entry, the callback
 *   erases the error message, and removes itself from the text widget.
 */

static void clear_dialog_error(file_data *fd)
{
  if (XtIsManaged(fd->error_text))
    {
      XtUnmanageChild(fd->error_text);
      if (fd->comment_text)
	{
	  XtVaSetValues(fd->comment_text, 
			XmNbottomAttachment, XmATTACH_FORM,
			NULL);
	}
    }
}


static void show_dialog_error(file_data *fd)
{
  if (!(XtIsManaged(fd->error_text))) 
    {
      if (fd->comment_text)
	{
	  XtVaSetValues(fd->comment_text, 
			XmNbottomAttachment, XmATTACH_WIDGET,
			XmNbottomWidget, fd->error_text,
			NULL);
	}
      XtManageChild(fd->error_text);
    }
}


static void post_file_dialog_error(const char *error_msg, file_data *fd)
{
  XmString msg;
  msg = XmStringCreateLocalized((char *)error_msg);
  XtVaSetValues(fd->error_text, 
		XmNbackground, ss->yellow,
		XmNlabelString, msg, 
		NULL);
  XmStringFree(msg);
  show_dialog_error(fd);
}


static void redirect_post_file_dialog_error(const char *error_msg, void *ufd)
{
  post_file_dialog_error(error_msg, (file_data *)ufd);
}


static void filename_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  file_data *fd = (file_data *)context;
  XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)info;
  Widget dialog_filename_text;
  clear_dialog_error(fd);
  dialog_filename_text = FSB_BOX(fd->dialog, XmDIALOG_TEXT);
  if (dialog_filename_text) XtRemoveCallback(dialog_filename_text, XmNmodifyVerifyCallback, filename_modify_callback, context);
  cbs->doit = true;
}


static void clear_error_if_filename_changes(Widget dialog, file_data *data)
{
  Widget dialog_filename_text;
  dialog_filename_text = FSB_BOX(dialog, XmDIALOG_TEXT);
  if (dialog_filename_text) 
    XtAddCallback(dialog_filename_text, XmNmodifyVerifyCallback, filename_modify_callback, (XtPointer)data);
}


static void chans_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  file_data *fd = (file_data *)context;
  XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)info;
  clear_dialog_error(fd);
  XtRemoveCallback(fd->chans_text, XmNmodifyVerifyCallback, chans_modify_callback, context);
  cbs->doit = true;
}


static void clear_error_if_chans_changes(Widget dialog, file_data *fd)
{
  if (fd->chans_text) XtAddCallback(fd->chans_text, XmNmodifyVerifyCallback, chans_modify_callback, (XtPointer)fd);
}


static void panel_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  file_data *fd = (file_data *)context;
  XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)info;
  clear_dialog_error(fd);
  XtRemoveCallback(w, XmNmodifyVerifyCallback, panel_modify_callback, context);
  cbs->doit = true;
}


static void clear_error_if_panel_changes(Widget dialog, file_data *fd)
{
  Widget baddy;
  switch (fd->error_widget)
    {
    case SRATE_WIDGET:         baddy = fd->srate_text;    break;
    case DATA_LOCATION_WIDGET: baddy = fd->location_text; break;
    case SAMPLES_WIDGET:       baddy = fd->samples_text;  break;
    default:                   baddy = fd->chans_text;    break;
    }
  if (baddy) XtAddCallback(baddy, XmNmodifyVerifyCallback, panel_modify_callback, (XtPointer)fd);
}


static void post_file_panel_error(const char *error_msg, void *ufd)
{
  file_data *fd = (file_data *)ufd;
  fd->error_widget = fd->scanf_widget;
  post_file_dialog_error(error_msg, fd);
}


static void file_data_type_callback(Widget w, XtPointer context, XtPointer info) 
{
  int pos;
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)info;
  file_data *fd;

  XtVaGetValues(w, XmNuserData, &fd, NULL);
  pos = cbs->item_position - 1;
  if (position_to_header_type(pos) != fd->current_header_type)
    {
      position_to_header_type_and_sample_type(fd, pos);
      set_file_dialog_sound_attributes(fd,
				       fd->current_header_type,
				       fd->current_sample_type,
				       IGNORE_SRATE, IGNORE_CHANS, IGNORE_DATA_LOCATION, IGNORE_SAMPLES, 
				       NULL);
    }
}


static void file_sample_type_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)info;
  file_data *fd;
  XtVaGetValues(w, XmNuserData, &fd, NULL);
  fd->current_sample_type = position_to_sample_type(fd->current_header_type, cbs->item_position - 1);
}


static void file_data_src_callback(Widget w, XtPointer context, XtPointer info)
{
  file_data *fd = (file_data *)context;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  fd->src = cb->set;
}


static void file_data_auto_comment_callback(Widget w, XtPointer context, XtPointer info)
{
  file_data *fd = (file_data *)context;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  fd->auto_comment = cb->set;
}



/* ---------------- File Data Panel ---------------- */


#define PANEL_COMMENT_SPACE 16
#define WITHOUT_AUTO_COMMENT false
#define WITH_SRATE true
#define WITHOUT_SRATE false


static file_data *make_file_data_panel(Widget parent, const char *name, Arg *in_args, int in_n, 
				       dialog_channels_t with_chan, 
				       mus_header_t header_type, 
				       mus_sample_t sample_type,
				       dialog_data_location_t with_loc, 
				       dialog_samples_t with_samples,
				       dialog_header_type_t with_header_type,
				       dialog_comment_t with_comment,
				       header_choice_t header_choice,
				       bool with_src, bool with_auto_comment)
{
  Widget form, header_label, data_label, srate_label, chans_label, sep1, sep2 = NULL, sep3, sep4;
  Widget comment_label = NULL, location_label, samples_label;
  file_data *fdat;
  Arg args[32];
  int i, n;
  XmString *strs;
  int nsample_types = 0, nheaders = 0;
  const char **sample_types = NULL, **header_types = NULL;

  switch (header_choice)
    {
    case WITH_READABLE_HEADERS: header_types = short_readable_headers(&nheaders); break;
    case WITH_WRITABLE_HEADERS: header_types = short_writable_headers(&nheaders); break;
    case WITH_BUILTIN_HEADERS:  header_types = short_builtin_headers(&nheaders);  break;
    }

  fdat = (file_data *)calloc(1, sizeof(file_data));
  fdat->src = save_as_dialog_src(ss);
  fdat->auto_comment = save_as_dialog_auto_comment(ss);
  fdat->saved_comment = NULL;
  fdat->current_header_type = header_type;
  fdat->current_sample_type = sample_type;
  sample_types = header_type_and_sample_type_to_position(fdat, header_type, sample_type);
  nsample_types = fdat->sample_types;

  /* pick up all args from caller -- args here are attachment points */
  form = XtCreateManagedWidget(name, xmFormWidgetClass, parent, in_args, in_n);

  if (with_header_type == WITH_HEADER_TYPE_FIELD)
    {
      n = 0;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
      XtSetArg(args[n], XmNwidth, 5); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      sep1 = XtCreateManagedWidget("sep1", xmSeparatorWidgetClass, form, args, n);
      
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, sep1); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      header_label = XtCreateManagedWidget("header", xmLabelWidgetClass, form, args, n);
      
      /* what is selected depends on current type */
      strs = (XmString *)calloc(nheaders, sizeof(XmString)); 
      for (i = 0; i < nheaders; i++) 
	strs[i] = XmStringCreateLocalized((char *)header_types[i]);

      n = 0;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, header_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, sep1); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlistMarginWidth, 1); n++;
      XtSetArg(args[n], XmNuserData, (XtPointer)fdat); n++;
      XtSetArg(args[n], XmNitems, strs); n++;
      XtSetArg(args[n], XmNitemCount, nheaders); n++;
      XtSetArg(args[n], XmNvisibleItemCount, NUM_VISIBLE_HEADERS); n++;
      fdat->header_type_list = XmCreateScrolledList(form, (char *)"header-type", args, n);
      XtManageChild(fdat->header_type_list);

      for (i = 0; i < nheaders; i++) 
	XmStringFree(strs[i]);
      free(strs);
      XmListSelectPos(fdat->header_type_list, fdat->header_type_pos + 1, false);
      XtAddCallback(fdat->header_type_list, XmNbrowseSelectionCallback, file_data_type_callback, NULL);
      
      n = 0;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, fdat->header_type_list); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
      XtSetArg(args[n], XmNwidth, 15); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      sep2 = XtCreateManagedWidget("sep2", xmSeparatorWidgetClass, form, args, n);
    }

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  if (with_header_type == WITH_HEADER_TYPE_FIELD)
    {
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, sep2); n++;
    }
  else
    {
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    }
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  data_label = XtCreateManagedWidget("data", xmLabelWidgetClass, form, args, n);

  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, data_label); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  if (with_header_type == WITH_HEADER_TYPE_FIELD)
    {
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, sep2); n++;
    }
  else
    {
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    }
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNuserData, (XtPointer)fdat); n++;
  fdat->sample_type_list = XmCreateScrolledList(form, (char *)"sample-type", args, n);

  strs = (XmString *)calloc(nsample_types, sizeof(XmString)); 
  for (i = 0; i < nsample_types; i++) 
    strs[i] = XmStringCreateLocalized((char *)sample_types[i]);
  XtVaSetValues(fdat->sample_type_list, 
		XmNitems, strs, 
		XmNitemCount, nsample_types, 
		NULL);
  for (i = 0; i < nsample_types; i++) 
    XmStringFree(strs[i]);
  free(strs);

  XmListSelectPos(fdat->sample_type_list, fdat->sample_type_pos + 1, false);
  XtManageChild(fdat->sample_type_list);
  XtAddCallback(fdat->sample_type_list, XmNbrowseSelectionCallback, file_sample_type_callback, NULL);

  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, fdat->sample_type_list); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
  XtSetArg(args[n], XmNwidth, 15); n++;
  XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
  sep3 = XtCreateManagedWidget("sep3", xmSeparatorWidgetClass, form, args, n);


  /* srate */
  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, sep3); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  srate_label = XtCreateManagedWidget("srate", xmLabelWidgetClass, form, args, n);

  n = 0;
  XtSetArg(args[n], XmNcolumns, 8); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, srate_label); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, sep3); n++;
  if (with_src)
    {
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
    }
  else
    {
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    }
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
  fdat->srate_text = make_textfield_widget("srate-text", form, args, n, NOT_ACTIVATABLE, add_completer_func(srate_completer, NULL));

  if (with_src)
    {
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, srate_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, fdat->srate_text); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++; /* this is probably clobbered by color_file_selection_box */
      fdat->src_button = make_togglebutton_widget("src", form, args, n);
      XtAddCallback(fdat->src_button, XmNvalueChangedCallback, file_data_src_callback, (XtPointer)fdat);
      XmToggleButtonSetState(fdat->src_button, fdat->src, false);
    }

  if (with_chan != WITHOUT_CHANNELS_FIELD)
    {
      /* chans */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, fdat->srate_text); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, sep3); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      chans_label = XtCreateManagedWidget((char *)((with_chan == WITH_CHANNELS_FIELD) ? "channels" : "extract channel"), xmLabelWidgetClass, form, args, n);

      n = 0;
      XtSetArg(args[n], XmNcolumns, 6); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, chans_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, sep3); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      fdat->chans_text = make_textfield_widget("chans-text", form, args, n, NOT_ACTIVATABLE, NO_COMPLETER);
      XmTextFieldSetString(fdat->chans_text, (char *)"0");

      if (with_loc == WITH_DATA_LOCATION_FIELD)
	{
	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
	  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
	  XtSetArg(args[n], XmNtopWidget, fdat->chans_text); n++;
	  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
	  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
	  XtSetArg(args[n], XmNleftWidget, sep3); n++;
	  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
	  location_label = XtCreateManagedWidget("data location", xmLabelWidgetClass, form, args, n);

	  n = 0;
	  XtSetArg(args[n], XmNcolumns, 6); n++;
	  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
	  XtSetArg(args[n], XmNtopWidget, location_label); n++;
	  XtSetArg(args[n], XmNbottomAttachment, (with_samples == WITHOUT_SAMPLES_FIELD) ? XmATTACH_FORM : XmATTACH_NONE); n++;
	  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
	  XtSetArg(args[n], XmNleftWidget, sep3); n++;
	  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
	  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
	  fdat->location_text = make_textfield_widget("location-text", form, args, n, NOT_ACTIVATABLE, NO_COMPLETER);
	}
    }

  if (with_samples == WITH_SAMPLES_FIELD)
    {
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, ((fdat->location_text) ? fdat->location_text : 
				       ((fdat->chans_text) ? fdat->chans_text : fdat->srate_text))); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, sep3); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      samples_label = XtCreateManagedWidget("samples", xmLabelWidgetClass, form, args, n);

      n = 0;
      XtSetArg(args[n], XmNcolumns, 8); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, samples_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, sep3); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      fdat->samples_text = make_textfield_widget("samples-text", form, args, n, NOT_ACTIVATABLE, NO_COMPLETER);
    }

  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, form); n++; /* form is the internal XmForm widget holding the lists etc */
  XtSetArg(args[n], XmNbottomAttachment, (with_comment != WITHOUT_COMMENT_FIELD) ? XmATTACH_NONE : XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
  XtSetArg(args[n], XmNheight, (with_comment != WITHOUT_COMMENT_FIELD) ? PANEL_COMMENT_SPACE : 2); n++;
  XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
  sep4 = XtCreateManagedWidget("sep4", xmSeparatorWidgetClass, parent, args, n);

  /* try to make the comment field the one that grows */
  n = 0;
  if (with_comment == WITHOUT_COMMENT_FIELD)
    {
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep4); n++;
    }
  else
    {
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
    }
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++; /* overridden later -> yellow */
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNborderColor, ss->black); n++;
  XtSetArg(args[n], XmNborderWidth, 2); n++;
  XtSetArg(args[n], XmNmarginWidth, 10); n++;
  XtSetArg(args[n], XmNmarginHeight, 10); n++;
  fdat->error_text = XtCreateWidget("", xmLabelWidgetClass, parent, args, n);
  /* XtUnmanageChild(fdat->error_text); */

  if (with_comment != WITHOUT_COMMENT_FIELD)
    {
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep4); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      comment_label = XtCreateManagedWidget("comment", xmLabelWidgetClass, parent, args, n);

      if (with_auto_comment)
	{
	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
	  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
	  XtSetArg(args[n], XmNtopWidget, sep4); n++;
	  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
	  XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
	  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
	  XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
	  fdat->auto_comment_button = make_togglebutton_widget("auto", parent, args, n);
	  XtAddCallback(fdat->auto_comment_button, XmNvalueChangedCallback, file_data_auto_comment_callback, (XtPointer)fdat);
	  XmToggleButtonSetState(fdat->auto_comment_button, fdat->auto_comment, false);
	}

      n = 0;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      if (with_auto_comment)
	{
	  XtSetArg(args[n], XmNtopWidget, fdat->auto_comment_button); n++;
	}
      else
	{
	  XtSetArg(args[n], XmNtopWidget, comment_label); n++;
	}
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrows, 4); n++;
      /* XtSetArg(args[n], XmNcolumns, 16); n++; */ /* this sets the lower size, so we don't want it too big */
      fdat->comment_text = make_text_widget("comment-text", parent, args, n);
    }
  else fdat->comment_text = NULL;

  return(fdat);
}


static void reflect_file_data_panel_change(file_data *fd, void *data, void (*change_action)(Widget w, XtPointer context, XtPointer info))
{
  if (fd->srate_text)
    XtAddCallback(fd->srate_text, XmNvalueChangedCallback, change_action, (XtPointer)data);
  if (fd->chans_text)
    XtAddCallback(fd->chans_text, XmNvalueChangedCallback, change_action, (XtPointer)data);
  if (fd->samples_text)
    XtAddCallback(fd->samples_text, XmNvalueChangedCallback, change_action, (XtPointer)data);
  if (fd->location_text)
    XtAddCallback(fd->location_text, XmNvalueChangedCallback, change_action, (XtPointer)data);
  if (fd->comment_text)
    XtAddCallback(fd->comment_text, XmNvalueChangedCallback, change_action, (XtPointer)data);
  if (fd->sample_type_list)
    XtAddCallback(fd->sample_type_list, XmNbrowseSelectionCallback, change_action, (XtPointer)data);
  if (fd->header_type_list)
    XtAddCallback(fd->header_type_list, XmNbrowseSelectionCallback, change_action, (XtPointer)data);
}


static void unreflect_file_data_panel_change(file_data *fd, void *data, void (*change_action)(Widget w, XtPointer context, XtPointer info))
{
  if (fd->srate_text)
    XtRemoveCallback(fd->srate_text, XmNvalueChangedCallback, change_action, (XtPointer)data);
  if (fd->chans_text)
    XtRemoveCallback(fd->chans_text, XmNvalueChangedCallback, change_action, (XtPointer)data);
  if (fd->samples_text)
    XtRemoveCallback(fd->samples_text, XmNvalueChangedCallback, change_action, (XtPointer)data);
  if (fd->location_text)
    XtRemoveCallback(fd->location_text, XmNvalueChangedCallback, change_action, (XtPointer)data);
  if (fd->comment_text)
    XtRemoveCallback(fd->comment_text, XmNvalueChangedCallback, change_action, (XtPointer)data);
  if (fd->sample_type_list)
    XtRemoveCallback(fd->sample_type_list, XmNbrowseSelectionCallback, change_action, (XtPointer)data);
  if (fd->header_type_list)
    XtRemoveCallback(fd->header_type_list, XmNbrowseSelectionCallback, change_action, (XtPointer)data);
}


/* -------- save as dialog (file and edit menus) -------- */

typedef struct {
  file_data *panel_data;
  Widget dialog, filename_widget, extractB, mkdirB;
  char *filename; /* output name (?) */
  save_dialog_t type;
  file_pattern_info *fp;
  dialog_play_info *dp;
  void *file_watcher;
  file_popup_info *fpop;
  const char *original_filename;
} save_as_dialog_info;

static save_as_dialog_info *save_sound_as = NULL, *save_selection_as = NULL, *save_region_as = NULL;


static save_as_dialog_info *new_save_as_dialog_info(save_dialog_t type)
{
  save_as_dialog_info *sd;
  sd = (save_as_dialog_info *)calloc(1, sizeof(save_as_dialog_info));
  sd->type = type;
  return(sd);
}


static void make_auto_comment(save_as_dialog_info *sd)
{
  if ((sd == save_sound_as) &&
      (XtIsManaged(sd->dialog)))
    {
      file_data *fd;
      fd = sd->panel_data;

      if (!(fd->auto_comment))
	{
	  /* don't erase typed-in comment, if any */
	  XmTextSetString(fd->comment_text, fd->saved_comment);
	}
      else
	{
	  snd_info *sp;
	  bool edits = false;
	  int i;
	  char *original_sound_comment, *comment, *orig_comment = NULL;

	  sp = any_selected_sound();

	  original_sound_comment = mus_sound_comment(sp->filename);
	  if (original_sound_comment)
	    {
	      if (*original_sound_comment)
		orig_comment = mus_format("\n%s comment:\n%s\n", sp->short_filename, original_sound_comment);
	      free(original_sound_comment);
	      original_sound_comment = NULL;
	    }

	  if (fd->saved_comment) XtFree(fd->saved_comment);
	  fd->saved_comment = XmTextGetString(fd->comment_text);
	  if ((fd->saved_comment) &&
	      (!(*(fd->saved_comment))))
	    {
	      /* this is the norm in Motif */
	      XtFree(fd->saved_comment);
	      fd->saved_comment = NULL;
	    }

	  for (i = 0; i < (int)sp->nchans; i++)
	    if (sp->chans[i]->edit_ctr != 0)
	      {
		edits = true;
		break;
	      }

	  if (!edits)
	    comment = mus_format("%s%ssaved %s from %s (no edits)\n%s", 
				 (fd->saved_comment) ? fd->saved_comment : "",
				 (fd->saved_comment) ? "\n" : "",
				 snd_local_time(),
				 sp->filename,
				 (orig_comment) ? orig_comment : "");
	  else 
	    {
	      int len;
	      char **edit_strs;
	      char *time;
	  
	      time = snd_local_time();
	      len = 2 * mus_strlen(sp->filename) + 
		    mus_strlen(time) + 
		    32 * sp->nchans + 
		    mus_strlen(fd->saved_comment) + 
		    mus_strlen(original_sound_comment);

	      edit_strs = (char **)malloc(sp->nchans * sizeof(char *));
	      for (i = 0; i < (int)sp->nchans; i++)
		{
		  edit_strs[i] = edit_list_to_function(sp->chans[i], 1, sp->chans[i]->edit_ctr);
		  len += mus_strlen(edit_strs[i]);
		}

	      comment = (char *)calloc(len, sizeof(char));
	      snprintf(comment, len, "%s%ssaved %s from %s with edits:\n", 
			   (fd->saved_comment) ? fd->saved_comment : "",
			   (fd->saved_comment) ? "\n" : "",
			   snd_local_time(),
			   sp->filename);
	      
	      for (i = 0; i < (int)sp->nchans; i++)
		{
		  if (sp->nchans > 1)
		    {
		      char buf[64];
		      snprintf(buf, 64, "\n-------- channel %d --------\n", i);
		      strcat(comment, buf);
		    }
		  strcat(comment, edit_strs[i]);
		}

	      if (orig_comment)
		strcat(comment, orig_comment);
	      free(edit_strs);
	    }

	  XmTextSetString(fd->comment_text, comment);
	  if (comment) free(comment);
	  if (orig_comment) free(orig_comment);
	}
    }
}


static void auto_comment_callback(Widget w, XtPointer context, XtPointer info)
{
  save_as_dialog_info *sd = (save_as_dialog_info *)context;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  sd->panel_data->auto_comment = (cb->set);
  make_auto_comment(sd);
}


void reflect_save_as_src(bool val)
{
  if (save_sound_as)
    XmToggleButtonSetState(save_sound_as->panel_data->src_button, val, true);
  if (save_selection_as)
    XmToggleButtonSetState(save_selection_as->panel_data->src_button, val, true);
  if (save_region_as)
    XmToggleButtonSetState(save_region_as->panel_data->src_button, val, true);
}


void reflect_save_as_auto_comment(bool val)
{
  if (save_sound_as)
    XmToggleButtonSetState(save_sound_as->panel_data->auto_comment_button, val, true);
}


void reflect_save_as_sound_selection(const char *sound_name)
{
  if ((save_sound_as) &&
      (XtIsManaged(save_sound_as->dialog)))
    {
      XmString xmstr2;
      char *file_string;
      file_string = (char *)calloc(PRINT_BUFFER_SIZE, sizeof(char));
      if (sound_name)
	snprintf(file_string, PRINT_BUFFER_SIZE, "save %s", sound_name);
      else 
	{
	  snd_info *sp;
	  sp = any_selected_sound();
	  if (sp)
	    snprintf(file_string, PRINT_BUFFER_SIZE, "save %s", sp->short_filename);
	  else snprintf(file_string, PRINT_BUFFER_SIZE, "nothing to save!");
	}
      xmstr2 = XmStringCreateLocalized(file_string);
      XtVaSetValues(save_sound_as->dialog, XmNdialogTitle, xmstr2, NULL);
      XmStringFree(xmstr2);
      free(file_string);
    }
}


void reflect_selection_in_save_as_dialog(bool on)
{
  if ((on) &&
      (save_selection_as) &&
      (save_selection_as->panel_data))
    clear_dialog_error(save_selection_as->panel_data);
}


void reflect_region_in_save_as_dialog(void)
{
  if ((save_region_as) &&
      (save_region_as->dialog) &&
      (XtIsManaged(save_region_as->dialog)) &&
      (region_ok(region_dialog_region())))
    clear_dialog_error(save_region_as->panel_data);
}


static void save_as_filename_modify_callback(Widget w, XtPointer context, XtPointer info);

static void save_as_undoit(save_as_dialog_info *sd)
{
  XmString ok_label;
  ok_label = XmStringCreateLocalized((char *)"Save");
  XtVaSetValues(sd->dialog,
		XmNokLabelString, ok_label, 
		NULL);
  XmStringFree(ok_label);
  clear_dialog_error(sd->panel_data);
  XtRemoveCallback(sd->filename_widget, XmNmodifyVerifyCallback, save_as_filename_modify_callback, (XtPointer)(sd->panel_data));
  sd->file_watcher = unmonitor_file(sd->file_watcher);
}


static void save_as_filename_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)info;
  save_as_undoit((save_as_dialog_info *)context);
  cbs->doit = true;
}


static void clear_error_if_save_as_filename_changes(Widget dialog, save_as_dialog_info *sd)
{
  XtAddCallback(sd->filename_widget, XmNmodifyVerifyCallback, save_as_filename_modify_callback, (XtPointer)sd);
}


static bool srates_differ(int srate, save_as_dialog_info *sd)
{
  switch (sd->type)
    {
    case SOUND_SAVE_AS:
      return(snd_srate(any_selected_sound()) != srate);
      
    case SELECTION_SAVE_AS:
      return(selection_srate() != srate);
      
    case REGION_SAVE_AS:
      return(region_srate(region_dialog_region()) != srate);
    }

  return(false);
}


static double srate_ratio(int srate, save_as_dialog_info *sd)
{
  switch (sd->type)
    {
    case SOUND_SAVE_AS:
      return((double)(snd_srate(any_selected_sound())) / (double)srate);
      
    case SELECTION_SAVE_AS:
      return((double)selection_srate() / (double)srate);
      
    case REGION_SAVE_AS:
      return((double)region_srate(region_dialog_region()) / (double)srate);
    }

  return(1.0);
}


static void save_or_extract(save_as_dialog_info *sd, bool saving)
{
  char *str = NULL, *comment = NULL, *msg = NULL, *fullname = NULL, *tmpfile = NULL;
  snd_info *sp = NULL;
  mus_header_t header_type = MUS_NEXT, output_type;
  mus_sample_t sample_type = DEFAULT_OUTPUT_SAMPLE_TYPE;
  int srate = DEFAULT_OUTPUT_SRATE;
  int chan = 0, extractable_chans = 0;
  bool file_exists = false;
  io_error_t io_err = IO_NO_ERROR;

  clear_dialog_error(sd->panel_data);

  if ((sd->type == SELECTION_SAVE_AS) &&
      (!(selection_is_active())))
    {
      if (saving)
	msg = (char *)"no selection to save";
      else msg = (char *)"can't extract: no selection";
      post_file_dialog_error((const char *)msg, sd->panel_data);
      return;
    }

  if ((sd->type == REGION_SAVE_AS) &&
      (!(region_ok(region_dialog_region()))))
    {
      post_file_dialog_error("no region to save", sd->panel_data);
      return;
    }

  sp = any_selected_sound();
  if ((!sp) && 
      (sd->type != REGION_SAVE_AS))
    {
      if (saving)
	msg = (char *)"nothing to save";
      else msg = (char *)"nothing to extract";
      post_file_dialog_error((const char *)msg, sd->panel_data);
      clear_error_if_filename_changes(sd->dialog, sd->panel_data);
      return;
    }

  /* get output filename */
  str = XmTextGetString(sd->filename_widget);
  if ((!str) || (!*str))
    {
      if (saving)
	msg = (char *)"can't save: no file name given";
      else msg = (char *)"can't extract: no file name given";
      post_file_dialog_error((const char *)msg, sd->panel_data);
      clear_error_if_filename_changes(sd->dialog, sd->panel_data);
      return;
    }

  /* get output file attributes */
  redirect_snd_error_to(post_file_panel_error, (void *)(sd->panel_data));
  {
    mus_long_t location = 28, samples = 0;
    int chans = 1;
    if (saving)
      comment = get_file_dialog_sound_attributes(sd->panel_data, &srate, &chans, &header_type, &sample_type, &location, &samples, 0);
    else comment = get_file_dialog_sound_attributes(sd->panel_data, &srate, &chan, &header_type, &sample_type, &location, &samples, 0);
  }
  output_type = header_type;
  redirect_snd_error_to(NULL, NULL);

  if (sd->panel_data->error_widget != NOT_A_SCANF_WIDGET)
    {
      clear_error_if_panel_changes(sd->dialog, sd->panel_data);
      if (comment) free(comment);
      XtFree(str);
      return;
    }

  switch (sd->type)
    {
    case SOUND_SAVE_AS:
      clear_status_area(sp);
      if (!saving)
	extractable_chans = sp->nchans;
      break;

    case SELECTION_SAVE_AS:
      if (!saving)
	extractable_chans = selection_chans();
      break;

    default:
      break;
    }

  if (!saving)
    {
      if ((chan > extractable_chans) ||
	  (((extractable_chans > 1) && (chan == extractable_chans)) ||
	   (chan < 0)))
	{
	  if (chan > extractable_chans)
	    msg = mus_format("can't extract chan %d (%s has %d chan%s)", 
			     chan, 
			     (sd->type == SOUND_SAVE_AS) ? "sound" : "selection",
			     extractable_chans, 
			     (extractable_chans > 1) ? "s" : "");
	  else msg = mus_format("can't extract chan %d (first chan is numbered 0)", chan);
	  post_file_dialog_error((const char *)msg, sd->panel_data);
	  clear_error_if_chans_changes(sd->dialog, sd->panel_data);
	  free(msg);
	  if (comment) free(comment);
	  XtFree(str);
	  return;
	}
    }

  fullname = mus_expand_filename(str);
  if (run_before_save_as_hook(sp, fullname, sd->type != SOUND_SAVE_AS, srate, sample_type, header_type, comment))
    {
      msg = mus_format("%s cancelled by %s", (saving) ? "save" : "extract", S_before_save_as_hook);
      post_file_dialog_error((const char *)msg, sd->panel_data);
      clear_error_if_filename_changes(sd->dialog, sd->panel_data);      
      free(msg);
      free(fullname);
      if (comment) free(comment);
      XtFree(str);
      return;
    }

  file_exists = mus_file_probe(fullname);
  if ((sd->type == SOUND_SAVE_AS) &&
      (mus_strcmp(fullname, sp->filename)))
    {
      /* save-as here is the same as save */
      if ((sp->user_read_only == FILE_READ_ONLY) || 
	  (sp->file_read_only == FILE_READ_ONLY))
	{
	  msg = mus_format("can't overwrite %s (it is write-protected)", sp->short_filename);
	  post_file_dialog_error((const char *)msg, sd->panel_data);
	  clear_error_if_filename_changes(sd->dialog, sd->panel_data); 
	  free(msg);
	  free(fullname);
	  if (comment) free(comment);
	  XtFree(str);
	  return;
	}
    }
  else
    {
      if (!(sd->file_watcher))
	{
	  /* check for overwrites that are questionable -- DoIt click will return here with sd->file_watcher active */
	  snd_info *parlous_sp = NULL;
	  if ((file_exists) &&
	      ((ask_before_overwrite(ss)) ||
	       ((sd->type == SOUND_SAVE_AS) &&
		(parlous_sp = file_is_open_elsewhere_and_has_unsaved_edits(sp, fullname)))))	   
	    {
	      XmString ok_label;
	      msg = mus_format("%s exists%s. To overwrite it, click 'DoIt'", 
			       str,
			       (parlous_sp) ? ", and has unsaved edits" : "");
	      post_file_dialog_error((const char *)msg, sd->panel_data);
	      clear_error_if_save_as_filename_changes(sd->dialog, sd);
	      ok_label = XmStringCreateLocalized((char *)"DoIt");
	      XtVaSetValues(sd->dialog, 
			    XmNokLabelString, ok_label, 
			    NULL);
	      XmUpdateDisplay(FSB_BOX(sd->dialog, XmDIALOG_OK_BUTTON));
	      XmStringFree(ok_label);
	      free(msg);
	      free(fullname);
	      if (comment) free(comment);
	      XtFree(str);
	      return;
	    }
	}
    }

  /* try to save... if it exists already, first write as temp, then move */
  if (sd->file_watcher)
    save_as_undoit(sd);
  ss->local_errno = 0;

  if (header_is_encoded(header_type))
    {
      output_type = header_type;
      sample_type = MUS_LSHORT;
      header_type = MUS_RIFF;
      tmpfile = snd_tempnam();
    }
  else
    {
      tmpfile = fullname;
    }

  redirect_snd_error_to(redirect_post_file_dialog_error, (void *)(sd->panel_data));
  switch (sd->type)
    {
    case SOUND_SAVE_AS:
      if (saving)
	io_err = save_edits_without_display(sp, tmpfile, header_type, sample_type, srate, comment, AT_CURRENT_EDIT_POSITION);
      else io_err = save_channel_edits(sp->chans[chan], tmpfile, AT_CURRENT_EDIT_POSITION); /* protects if same name */
      break;

    case SELECTION_SAVE_AS:
      {
	char *ofile;
	if (file_exists) /* file won't exist if we're encoding, so this isn't as wasteful as it looks */
	  ofile = snd_tempnam();
	else ofile = mus_strdup(tmpfile);
	io_err = save_selection(ofile, srate, sample_type, header_type, comment, (saving) ? SAVE_ALL_CHANS : chan);
	if (io_err == IO_NO_ERROR)
	  io_err = move_file(ofile, fullname);
	free(ofile);
      }
      break;

    case REGION_SAVE_AS:
      {
	if (region_ok(region_dialog_region()))
	  {
	    char *ofile;
	    if (file_exists)
	      ofile = snd_tempnam();
	    else ofile = mus_strdup(tmpfile);
	    io_err = save_region(region_dialog_region(), ofile, sample_type, header_type, comment);
	    if (io_err == IO_NO_ERROR)
	      io_err = move_file(ofile, fullname);
	    free(ofile);
	  }
      }
      break;
    }
  redirect_snd_error_to(NULL, NULL);

  /* check for possible srate conversion */
  if ((sd->panel_data->src) &&
      (srates_differ(srate, sd)))
    {
      /* if src, and srates differ, do the sampling rate conversion.
       *    this needs to happen before the snd_encode (->OGG etc) below
       *    if we do it before the save-as above, then undo it later, it messes up the user's edit history list
       *    so do it here to tmpfile (tmpfile is fullname unless we're doing a translation to something like OGG)
       */
      src_file(tmpfile, srate_ratio(srate, sd));
    }

  if (io_err == IO_NO_ERROR)
    {
      if (header_is_encoded(output_type))
	{
	  snd_encode(output_type, tmpfile, fullname);
	  snd_remove(tmpfile, REMOVE_FROM_CACHE);
	  free(tmpfile);
	}
      remember_filename(fullname, sd->fpop->file_text_names);

      if (!file_exists)
	force_directory_reread(sd->dialog);
      if (saving)
	{
	  if (sd->type == SOUND_SAVE_AS)
	    status_report(sp, "%s saved as %s", sp->short_filename, str);
	  else status_report(sp, "%s saved as %s", (sd->type == SELECTION_SAVE_AS) ? "selection" : "region", str);
	}
      else
	{
	  if (sd->type == SOUND_SAVE_AS)
	    status_report(sp, "%s chan %d saved as %s", sp->short_filename, chan, str);
	  else status_report(sp, "selection chan %d saved as %s", chan, str);
	}
      run_after_save_as_hook(sp, str, true); /* true => from dialog */
      XtUnmanageChild(sd->dialog);
    }
  else
    {
      msg = mus_format("%s as %s: %s (%s)", (saving) ? "save" : "extract chan", str, io_error_name(io_err), snd_io_strerror());
      post_file_dialog_error((const char *)msg, sd->panel_data);
      clear_error_if_filename_changes(sd->dialog, sd->panel_data);
      free(msg);
    }

  free(fullname);
  XtFree(str);
  if (comment) free(comment);
}


static void save_as_ok_callback(Widget w, XtPointer context, XtPointer info)
{ 
  save_or_extract((save_as_dialog_info *)context, true);
}


static void save_as_extract_callback(Widget w, XtPointer context, XtPointer info) 
{
  save_or_extract((save_as_dialog_info *)context, false);
}


static void save_as_dialog_select_callback(Widget w, XtPointer context, XtPointer info)
{
#if WITH_AUDIO
  dialog_play_info *dp = (dialog_play_info *)context;
  XmString *strs = NULL;
  XtVaGetValues(w, XmNselectedItems, &strs, NULL);
  if (strs)
    {
      char *filename;
      filename = (char *)XmStringUnparse(strs[0], NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
      if ((filename) && (is_sound_file(filename)))
	XtManageChild(dp->play_button);
      else
	{
	  if (XtIsManaged(dp->play_button)) 
	    XtUnmanageChild(dp->play_button);
	}
      if (filename) XtFree(filename);
    }
#endif
}


static void save_as_cancel_callback(Widget w, XtPointer context, XtPointer info)
{ 
  save_as_dialog_info *sd = (save_as_dialog_info *)context;
  XtUnmanageChild(sd->dialog);
} 


static void save_as_help_callback(Widget w, XtPointer context, XtPointer info) 
{
  save_as_dialog_help();
}


static bool directory_exists(char *name)
{
  char temp;
  bool result;
  int i, len, last_slash = -1;
  len = strlen(name);
  for (i = 0; i < len; i++) 
    if (name[i] == '/') 
      last_slash = i;
  if (last_slash <= 0)
    return(true);
  if (last_slash >= len - 1) /* can't be > */
    return(is_directory(name));
  temp = name[last_slash + 1];
  name[last_slash + 1] = '\0';
  result = is_directory(name);
  name[last_slash + 1] = temp;
  return(result);
}


static void save_as_file_exists_check(Widget w, XtPointer context, XtPointer info)
{
  Widget dialog = (Widget)context;
  char *filename = NULL;
  XmString s1;
  filename = XmTextGetString(w);
  if ((filename) && (*filename))
    {
      if ((mus_file_probe(filename)) && 
	  (!is_directory(filename)))
	{
#ifndef _MSC_VER
	  if (access(filename, W_OK) < 0)
	    s1 = XmStringCreateLocalized((char *)"save as (file write-protected?):");
	  else
#endif
	    s1 = XmStringCreateLocalized((char *)"save as (overwriting):");
	}
      else
	{
	  if (!(directory_exists(filename)))
	    s1 = XmStringCreateLocalized((char *)"save as (no such directory?):");
	  else s1 = XmStringCreateLocalized((char *)"save as:");
	}
    }
  else s1 = XmStringCreateLocalized((char *)"save as:");
  XtVaSetValues(dialog, 
		XmNselectionLabelString, s1, 
		NULL);
  if (filename) XtFree(filename);
}


static int snd_mkdir(const char *filename)
{
#ifdef __MINGW32__ 
  return(mkdir(filename));
#else 
  return(mkdir(filename, 0777));
#endif 
}


static void save_as_mkdir_callback(Widget w, XtPointer context, XtPointer info)
{
  save_as_dialog_info *sd = (save_as_dialog_info *)context;
  char *filename = NULL;
  filename = XmTextGetString(FSB_BOX(sd->dialog, XmDIALOG_TEXT));
  if (snd_mkdir(filename) < 0)
    {
      /* could not make the directory */
      char *str;
      str = mus_format("can't make %s: %s", filename, strerror(errno));
      post_file_dialog_error((const char *)str, sd->panel_data);
      clear_error_if_filename_changes(sd->dialog, sd->panel_data); 
      free(str);
    }
  else
    {
      /* set FSB to new dir and force update */
      char *filter;
      filter = mus_format("%s*", filename); /* already has the "/" at the end */
      update_dir_list(sd->dialog, filter);
      free(filter);
      XtSetSensitive(w, false);
    }
  XtFree(filename);
}


static void reflect_text_in_save_button(Widget w, XtPointer context, XtPointer info)
{
  save_as_dialog_info *sd = (save_as_dialog_info *)context;
  /* w here is text widget, not button */
  XtSetSensitive(FSB_BOX(sd->dialog, XmDIALOG_OK_BUTTON), (!(file_is_directory(sd->dialog))));
  if (sd->mkdirB) XtSetSensitive(sd->mkdirB, file_is_nonexistent_directory(sd->dialog));
}


static void reflect_text_in_extract_button(Widget w, XtPointer context, XtPointer info)
{
  save_as_dialog_info *sd = (save_as_dialog_info *)context;
  /* w here is text widget, not button */
  XtSetSensitive(sd->extractB, (!(file_is_directory(sd->dialog))));
}


static void save_as_filter_text_activate_callback(Widget w, XtPointer context, XtPointer info)
{
  save_as_dialog_info *sd = (save_as_dialog_info *)context;
  force_directory_reread_and_let_filename_change(sd->dialog);
}


static void make_save_as_dialog(save_as_dialog_info *sd, char *sound_name, mus_header_t header_type, mus_sample_t sample_type)
{
  char *file_string;

  sd->original_filename = sound_name;
  if (!(sd->dialog))
    {
      Arg args[32];
      int n;
      XmString xmstr1, xmstr2, s1;
      XmString filter_list_label, cancel_label;
      Widget extractB, mainform;

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      s1 = XmStringCreateLocalized((char *)"save as:");
      XtSetArg(args[n], XmNselectionLabelString, s1); n++;

      xmstr1 = XmStringCreateLocalized((char *)"Save");
      XtSetArg(args[n], XmNokLabelString, xmstr1); n++;

      file_string = (char *)calloc(PRINT_BUFFER_SIZE, sizeof(char));
      snprintf(file_string, PRINT_BUFFER_SIZE, "save %s", sound_name);

      xmstr2 = XmStringCreateLocalized(file_string);
      XtSetArg(args[n], XmNdialogTitle, xmstr2); n++;

      filter_list_label = XmStringCreateLocalized((char *)"files listed:");
      XtSetArg(args[n], XmNfilterLabelString, filter_list_label); n++;

      cancel_label = XmStringCreateLocalized((char *)I_GO_AWAY);
      XtSetArg(args[n], XmNcancelLabelString, cancel_label); n++;

      sd->fp = (file_pattern_info *)calloc(1, sizeof(file_pattern_info));
      sd->fp->in_just_sounds_update = false;
      if (just_sounds(ss))
	sd->fp->filter_choice = JUST_SOUNDS_FILTER;
      else sd->fp->filter_choice = NO_FILE_FILTER;

      sd->dp = (dialog_play_info *)calloc(1, sizeof(dialog_play_info));
      sd->fpop = (file_popup_info *)calloc(1, sizeof(file_popup_info));
      sd->fpop->fp = sd->fp;

      XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
      XtSetArg(args[n], XmNnoResize, false); n++;
      XtSetArg(args[n], XmNautoUnmanage, false); n++;
      XtSetArg(args[n], XmNchildPlacement, XmPLACE_ABOVE_SELECTION); n++;
      XtSetArg(args[n], XmNallowOverlap, false); n++;
      XtSetArg(args[n], XmNheight, 600); n++;
      XtSetArg(args[n], XmNuserData, (XtPointer)sd->fp); n++;
      XtSetArg(args[n], XmNfileFilterStyle, XmFILTER_HIDDEN_FILES); n++;
      XtSetArg(args[n], XmNfileSearchProc, snd_directory_reader); n++;        /* over-ride Motif's directory reader altogether */      

      sd->dialog = XmCreateFileSelectionDialog(main_shell(ss), (char *)"save-as", args, n);
      sd->fp->dialog = sd->dialog;
      sd->dp->dialog = sd->dialog;
      sd->fpop->dialog = sd->dialog;

      free(file_string);

      XtUnmanageChild(FSB_BOX(sd->dialog, XmDIALOG_DIR_LIST_LABEL));
      XtUnmanageChild(FSB_BOX(sd->dialog, XmDIALOG_LIST_LABEL));
      XtUnmanageChild(FSB_BOX(sd->dialog, XmDIALOG_APPLY_BUTTON));

      XtVaSetValues(FSB_BOX(sd->dialog, XmDIALOG_FILTER_LABEL), XmNbackground, ss->basic_color, NULL);
      XtVaSetValues(FSB_BOX(sd->dialog, XmDIALOG_SELECTION_LABEL), XmNbackground, ss->basic_color, NULL);

      XmStringFree(s1);
      XmStringFree(xmstr1);
      XmStringFree(xmstr2);
      XmStringFree(filter_list_label);
      XmStringFree(cancel_label);

      sd->filename_widget = FSB_BOX(sd->dialog, XmDIALOG_TEXT);
      XtAddCallback(sd->dialog, XmNhelpCallback, save_as_help_callback, (XtPointer)sd);
      XtAddCallback(sd->dialog, XmNcancelCallback, save_as_cancel_callback, (XtPointer)sd);
      XtAddCallback(sd->dialog, XmNokCallback, save_as_ok_callback, (XtPointer)sd);

      mainform = XtVaCreateManagedWidget("filebuttons-mainform", xmFormWidgetClass, sd->dialog, NULL);
      add_play_and_just_sounds_buttons(sd->dialog, mainform, sd->fp, sd->dp);

      n = 0;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sd->fp->just_sounds_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      sd->panel_data = make_file_data_panel(mainform, "data-form", args, n, 
					    (sd->type == REGION_SAVE_AS) ? WITHOUT_CHANNELS_FIELD : WITH_EXTRACT_CHANNELS_FIELD, 
					    header_type, sample_type, 
					    WITHOUT_DATA_LOCATION_FIELD, 
					    WITHOUT_SAMPLES_FIELD,
					    WITH_HEADER_TYPE_FIELD, 
					    WITH_COMMENT_FIELD,
					    WITH_WRITABLE_HEADERS,
					    WITH_SRATE,
					    sd->type == SOUND_SAVE_AS); /* auto comment */

      sd->panel_data->dialog = sd->dialog;

      color_file_selection_box(sd->dialog);

      XtVaSetValues(sd->panel_data->sample_type_list, XmNbackground, ss->white, XmNforeground, ss->black, NULL);
      XtVaSetValues(sd->panel_data->header_type_list, XmNbackground, ss->white, XmNforeground, ss->black, NULL);
      XtVaSetValues(sd->fp->just_sounds_button, XmNselectColor, ss->selection_color, NULL);
#if WITH_AUDIO
      XtVaSetValues(sd->dp->play_button, XmNselectColor, ss->selection_color, NULL);
#endif

      XtVaSetValues(sd->panel_data->src_button, XmNselectColor, ss->selection_color, NULL);
      if (sd->type == SOUND_SAVE_AS)
	{
	  XtVaSetValues(sd->panel_data->auto_comment_button, XmNselectColor, ss->selection_color, NULL);
	  XtAddCallback(sd->panel_data->auto_comment_button, XmNvalueChangedCallback, auto_comment_callback, (XtPointer)sd);
	}
      XtAddCallback(FSB_BOX(sd->dialog, XmDIALOG_LIST),
		    XmNbrowseSelectionCallback, save_as_dialog_select_callback, (XtPointer)(sd->dp));
      XtAddCallback(sd->filename_widget, XmNvalueChangedCallback, save_as_file_exists_check, (XtPointer)(sd->dialog));
      XtAddCallback(FSB_BOX(sd->dialog, XmDIALOG_FILTER_TEXT), XmNactivateCallback, save_as_filter_text_activate_callback, (void *)sd);

      {
	Widget wtmp;
	wtmp = FSB_BOX(sd->dialog, XmDIALOG_DIR_LIST);
	if (wtmp) XtAddCallback(wtmp, XmNbrowseSelectionCallback, file_change_directory_callback, (XtPointer)(sd->fp));
      }

      add_file_popups(sd->fpop);

      /* this must come after the file data panel so that Motif puts it in the button box, not the main work area */
      if (sd->type != REGION_SAVE_AS)
	{
	  /* add "Extract" button */
	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
	  XtSetArg(args[n], XmNarmColor,   ss->selection_color); n++;
	  extractB = XtCreateManagedWidget("Extract", xmPushButtonGadgetClass, sd->dialog, args, n);
	  XtAddCallback(extractB, XmNactivateCallback, save_as_extract_callback, (XtPointer)sd);
	  sd->extractB = extractB;

	  XtSetSensitive(extractB, (!(file_is_directory(sd->dialog))));
	  XtAddCallback(FSB_BOX(sd->dialog, XmDIALOG_TEXT), XmNvalueChangedCallback, reflect_text_in_extract_button, (void *)sd);
	}
	 
      /* add "Mkdir" button */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor,   ss->selection_color); n++;
      sd->mkdirB = XtCreateManagedWidget("Mkdir", xmPushButtonGadgetClass, sd->dialog, args, n);
      XtAddCallback(sd->mkdirB, XmNactivateCallback, save_as_mkdir_callback, (XtPointer)sd);
      XtSetSensitive(sd->mkdirB, false);

      XtSetSensitive(FSB_BOX(sd->dialog, XmDIALOG_OK_BUTTON), (!(file_is_directory(sd->dialog))));
      XtAddCallback(FSB_BOX(sd->dialog, XmDIALOG_TEXT), XmNvalueChangedCallback, reflect_text_in_save_button, (void *)sd);

      XtManageChild(sd->dialog);
      switch (sd->type)
	{
	case SOUND_SAVE_AS:
	  set_dialog_widget(SOUND_SAVE_AS_DIALOG, sd->dialog);
	  break;

	case SELECTION_SAVE_AS:
	  set_dialog_widget(SELECTION_SAVE_AS_DIALOG, sd->dialog);
	  break;

	case REGION_SAVE_AS:
	  set_dialog_widget(REGION_SAVE_AS_DIALOG, sd->dialog);
	  break;

	default:
	  snd_error("internal screw up");
	  break;
	}
    }
  else
    {
      XmString xmstr2;
      file_string = (char *)calloc(PRINT_BUFFER_SIZE, sizeof(char));
      snprintf(file_string, PRINT_BUFFER_SIZE, "save %s", sound_name);
      xmstr2 = XmStringCreateLocalized(file_string);
      XtVaSetValues(sd->dialog, 
		    XmNdialogTitle, xmstr2, 
		    NULL);
      XmStringFree(xmstr2);
      free(file_string);
    }
}


static save_as_dialog_info *make_sound_save_as_dialog_1(bool managed, int chan)
{
  /* should the save-as dialog, at least in the file case, reflect the current file attributes/comment?
   *          or should we have a save-as-hook that can set up the dialog fields? 
   */

  snd_info *sp = NULL;
  char *com = NULL;
  file_info *hdr = NULL;
  save_as_dialog_info *sd;

  if (!save_sound_as)
    save_sound_as = new_save_as_dialog_info(SOUND_SAVE_AS);
  sd = save_sound_as;

  sp = any_selected_sound();
  if (sp) hdr = sp->hdr;

  make_save_as_dialog(sd,
		      (char *)((sp) ? sp->short_filename : ""),
		      default_output_header_type(ss),
		      default_output_sample_type(ss));

  set_file_dialog_sound_attributes(sd->panel_data,
				   sd->panel_data->current_header_type,
				   sd->panel_data->current_sample_type,
				   (hdr) ? hdr->srate : selection_srate(), 
				   IGNORE_CHANS, IGNORE_DATA_LOCATION, IGNORE_SAMPLES,
				   com = output_comment(hdr));
  if (com) free(com);

  if (chan >= 0)
    {
      char *chan_str;  
      chan_str = (char *)calloc(16, sizeof(char));
      snprintf(chan_str, 16, "%d", chan);
      XmTextFieldSetString(sd->panel_data->chans_text, chan_str);
      free(chan_str);
    }

  if (sd->fp->reread_directory) 
    {
      force_directory_reread(sd->dialog);
      sd->fp->reread_directory = false;
    }

  if ((managed) && (!XtIsManaged(sd->dialog))) 
    XtManageChild(sd->dialog);

  make_auto_comment(sd);
  return(sd);
}


widget_t make_sound_save_as_dialog(bool managed)
{
  save_as_dialog_info *sd;
  sd = make_sound_save_as_dialog_1(managed, -1);
  return(sd->dialog);
}


void make_channel_extract_dialog(int chan)
{
  make_sound_save_as_dialog_1(true, chan);
}


widget_t make_selection_save_as_dialog(bool managed)
{
  save_as_dialog_info *sd;

  if (!save_selection_as)
    save_selection_as = new_save_as_dialog_info(SELECTION_SAVE_AS);
  sd = save_selection_as;

  make_save_as_dialog(sd,
		      (char *)"current selection",
		      default_output_header_type(ss),
		      default_output_sample_type(ss));
  set_file_dialog_sound_attributes(sd->panel_data,
				   sd->panel_data->current_header_type,
				   sd->panel_data->current_sample_type,
				   selection_srate(), 
				   IGNORE_CHANS, IGNORE_DATA_LOCATION, IGNORE_SAMPLES, 
				   NULL);
  if (sd->fp->reread_directory) 
    {
      force_directory_reread(sd->dialog);
      sd->fp->reread_directory = false;
    }
  if ((managed) && (!XtIsManaged(sd->dialog))) 
    XtManageChild(sd->dialog);
  return(sd->dialog);
}


widget_t make_region_save_as_dialog(bool managed)
{
  save_as_dialog_info *sd;
  char *comment = NULL;

  if (!save_region_as)
    save_region_as = new_save_as_dialog_info(REGION_SAVE_AS);
  sd = save_region_as;

  make_save_as_dialog(sd,
		      (char *)"selected region",
		      default_output_header_type(ss),
		      default_output_sample_type(ss));
  comment = region_description(region_dialog_region());
  set_file_dialog_sound_attributes(sd->panel_data,
				   sd->panel_data->current_header_type,
				   sd->panel_data->current_sample_type,
				   region_srate(region_dialog_region()), 
				   IGNORE_CHANS, IGNORE_DATA_LOCATION, IGNORE_SAMPLES, 
				   comment);
  if (sd->fp->reread_directory) 
    {
      force_directory_reread(sd->dialog);
      sd->fp->reread_directory = false;
    }
  if ((managed) && (!XtIsManaged(sd->dialog))) 
    XtManageChild(sd->dialog);
  if (comment) free(comment);
  return(sd->dialog);
}



/* -------- save/restore for all these dialogs -------- */

void save_file_dialog_state(FILE *fd)
{
  if ((odat) && (XtIsManaged(odat->dialog)))
    {
      /* odat->file_dialog_read_only -> "view-sound" dialog -- this distinction currently ignored */
#if HAVE_SCHEME
      fprintf(fd, "(%s #t)\n", S_open_file_dialog);
#endif
#if HAVE_RUBY
      fprintf(fd, "%s(true)\n", to_proc_name(S_open_file_dialog));
#endif
#if HAVE_FORTH
      fprintf(fd, "#t %s drop\n", S_open_file_dialog);
#endif
    }
  if ((mdat) && (XtIsManaged(mdat->dialog)))
    {
#if HAVE_SCHEME
      fprintf(fd, "(%s #t)\n", S_mix_file_dialog);
#endif
#if HAVE_RUBY
      fprintf(fd, "%s(true)\n", to_proc_name(S_mix_file_dialog));
#endif
#if HAVE_FORTH
      fprintf(fd, "#t %s drop\n", S_mix_file_dialog);
#endif
    }
  if ((idat) && (XtIsManaged(idat->dialog)))
    {
#if HAVE_SCHEME
      fprintf(fd, "(%s #t)\n", S_insert_file_dialog);
#endif
#if HAVE_RUBY
      fprintf(fd, "%s(true)\n", to_proc_name(S_insert_file_dialog));
#endif
#if HAVE_FORTH
      fprintf(fd, "#t %s drop\n", S_insert_file_dialog);
#endif
    }
  if ((save_sound_as) && (XtIsManaged(save_sound_as->dialog)))
    {
#if HAVE_SCHEME
      fprintf(fd, "(%s #t)\n", S_save_sound_dialog);
#endif
#if HAVE_RUBY
      fprintf(fd, "%s(true)\n", to_proc_name(S_save_sound_dialog));
#endif
#if HAVE_FORTH
      fprintf(fd, "#t %s drop\n", S_save_sound_dialog);
#endif
    }
  if ((save_selection_as) && (XtIsManaged(save_selection_as->dialog)))
    {
#if HAVE_SCHEME
      fprintf(fd, "(%s #t)\n", S_save_selection_dialog);
#endif
#if HAVE_RUBY
      fprintf(fd, "%s(true)\n", to_proc_name(S_save_selection_dialog));
#endif
#if HAVE_FORTH
      fprintf(fd, "#t %s drop\n", S_save_selection_dialog);
#endif
    }
  if ((save_region_as) && (XtIsManaged(save_region_as->dialog)))
    {
#if HAVE_SCHEME
      fprintf(fd, "(%s #t)\n", S_save_region_dialog);
#endif
#if HAVE_RUBY
      fprintf(fd, "%s(true)\n", to_proc_name(S_save_region_dialog));
#endif
#if HAVE_FORTH
      fprintf(fd, "#t %s drop\n", S_save_region_dialog);
#endif
    }
}



/* -------------------------------- New File -------------------------------- */

static Widget new_file_dialog = NULL;
static file_data *ndat = NULL;
static mus_long_t initial_samples = 1;
static Widget new_file_text = NULL;
static char *new_file_filename = NULL;
static void *new_file_watcher = NULL;


static void new_filename_modify_callback(Widget w, XtPointer context, XtPointer info);

static void new_file_undoit(void)
{
  XmString ok_label;
  ok_label = XmStringCreateLocalized((char *)"Ok");
  XtVaSetValues(new_file_dialog, 
		XmNokLabelString, ok_label, 
		NULL);
  XmStringFree(ok_label);
  clear_dialog_error(ndat);
  XtRemoveCallback(new_file_text, XmNmodifyVerifyCallback, new_filename_modify_callback, NULL);
  new_file_watcher = unmonitor_file(new_file_watcher);
}


static void new_filename_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)info;
  new_file_undoit();
  cbs->doit = true;
}


static void clear_error_if_new_filename_changes(Widget dialog)
{
  XtAddCallback(new_file_text, XmNmodifyVerifyCallback, new_filename_modify_callback, NULL);
}


static void new_file_ok_callback(Widget w, XtPointer context, XtPointer info) 
{
  mus_long_t loc;
  char *newer_name = NULL, *msg;
  mus_header_t header_type;
  mus_sample_t sample_type;
  int srate, chans;
  newer_name = XmTextGetString(new_file_text);
  if ((!newer_name) || (!(*newer_name)))
    {
      msg = (char *)"new sound needs a file name ('New file:' field is empty)";
      post_file_dialog_error((const char *)msg, ndat);
      clear_error_if_new_filename_changes(new_file_dialog);
    }
  else
    {
      char *comment;
      redirect_snd_error_to(post_file_panel_error, (void *)ndat);
      comment = get_file_dialog_sound_attributes(ndat, &srate, &chans, &header_type, &sample_type, &loc, &initial_samples, 1);
      redirect_snd_error_to(NULL, NULL);
      if (ndat->error_widget != NOT_A_SCANF_WIDGET)
	{
	  clear_error_if_panel_changes(new_file_dialog, ndat);
	}
      else
	{
	  /* handle the overwrite hook directly */
	  if (new_file_filename) free(new_file_filename);
	  new_file_filename = mus_expand_filename(newer_name); 
	  if ((!new_file_watcher) &&
	      (ask_before_overwrite(ss)) && 
	      (mus_file_probe(new_file_filename)))
	    {
	      XmString ok_label;
	      msg = mus_format("%s exists. If you want to overwrite it, click 'DoIt'", newer_name);
	      post_file_dialog_error((const char *)msg, ndat);
	      clear_error_if_new_filename_changes(new_file_dialog);
	      ok_label = XmStringCreateLocalized((char *)"DoIt");
	      XtVaSetValues(new_file_dialog, 
			    XmNokLabelString, ok_label, 
			    NULL);
	      XmUpdateDisplay(MSG_BOX(new_file_dialog, XmDIALOG_OK_BUTTON));
	      XmStringFree(ok_label);
	      free(msg);
	    }
	  else
	    {
	      snd_info *sp;
	      if (new_file_watcher)
		new_file_undoit();

	      ss->local_errno = 0;
	      redirect_snd_error_to(redirect_post_file_dialog_error, (void *)ndat);
	      sp = snd_new_file(new_file_filename, chans, srate, sample_type, header_type, comment, initial_samples);
	      redirect_snd_error_to(NULL, NULL);
	      if (!sp)
		{
		  clear_error_if_new_filename_changes(new_file_dialog);
		}
	      else
		{
		  XtUnmanageChild(new_file_dialog);
		}
	    }
	}
      XtFree(newer_name);
      if (comment) free(comment);
    }
}


static char *new_file_dialog_filename(mus_header_t header_type)
{
  static int new_file_dialog_file_ctr = 1;
  char *filename = NULL;
  const char *extension = NULL;
  filename = (char *)calloc(64, sizeof(char));
  switch (header_type)
    {
    case MUS_AIFC: extension = "aiff"; break;
    case MUS_AIFF: extension = "aiff"; break;
    case MUS_RIFF: extension = "wav";  break;
    case MUS_RF64: extension = "wav";  break;
    case MUS_CAFF: extension = "caf";  break;
    default:       extension = "snd";  break;
    }
  snprintf(filename, 64, "new-%d.%s", new_file_dialog_file_ctr++, extension);
  return(filename);
}


static void load_new_file_defaults(char *newname)
{
  char *new_comment = NULL;
  mus_header_t header_type;
  mus_sample_t sample_type;
  int chans, srate;

  header_type = default_output_header_type(ss);
  chans =       default_output_chans(ss);
  sample_type = default_output_sample_type(ss);
  srate =       default_output_srate(ss);
  new_comment = output_comment(NULL);

  if ((!newname) || (!(*newname))) 
    newname = new_file_dialog_filename(header_type);
  mus_sound_forget(newname);

  set_file_dialog_sound_attributes(ndat, header_type, sample_type, srate, chans, IGNORE_DATA_LOCATION, initial_samples, new_comment);
  if (new_comment) free(new_comment);
}


static void new_file_reset_callback(Widget w, XtPointer context, XtPointer info) 
{
  char *current_name;
  current_name = XmTextGetString(new_file_text);
  load_new_file_defaults(current_name);
  if (current_name) XtFree(current_name);
  if (new_file_watcher)
    new_file_undoit();
}


static void new_file_cancel_callback(Widget w, XtPointer context, XtPointer info) 
{
  XtUnmanageChild(w);
}


static void new_file_help_callback(Widget w, XtPointer context, XtPointer info) 
{
  new_file_dialog_help();
}


widget_t make_new_file_dialog(bool managed)
{
  if (!new_file_dialog)
    {
      Arg args[20];
      int n;
      XmString xok, xcancel, xhelp;
      Widget name_label, form;
      XmString titlestr;
      Widget sep, reset_button;

      titlestr = XmStringCreateLocalized((char *)"New file");
      xhelp = XmStringCreateLocalized((char *)I_HELP);
      xcancel = XmStringCreateLocalized((char *)I_GO_AWAY);
      xok = XmStringCreateLocalized((char *)"Ok");

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
      XtSetArg(args[n], XmNcancelLabelString, xcancel); n++;
      XtSetArg(args[n], XmNhelpLabelString, xhelp); n++;
      XtSetArg(args[n], XmNokLabelString, xok); n++;
      XtSetArg(args[n], XmNdialogTitle, titlestr); n++;
      XtSetArg(args[n], XmNnoResize, false); n++;
      XtSetArg(args[n], XmNautoUnmanage, false); n++;
      new_file_dialog = XmCreateTemplateDialog(main_shell(ss), (char *)"new", args, n);

      XmStringFree(titlestr);
      XmStringFree(xok);
      XmStringFree(xcancel);
      XmStringFree(xhelp);

      XtAddCallback(new_file_dialog, XmNhelpCallback,   new_file_help_callback,   NULL);
      XtAddCallback(new_file_dialog, XmNcancelCallback, new_file_cancel_callback, NULL);
      XtAddCallback(new_file_dialog, XmNokCallback,     new_file_ok_callback,     NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      reset_button = XtCreateManagedWidget("Reset", xmPushButtonGadgetClass, new_file_dialog, args, n);
      XtAddCallback(reset_button, XmNactivateCallback, new_file_reset_callback, NULL);

      n = 0;
      form = XtCreateManagedWidget("newfile", xmFormWidgetClass, new_file_dialog, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNforeground, ss->black); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      name_label = XtCreateManagedWidget("New file:", xmLabelWidgetClass, form, args, n);

      n = 0;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, name_label); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      new_file_text = make_textfield_widget("newtext", form, args, n, ACTIVATABLE, add_completer_func(filename_completer, NULL));

      n = 0;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, new_file_text); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNheight, 8); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      sep = XtCreateManagedWidget("sep", xmSeparatorWidgetClass, form, args, n);

      n = 0;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      ndat = make_file_data_panel(form, "data-form", args, n, 
				  WITH_CHANNELS_FIELD, 
				  default_output_header_type(ss), 
				  default_output_sample_type(ss), 
				  WITHOUT_DATA_LOCATION_FIELD, 
				  WITH_SAMPLES_FIELD,
				  WITH_HEADER_TYPE_FIELD, 
				  WITH_COMMENT_FIELD,
				  WITH_BUILTIN_HEADERS,
				  WITHOUT_SRATE, 
				  WITHOUT_AUTO_COMMENT);
      ndat->dialog = new_file_dialog;
      XtManageChild(ndat->error_text);
      XtManageChild(new_file_dialog);

      map_over_children(new_file_dialog, set_main_color_of_widget);
      XtVaSetValues(ndat->sample_type_list, XmNbackground, ss->white, XmNforeground, ss->black, NULL);
      XtVaSetValues(ndat->header_type_list, XmNbackground, ss->white, XmNforeground, ss->black, NULL);

      XtVaSetValues(MSG_BOX(new_file_dialog, XmDIALOG_OK_BUTTON),     XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(new_file_dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(new_file_dialog, XmDIALOG_HELP_BUTTON),   XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(new_file_dialog, XmDIALOG_OK_BUTTON),     XmNbackground, ss->highlight_color,   NULL);
      XtVaSetValues(MSG_BOX(new_file_dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color,   NULL);
      XtVaSetValues(MSG_BOX(new_file_dialog, XmDIALOG_HELP_BUTTON),   XmNbackground, ss->highlight_color,   NULL);

      set_dialog_widget(NEW_FILE_DIALOG, new_file_dialog);
      XtUnmanageChild(ndat->error_text); 

      load_new_file_defaults(NULL);
    }
  else
    {
      char *new_name;
      new_name = XmTextGetString(new_file_text);

      if (new_file_watcher)
	{
	  /* if overwrite question pends, but file has been deleted in the meantime, go back to normal state */
	  if ((!new_name) || (!(*new_name)) ||
	      (!(mus_file_probe(new_name))))
	    new_file_undoit();
	}

      if (strncmp(new_name, "new-", 4) == 0)
	{
	  /* if file is open with currently posted new-file dialog name, and it's our name (new-%d), then tick the counter */
	  snd_info *sp;
	  sp = find_sound(new_name, 0);
	  if (sp)
	    {
	      char *filename;
	      filename = new_file_dialog_filename(default_output_header_type(ss));
	      XmTextSetString(new_file_text, filename);  
	      mus_sound_forget(filename);
	      free(filename);
	    }
	}
      if (new_name) XtFree(new_name);
    }
  if ((managed) && 
      (!(XtIsManaged(new_file_dialog))))
    XtManageChild(new_file_dialog);
  return(new_file_dialog);
}



/* ---------------- Edit Header ---------------- */

typedef struct edhead_info {
  Widget dialog;
  file_data *edat;
  snd_info *sp;
  bool panel_changed;
  void *file_ro_watcher;
  int sp_ro_watcher_loc;
} edhead_info;

static int edhead_info_size = 0;
static edhead_info **edhead_infos = NULL;


static edhead_info *new_edhead_dialog(void)
{
  int loc = -1;
  if (edhead_info_size == 0)
    {
      loc = 0;
      edhead_info_size = 4;
      edhead_infos = (edhead_info **)calloc(edhead_info_size, sizeof(edhead_info *));
    }
  else
    {
      int i;
      for (i = 0; i < edhead_info_size; i++)
	if ((!edhead_infos[i]) ||
	    (!(XtIsManaged(edhead_infos[i]->dialog))))
	  {
	    loc = i;
	    break;
	  }
      if (loc == -1)
	{
	  loc = edhead_info_size;
	  edhead_info_size += 4;
	  edhead_infos = (edhead_info **)realloc(edhead_infos, edhead_info_size * sizeof(edhead_info *));
	  for (i = loc; i < edhead_info_size; i++) edhead_infos[i] = NULL;
	}
    }
  if (!edhead_infos[loc])
    {
      edhead_infos[loc] = (edhead_info *)calloc(1, sizeof(edhead_info));
      edhead_infos[loc]->dialog = NULL;
      edhead_infos[loc]->panel_changed = false;
    }
  edhead_infos[loc]->sp = NULL;
  edhead_infos[loc]->file_ro_watcher = NULL;
  return(edhead_infos[loc]);
}


static XmString make_header_dialog_title(edhead_info *ep, snd_info *sp)
{
  /* dialog may not yet exist */
  char *str;
  XmString xstr;
  str = (char *)calloc(PRINT_BUFFER_SIZE, sizeof(char));
  if ((sp->user_read_only == FILE_READ_ONLY) || 
      (sp->file_read_only == FILE_READ_ONLY))
    {
      if (sp->hdr->type == MUS_RAW)
	snprintf(str, PRINT_BUFFER_SIZE, "Add header to (write-protected) %s", sp->short_filename);
      else snprintf(str, PRINT_BUFFER_SIZE, "Edit header of (write-protected) %s", sp->short_filename);
      if (ep->dialog)
	set_sensitive(MSG_BOX(ep->dialog, XmDIALOG_OK_BUTTON), (sp->hdr->type == MUS_RAW));
    }
  else 
    {
      if (sp->hdr->type == MUS_RAW)
	snprintf(str, PRINT_BUFFER_SIZE, "Add header to %s", sp->short_filename);
      else snprintf(str, PRINT_BUFFER_SIZE, "Edit header of %s", sp->short_filename);
      if (ep->dialog)
	set_sensitive(MSG_BOX(ep->dialog, XmDIALOG_OK_BUTTON), ep->panel_changed);
    }
  xstr = XmStringCreateLocalized(str);
  free(str);
  return(xstr);
}


static void edit_header_help_callback(Widget w, XtPointer context, XtPointer info) 
{
  edit_header_dialog_help();
}


static void edit_header_set_ok_sensitive(Widget w, XtPointer context, XtPointer info)
{
  edhead_info *ep = (edhead_info *)context;
  if (ep->sp->file_read_only == FILE_READ_WRITE)
    set_sensitive(MSG_BOX(ep->dialog, XmDIALOG_OK_BUTTON), true);
  ep->panel_changed = true;
}


static void eh_cancel(edhead_info *ep)
{
  unreflect_file_data_panel_change(ep->edat, (void *)ep, edit_header_set_ok_sensitive);
  ep->panel_changed = false;
  if ((ep->file_ro_watcher) &&
      (ep->sp) &&
      (ep->sp->active) &&
      (ep->sp->filename))
    ep->file_ro_watcher = unmonitor_file(ep->file_ro_watcher);
}


static void edit_header_cancel_callback(Widget w, XtPointer context, XtPointer info) 
{
  edhead_info *ep = (edhead_info *)context;
  XtUnmanageChild(ep->dialog);
  eh_cancel(ep);
}


static void edit_header_wm_delete_callback(Widget w, XtPointer context, XtPointer info) 
{
  eh_cancel((edhead_info *)context);
}


static void edit_header_ok_callback(Widget w, XtPointer context, XtPointer info) 
{
  edhead_info *ep = (edhead_info *)context;
  if ((ep->sp) && (ep->sp->active))
    {
      if (XmGetFocusWidget(ep->dialog) == MSG_BOX(ep->dialog, XmDIALOG_OK_BUTTON))
	{
	  bool ok;
	  redirect_snd_error_to(redirect_post_file_dialog_error, (void *)(ep->edat));
	  ok = edit_header_callback(ep->sp, ep->edat, redirect_post_file_dialog_error, post_file_panel_error);
	  /* edit_header_callback, if all goes well, writes the header, recopies the data,
	   *   then calls snd_update which closes the sound and reopens it, to force the
	   *   new_header to take effect.  The read-only watcher is disabled during that
	   *   process to keep it from getting a SOUND_IS_CLOSING message from close.
	   */
	  redirect_snd_error_to(NULL, NULL);
	  if (ep->edat->error_widget != NOT_A_SCANF_WIDGET)
	    {
	      clear_error_if_panel_changes(ep->dialog, ep->edat);
	      return;
	    }
	  else
	    {
	      if (!ok)
		{
		  set_sensitive(MSG_BOX(ep->dialog, XmDIALOG_OK_BUTTON), false);
		  return;
		}
	    }
	  ep->file_ro_watcher = unmonitor_file(ep->file_ro_watcher);
	  XtUnmanageChild(ep->dialog);
	  unreflect_file_data_panel_change(ep->edat, (void *)ep, edit_header_set_ok_sensitive);
	}
    }
}


Widget edit_header(snd_info *sp)
{
  file_info *hdr;
  XmString xstr4;
  Widget main_w;
  edhead_info *ep = NULL;

  if (!sp) return(NULL);

  /* look for a dialog already editing this sound, raise if found, else make a new one */
  if (edhead_info_size > 0)
    {
      int i;
      for (i = 0; i < edhead_info_size; i++)
	if ((edhead_infos[i]) &&
	    ((edhead_infos[i]->sp == sp) ||
	     ((edhead_infos[i]->sp) && /* maybe same sound open twice -- only one edit header dialog for it */
	      (edhead_infos[i]->sp->inuse == SOUND_NORMAL) &&
	      (mus_strcmp(sp->filename, edhead_infos[i]->sp->filename)))))
	  {
	    ep = edhead_infos[i];
	    break;
	  }
    }
  if (!ep)
    ep = new_edhead_dialog();

  ep->sp = sp;
  hdr = sp->hdr;
  ep->panel_changed = (hdr->type == MUS_RAW);
  xstr4 = make_header_dialog_title(ep, sp);

  if (!ep->dialog)
    {
      int n;
      Arg args[20];
      XmString xstr1, xstr2, xstr3, titlestr;

      n = 0;
      xstr1 = XmStringCreateLocalized((char *)I_GO_AWAY); /* needed by template dialog */
      xstr2 = XmStringCreateLocalized((char *)I_HELP);
      xstr3 = XmStringCreateLocalized((char *)"Save");
      titlestr = XmStringCreateLocalized((char *)"Edit Header");

      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNcancelLabelString, xstr1); n++;
      XtSetArg(args[n], XmNhelpLabelString, xstr2); n++;
      XtSetArg(args[n], XmNokLabelString, xstr3); n++;
      XtSetArg(args[n], XmNmessageString, xstr4); n++;
      XtSetArg(args[n], XmNdialogTitle, titlestr); n++;
      XtSetArg(args[n], XmNautoUnmanage, false); n++;
      XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
      XtSetArg(args[n], XmNnoResize, false); n++;
      XtSetArg(args[n], XmNtransient, false); n++;
      ep->dialog = XmCreateTemplateDialog(main_shell(ss), (char *)"Edit Header", args, n);

      XtAddCallback(ep->dialog, XmNcancelCallback, edit_header_cancel_callback, (XtPointer)ep);
      XtAddCallback(ep->dialog, XmNhelpCallback,   edit_header_help_callback,   (XtPointer)ep);
      XtAddCallback(ep->dialog, XmNokCallback,     edit_header_ok_callback,     (XtPointer)ep);

      XmStringFree(xstr1);
      XmStringFree(xstr2);
      XmStringFree(xstr3);
      XmStringFree(titlestr);

      n = 0;
      main_w = XtCreateManagedWidget("eh-main", xmFormWidgetClass, ep->dialog, args, n);

      n = 0;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      ep->edat = make_file_data_panel(main_w, "Edit Header", args, n, 
				      WITH_CHANNELS_FIELD, 
				      hdr->type, 
				      hdr->sample_type, 
				      WITH_DATA_LOCATION_FIELD, 
				      WITH_SAMPLES_FIELD,
				      WITH_HEADER_TYPE_FIELD, 
				      WITH_COMMENT_FIELD,
				      WITH_BUILTIN_HEADERS,
				      WITHOUT_SRATE, 
				      WITHOUT_AUTO_COMMENT);
      ep->edat->dialog = ep->dialog;

      if (hdr->type == MUS_RAW)
	set_file_dialog_sound_attributes(ep->edat, 
					 default_output_header_type(ss), 
					 hdr->sample_type, hdr->srate, hdr->chans, 
					 hdr->data_location, hdr->samples, hdr->comment);
      else set_file_dialog_sound_attributes(ep->edat, 
					    hdr->type, hdr->sample_type, hdr->srate, hdr->chans, 
					    hdr->data_location, hdr->samples, hdr->comment);
      XtManageChild(ep->edat->error_text);
      XtManageChild(ep->dialog);

      map_over_children(ep->dialog, set_main_color_of_widget);
      XtVaSetValues(MSG_BOX(ep->dialog, XmDIALOG_OK_BUTTON),     XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(ep->dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(ep->dialog, XmDIALOG_HELP_BUTTON),   XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(ep->dialog, XmDIALOG_OK_BUTTON),     XmNbackground, ss->highlight_color,   NULL);
      XtVaSetValues(MSG_BOX(ep->dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color,   NULL);
      XtVaSetValues(MSG_BOX(ep->dialog, XmDIALOG_HELP_BUTTON),   XmNbackground, ss->highlight_color,   NULL);
      XtVaSetValues(ep->edat->header_type_list, XmNbackground, ss->white, XmNforeground, ss->black, NULL);
      XtVaSetValues(ep->edat->sample_type_list, XmNbackground, ss->white, XmNforeground, ss->black, NULL);

      XtVaSetValues(MSG_BOX(ep->dialog, XmDIALOG_MESSAGE_LABEL), XmNbackground, ss->basic_color, NULL);

      set_dialog_widget(EDIT_HEADER_DIALOG, ep->dialog);

      {
	Atom wm_delete_window;
	wm_delete_window = XmInternAtom(main_display(ss), (char *)"WM_DELETE_WINDOW", false);
	XmAddWMProtocolCallback(XtParent(ep->dialog), wm_delete_window, edit_header_wm_delete_callback, (XtPointer)ep);
      }

      XtUnmanageChild(ep->edat->error_text);
    }
  else 
    {
      XtVaSetValues(ep->dialog, 
		    XmNmessageString, xstr4, 
		    NULL);
      if (hdr->type == MUS_RAW)
	set_file_dialog_sound_attributes(ep->edat, 
					 default_output_header_type(ss), 
					 hdr->sample_type, hdr->srate, hdr->chans, 
					 hdr->data_location, hdr->samples, hdr->comment);
      else set_file_dialog_sound_attributes(ep->edat, 
					    hdr->type, hdr->sample_type, hdr->srate, hdr->chans, 
					    hdr->data_location, hdr->samples, hdr->comment);
      raise_dialog(ep->dialog);
      clear_dialog_error(ep->edat);
    }
  set_sensitive(MSG_BOX(ep->dialog, XmDIALOG_OK_BUTTON), (hdr->type == MUS_RAW)); /* nothing needs to be saved when we start */
  XmStringFree(xstr4);
  if (!(XtIsManaged(ep->dialog))) XtManageChild(ep->dialog);
  reflect_file_data_panel_change(ep->edat, (void *)ep, edit_header_set_ok_sensitive);
  return(ep->dialog);
}


void save_edit_header_dialog_state(FILE *fd)
{
  int i;
  for (i = 0; i < edhead_info_size; i++)
    if (edhead_infos[i])
      {
	edhead_info *ep;
	ep = edhead_infos[i];
	if ((ep->dialog) && 
	    (XtIsManaged(ep->dialog)) && 
	    (snd_ok(ep->sp)))
	  {
#if HAVE_SCHEME
	    fprintf(fd, "(%s (%s \"%s\"))\n", S_edit_header_dialog, S_find_sound, ep->sp->short_filename);
#endif
#if HAVE_RUBY
	    fprintf(fd, "%s(%s(\"%s\"))\n", to_proc_name(S_edit_header_dialog), to_proc_name(S_find_sound), ep->sp->short_filename);
#endif
#if HAVE_FORTH
	    fprintf(fd, "\"%s\" %s %s drop\n", ep->sp->short_filename, S_find_sound, S_edit_header_dialog);
#endif
	    break;
	  }
    }
}


typedef enum {VF_AT_CURSOR, VF_AT_END, VF_AT_BEGINNING, VF_AT_MARK, VF_AT_SAMPLE} vf_location_t;

typedef struct {
  widget_t rw;
  widget_t nm;
#if WITH_AUDIO
  widget_t pl;
#endif
  int pos;
  void *vdat;
} vf_row;

typedef struct {
  vf_row **file_list_entries;
  int index, size;
  char **names;
  char **full_names;
  int end;
  int sorter;
  int *selected_files;
  int selected_files_size;
  int currently_selected_files;
  mus_float_t amp;
  vf_location_t location_choice;
  mus_float_t speed;
  graphics_context *env_ax;
  env_editor *spf;
  env *amp_env;
  bool has_error;
  int sort_items_size;
  speed_style_t speed_style;
  mus_long_t beg;

  int dirs_size;
  void *dirs;
  char **dir_names;
  bool need_update;

  widget_t dialog;
  widget_t file_list;
  widget_t file_list_holder;
  widget_t left_title;
  widget_t info1; 
  widget_t info2; 
  widget_t mixB; 
  widget_t insertB; 
  widget_t at_cursor_button; 
  widget_t at_end_button; 
  widget_t at_beginning_button; 
  widget_t at_mark_button; 
  widget_t at_sample_button; 
  widget_t at_sample_text; 
  widget_t at_mark_text;
  widget_t amp_number; 
  widget_t amp_scrollbar;
  widget_t speed_number; 
  widget_t speed_scrollbar;
  widget_t env_drawer;
  widget_t a_to_z; 
  widget_t z_to_a; 
  widget_t new_to_old; 
  widget_t old_to_new; 
  widget_t small_to_big; 
  widget_t big_to_small; 
  widget_t smenu; 
  widget_t current_play_button;
  widget_t amp_event; 
  widget_t speed_event;
  widget_t speed_label_event;
  widget_t add_text;
  widget_t* sort_items;

  GC env_gc;
} view_files_info;

static void vf_unhighlight_row(widget_t nm, widget_t rw);
static void vf_highlight_row(widget_t nm, widget_t rw);
static void vf_post_info(view_files_info *vdat, int pos);
static void vf_unpost_info(view_files_info *vdat);
static mus_long_t vf_location(view_files_info *vdat);
static void vf_post_error(const char *error_msg, view_files_info *data);
static void redirect_vf_post_error(const char *error_msg, void *data);
static void redirect_vf_post_location_error(const char *error_msg, void *data);
static void vf_post_add_error(const char *error_msg, view_files_info *data);
static widget_t make_view_files_dialog_1(view_files_info *vdat, bool managed);
static void vf_post_selected_files_list(view_files_info *vdat);
static void view_files_add_file_or_directory(view_files_info *vdat, const char *file_or_dir);
static void vf_reflect_sort_choice_in_menu(view_files_info *vdat);
static vf_row *view_files_make_row(view_files_info *vdat, widget_t last_row);
static void vf_flash_row(vf_row *r);
static void vf_set_amp(view_files_info *vdat, mus_float_t val);
static void vf_set_speed(view_files_info *vdat, mus_float_t val);
static void vf_set_amp_env(view_files_info *vdat, env *new_e);
static void vf_clear_error(view_files_info *vdat);
static void vf_mix_insert_buttons_set_sensitive(view_files_info *vdat, bool sensitive);
static int vf_mix(view_files_info *vdat);
static bool vf_insert(view_files_info *vdat);
static void view_files_display_list(view_files_info *vdat);

static void view_files_unmonitor_directories(view_files_info *vdat) {}
static void view_files_monitor_directory(view_files_info *vdat, const char *dirname) {}




/* -------------------------------- Raw Data Dialog -------------------------------- */

/* we keep an array of raw data dialogs so that any number can be active at once */

typedef struct raw_info {
  Widget dialog;
  mus_long_t location;
  file_data *rdat;
  read_only_t read_only;
  bool selected;
  char *filename;
  char *help;
  open_requestor_t requestor;
  void *requestor_data;
  Widget requestor_dialog;
} raw_info;

static int raw_info_size = 0;
static raw_info **raw_infos = NULL;


static raw_info *new_raw_dialog(void)
{
  int loc = -1;
  if (raw_info_size == 0)
    {
      loc = 0;
      raw_info_size = 4;
      raw_infos = (raw_info **)calloc(raw_info_size, sizeof(raw_info *));
    }
  else
    {
      int i;
      for (i = 0; i < raw_info_size; i++)
	if ((!raw_infos[i]) ||
	    (!(XtIsManaged(raw_infos[i]->dialog))))
	  {
	    loc = i;
	    break;
	  }
      if (loc == -1)
	{
	  loc = raw_info_size;
	  raw_info_size += 4;
	  raw_infos = (raw_info **)realloc(raw_infos, raw_info_size * sizeof(raw_info *));
	  for (i = loc; i < raw_info_size; i++) raw_infos[i] = NULL;
	}
    }
  if (!raw_infos[loc])
    {
      raw_infos[loc] = (raw_info *)calloc(1, sizeof(raw_info));
      raw_infos[loc]->dialog = NULL;
      raw_infos[loc]->filename = NULL;
      raw_infos[loc]->help = NULL;
    }
  raw_infos[loc]->requestor = NO_REQUESTOR;
  raw_infos[loc]->requestor_data = NULL;
  raw_infos[loc]->location = 0;
  return(raw_infos[loc]);
}


static void raw_data_ok_callback(Widget w, XtPointer context, XtPointer info) 
{
  raw_info *rp = (raw_info *)context;
  int raw_srate = 0, raw_chans = 0;
  mus_sample_t raw_sample_type = MUS_UNKNOWN_SAMPLE;

  redirect_snd_error_to(post_file_panel_error, (void *)(rp->rdat));
  get_file_dialog_sound_attributes(rp->rdat, &raw_srate, &raw_chans, NULL, &raw_sample_type, &(rp->location), NULL, 1);
  redirect_snd_error_to(NULL, NULL);

  if (rp->rdat->error_widget != NOT_A_SCANF_WIDGET)
    {
      clear_error_if_panel_changes(rp->dialog, rp->rdat);
    }
  else
    {
      mus_header_set_raw_defaults(raw_srate, raw_chans, raw_sample_type);
      mus_sound_override_header(rp->filename, raw_srate, raw_chans, 
				raw_sample_type, MUS_RAW, rp->location,
				mus_bytes_to_samples(raw_sample_type, 
						     mus_sound_length(rp->filename) - rp->location));
      /* choose action based on how we got here */
      if ((rp->requestor_dialog) &&
	  ((rp->requestor == FROM_MIX_DIALOG) ||
	   (rp->requestor == FROM_INSERT_DIALOG) ||
	   (rp->requestor == FROM_VIEW_FILES_MIX_DIALOG) ||
	   (rp->requestor == FROM_VIEW_FILES_INSERT_DIALOG)))
	{
	  ss->reloading_updated_file = true; /* don't reread lack-of-header! */
	  /* redirection may be still set here, but I'll make it obvious */
	  switch (rp->requestor)
	    {
	    case FROM_MIX_DIALOG:
	      redirect_snd_error_to(redirect_file_open_error, (void *)mdat);
	      mix_complete_file_at_cursor(any_selected_sound(), rp->filename);
	      break;

	    case FROM_INSERT_DIALOG:
	      redirect_snd_error_to(redirect_file_open_error, (void *)idat);
	      insert_complete_file_at_cursor(any_selected_sound(), rp->filename);
	      break;

	    case FROM_VIEW_FILES_MIX_DIALOG:
	      {
		view_files_info *vdat = (view_files_info *)(rp->requestor_data);
		redirect_snd_error_to(redirect_vf_post_error, rp->requestor_data);
		vf_mix(vdat);
	      }
	      break;

	    case FROM_VIEW_FILES_INSERT_DIALOG:
	      {
		view_files_info *vdat = (view_files_info *)(rp->requestor_data);
		redirect_snd_error_to(redirect_vf_post_error, rp->requestor_data);
		vf_insert(vdat);
	      }
	      break;

	    default:
	      snd_error("wrong requestor type in raw data dialog? %d\n", (int)(rp->requestor));
	      break;
	    }
	  redirect_snd_error_to(NULL, NULL);
	  ss->reloading_updated_file = false;
	}
      else
	{
	  /* FROM_OPEN_DIALOG (has requestor_dialog)
	   * FROM_KEYBOARD (has sp = requestor_data)
	   * FROM_DRAG_AND_DROP (just open, no needed side effects)
	   * FROM_VIEW_FILES (ditto)
	   * FROM_VIEW_FILES_MIX_DIALOG or INSERT -- requestor_data contains needed info to complete the action
	   */
	  file_info *hdr;
	  hdr = (file_info *)calloc(1, sizeof(file_info));
	  hdr->name = mus_strdup(rp->filename);
	  hdr->type = MUS_RAW;
	  hdr->srate = raw_srate;
	  hdr->chans = raw_chans;
	  hdr->sample_type = raw_sample_type;
	  hdr->samples = mus_bytes_to_samples(raw_sample_type, 
					      mus_sound_length(rp->filename) - rp->location);
	  hdr->data_location = rp->location;
	  hdr->comment = NULL;
	  if (rp->requestor == FROM_KEYBOARD)
	    {
	      clear_status_area((snd_info *)(rp->requestor_data));
	      rp->selected = true;
	    }
	  finish_opening_sound(add_sound_window(rp->filename, rp->read_only, hdr), rp->selected);
	}
      XtUnmanageChild(rp->dialog);
    }
}


static void raw_data_cancel_callback(Widget w, XtPointer context, XtPointer info) 
{
  raw_info *rp = (raw_info *)context;
  XtUnmanageChild(rp->dialog);
  if ((rp->requestor_dialog) && 
      ((rp->requestor == FROM_OPEN_DIALOG) ||
       (rp->requestor == FROM_MIX_DIALOG)))
    XtManageChild(rp->requestor_dialog);
}


static void raw_data_reset_callback(Widget w, XtPointer context, XtPointer info) 
{
  raw_info *rp = (raw_info *)context;
  int raw_srate, raw_chans;
  mus_sample_t raw_sample_type;
  rp->location = 0;
  mus_header_raw_defaults(&raw_srate, &raw_chans, &raw_sample_type); /* pick up defaults */  
  set_file_dialog_sound_attributes(rp->rdat, 
				   IGNORE_HEADER_TYPE, 
				   raw_sample_type, raw_srate, raw_chans, rp->location, 
				   IGNORE_SAMPLES, NULL);
  if (XtIsManaged(rp->rdat->error_text))
    XtUnmanageChild(rp->rdat->error_text);
}


static void raw_data_help_callback(Widget w, XtPointer context, XtPointer info) 
{
  raw_info *rp = (raw_info *)context;
  raw_data_dialog_help(rp->help);
}


static void make_raw_data_dialog(raw_info *rp, const char *title)
{
  XmString go_away, xhelp, xok, xtitle, titlestr;
  int n;
  int raw_srate, raw_chans;
  mus_sample_t raw_sample_type;
  Arg args[20];
  Widget reset_button, main_w;

  go_away = XmStringCreateLocalized((char *)I_GO_AWAY); /* needed by template dialog */
  xhelp = XmStringCreateLocalized((char *)I_HELP);
  xok = XmStringCreateLocalized((char *)"Ok");
  if (!title)
    titlestr = XmStringCreateLocalized((char *)"No header on file");
  else titlestr = XmStringCreateLocalized((char *)title);
  xtitle = XmStringCreateLocalized((char *)title);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNcancelLabelString, go_away); n++;
  XtSetArg(args[n], XmNhelpLabelString, xhelp); n++;
  XtSetArg(args[n], XmNokLabelString, xok); n++;
  XtSetArg(args[n], XmNmessageString, xtitle); n++;
  XtSetArg(args[n], XmNdialogTitle, titlestr); n++;
  XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
  XtSetArg(args[n], XmNallowShellResize, true); n++;
  XtSetArg(args[n], XmNnoResize, false); n++;
  XtSetArg(args[n], XmNautoUnmanage, false); n++;
  /* not transient -- we want this window to remain visible if possible */
  rp->dialog = XmCreateWarningDialog(main_shell(ss), (char *)"raw data", args, n);

  XtAddCallback(rp->dialog, XmNcancelCallback, raw_data_cancel_callback, (XtPointer)rp);
  XtAddCallback(rp->dialog, XmNhelpCallback,   raw_data_help_callback,   (XtPointer)rp);
  XtAddCallback(rp->dialog, XmNokCallback,     raw_data_ok_callback,     (XtPointer)rp);
  XmStringFree(go_away);
  XmStringFree(xhelp);
  XmStringFree(xok);
  XmStringFree(xtitle);
  XmStringFree(titlestr);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
  reset_button = XtCreateManagedWidget("Reset", xmPushButtonGadgetClass, rp->dialog, args, n);
  XtAddCallback(reset_button, XmNactivateCallback, raw_data_reset_callback, (XtPointer)rp);

  mus_header_raw_defaults(&raw_srate, &raw_chans, &raw_sample_type); /* pick up defaults */

  n = 0;
  XtSetArg(args[n], XmNallowResize, true); n++;
  XtSetArg(args[n], XmNresizePolicy, XmRESIZE_NONE); n++;
  main_w = XtCreateManagedWidget("raw-main", xmFormWidgetClass, rp->dialog, args, n);

  n = 0;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  rp->rdat = make_file_data_panel(main_w, "data-form", args, n, 
				  WITH_CHANNELS_FIELD, 
				  MUS_RAW, raw_sample_type, 
				  WITH_DATA_LOCATION_FIELD, 
				  WITHOUT_SAMPLES_FIELD,
				  WITHOUT_HEADER_TYPE_FIELD, 
				  WITHOUT_COMMENT_FIELD,
				  WITH_READABLE_HEADERS,
				  WITHOUT_SRATE, 
				  WITHOUT_AUTO_COMMENT);
  rp->rdat->dialog = rp->dialog;

  set_file_dialog_sound_attributes(rp->rdat, 
				   IGNORE_HEADER_TYPE, 
				   raw_sample_type, raw_srate, raw_chans, rp->location, 
				   IGNORE_SAMPLES, NULL);

  map_over_children(rp->dialog, set_main_color_of_widget);
  XtVaSetValues(MSG_BOX(rp->dialog, XmDIALOG_OK_BUTTON),     XmNarmColor,   ss->selection_color, NULL);
  XtVaSetValues(MSG_BOX(rp->dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor,   ss->selection_color, NULL);
  XtVaSetValues(MSG_BOX(rp->dialog, XmDIALOG_HELP_BUTTON),   XmNarmColor,   ss->selection_color, NULL);
  XtVaSetValues(MSG_BOX(rp->dialog, XmDIALOG_OK_BUTTON),     XmNbackground, ss->highlight_color,   NULL);
  XtVaSetValues(MSG_BOX(rp->dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color,   NULL);
  XtVaSetValues(MSG_BOX(rp->dialog, XmDIALOG_HELP_BUTTON),   XmNbackground, ss->highlight_color,   NULL);
  XtVaSetValues(reset_button, XmNselectColor, ss->selection_color, NULL);
  XtVaSetValues(rp->rdat->sample_type_list, XmNbackground, ss->white, XmNforeground, ss->black, NULL);
  /*
   * this line makes the dialog take up all vertical space on the screen
   * XtManageChild(rp->rdat->error_text);
  */
  XtVaSetValues(MSG_BOX(rp->dialog, XmDIALOG_MESSAGE_LABEL), XmNbackground, ss->basic_color, NULL);

  XtManageChild(rp->dialog);
  XtUnmanageChild(rp->rdat->error_text); 
  set_dialog_widget(RAW_DATA_DIALOG, rp->dialog);
}


void raw_data_dialog_to_file_info(const char *filename, char *title, char *info, read_only_t read_only, bool selected)
{
  /* put up dialog for srate, chans, sample type */
  raw_info *rp;
  rp = new_raw_dialog();
  rp->read_only = read_only;
  rp->selected = selected;
  if (rp->filename) free(rp->filename);
  rp->filename = mus_strdup(filename);
  rp->requestor = ss->open_requestor;
  rp->requestor_data = ss->open_requestor_data;
  rp->requestor_dialog = ss->requestor_dialog;
  ss->open_requestor = NO_REQUESTOR;
  ss->requestor_dialog = NULL;
  ss->open_requestor_data = NULL;
  if ((rp->requestor_dialog) &&
      ((rp->requestor == FROM_OPEN_DIALOG) ||
       (rp->requestor == FROM_MIX_DIALOG)))
    XtUnmanageChild(rp->requestor_dialog);
  if (!title) 
    title = mus_format("no header found on %s\n", filename);
  if (!(rp->dialog))
    make_raw_data_dialog(rp, title);
  else
    {
      XmString xstr4;
      xstr4 = XmStringCreateLocalized(title);
      XtVaSetValues(rp->dialog, 
		    XmNmessageString, xstr4, 
		    NULL);
      XmStringFree(xstr4);
    }
  free(title);
  if (rp->help) free(rp->help);
  if (info)
    {
      XtVaSetValues(MSG_BOX(rp->dialog, XmDIALOG_HELP_BUTTON), 
		    XmNbackground, ss->green, 
		    NULL);
      rp->help = mus_strdup(info);
      free(info);
    }
  else
    {
      XtVaSetValues(MSG_BOX(rp->dialog, XmDIALOG_HELP_BUTTON), 
		    XmNbackground, ss->highlight_color, 
		    NULL);
      rp->help = NULL;
    }
  raise_dialog(rp->dialog);
  if (!XtIsManaged(rp->dialog)) 
    XtManageChild(rp->dialog);
}



/* ---------------- INFO MONOLOG ---------------- */

#define POST_IT_ROWS 12
#define POST_IT_COLUMNS 56

static Widget post_it_dialog = NULL;
static Widget post_it_text = NULL;


static void create_post_it_monolog(void)
{
  /* create scrollable but not editable text window; used for fft peaks, sp info, and raw data help displays */
  Arg args[20];
  int n;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
  XtSetArg(args[n], XmNnoResize, false); n++;
  XtSetArg(args[n], XmNtransient, false); n++;
  post_it_dialog = XmCreateMessageDialog(main_pane(ss), (char *)"info", args, n);

  XtUnmanageChild(MSG_BOX(post_it_dialog, XmDIALOG_CANCEL_BUTTON));
  XtUnmanageChild(MSG_BOX(post_it_dialog, XmDIALOG_HELP_BUTTON));
  XtUnmanageChild(MSG_BOX(post_it_dialog, XmDIALOG_SYMBOL_LABEL));

  XtVaSetValues(MSG_BOX(post_it_dialog, XmDIALOG_MESSAGE_LABEL), XmNbackground, ss->highlight_color, NULL);
      
  n = 0;
  XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
  XtSetArg(args[n], XmNeditable, false); n++;
  XtSetArg(args[n], XmNcolumns, POST_IT_COLUMNS); n++;
  XtSetArg(args[n], XmNrows, POST_IT_ROWS); n++;
  XtSetArg(args[n], XmNforeground, ss->black); n++; /* needed if color allocation fails completely */
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  post_it_text = XmCreateScrolledText(post_it_dialog, (char *)"post-it-text", args, n);
  XtManageChild(post_it_text);
  XtManageChild(post_it_dialog);

  map_over_children(post_it_dialog, set_main_color_of_widget);
  XtVaSetValues(post_it_text, XmNbackground, ss->white, XmNforeground, ss->black, NULL);
  XtVaSetValues(MSG_BOX(post_it_dialog, XmDIALOG_OK_BUTTON), XmNarmColor, ss->selection_color, NULL);
  XtVaSetValues(MSG_BOX(post_it_dialog, XmDIALOG_OK_BUTTON), XmNbackground, ss->highlight_color, NULL);

  set_dialog_widget(POST_IT_DIALOG, post_it_dialog);
}


widget_t post_it(const char *subject, const char *str)
{
  /* place string in scrollable help window */
  XmString xstr1, xstr2;
  if (!ss) return(NULL); /* an attempt to call this before X/Motif is ready */
  if (!(post_it_dialog)) 
    create_post_it_monolog(); 
  else raise_dialog(post_it_dialog);
  xstr1 = XmStringCreateLocalized((char *)subject);
  xstr2 = XmStringCreateLocalized((char *)subject);
  XtVaSetValues(post_it_dialog, 
		XmNmessageString, xstr1, 
		XmNdialogTitle, xstr2,
		NULL);
  XmTextSetString(post_it_text, (char *)str);
  if (!XtIsManaged(post_it_dialog)) 
    XtManageChild(post_it_dialog);
  XmStringFree(xstr1);
  XmStringFree(xstr2);
  return(post_it_dialog);
}


void post_it_append(const char *str)
{
  if (post_it_dialog)
    XmTextInsert(post_it_text, XmTextGetLastPosition(post_it_text), (char *)str);
}


void save_post_it_dialog_state(FILE *fd)
{
  if ((post_it_dialog) && (XtIsManaged(post_it_dialog)))
    {
      char *subject = NULL, *text = NULL;
      subject = dialog_get_title(post_it_dialog);
      text = XmTextGetString(post_it_text);
#if HAVE_SCHEME
      fprintf(fd, "(%s \"%s\" \"%s\")\n", S_info_dialog, subject, text);
#endif
#if HAVE_RUBY
      fprintf(fd, "%s(\"%s\", \"%s\")\n", to_proc_name(S_info_dialog), subject, text);
#endif
#if HAVE_FORTH
      fprintf(fd, "\"%s\" \"%s\" %s drop\n", subject, text, S_info_dialog);
#endif
      if (subject) free(subject);
      if (text) XtFree(text);
    }
}


/* ---------------- unsaved edits dialog ---------------- */

static int num_unsaved_edits_dialogs = 0;
static Widget *unsaved_edits_dialogs = NULL;
static snd_info **unsaved_edits_sounds = NULL;

static Widget unsaved_edits_dialog(snd_info *sp)
{
  int i;
  /* are there any such dialogs? */
  if (num_unsaved_edits_dialogs == 0)
    return(NULL);

  /* now see if we've already prompted about this sound */
  for (i = 0; i < num_unsaved_edits_dialogs; i++)
    if (unsaved_edits_sounds[i] == sp)
      return(unsaved_edits_dialogs[i]);

  /* try to find a free unmanaged dialog */
  for (i = 0; i < num_unsaved_edits_dialogs; i++)
    if ((unsaved_edits_dialogs[i]) &&
	(!XtIsManaged(unsaved_edits_dialogs[i])))
      return(unsaved_edits_dialogs[i]);

  return(NULL);
}

static void save_unsaved_edits_dialog(Widget d, snd_info *sp)
{
  if (num_unsaved_edits_dialogs == 0)
    {
      unsaved_edits_dialogs = (Widget *)calloc(1, sizeof(Widget));
      unsaved_edits_sounds = (snd_info **)calloc(1, sizeof(snd_info *));
    }
  else
    {
      unsaved_edits_dialogs = (Widget *)realloc(unsaved_edits_dialogs, (num_unsaved_edits_dialogs + 1) * sizeof(Widget));
      unsaved_edits_sounds = (snd_info **)realloc(unsaved_edits_sounds, (num_unsaved_edits_dialogs + 1) * sizeof(snd_info *));
    }

  unsaved_edits_dialogs[num_unsaved_edits_dialogs] = d;
  unsaved_edits_sounds[num_unsaved_edits_dialogs] = sp;
  num_unsaved_edits_dialogs++;
}


void unpost_unsaved_edits_if_any(snd_info *sp)
{
  int i;
  for (i = 0; i < num_unsaved_edits_dialogs; i++)
    if (((unsaved_edits_sounds[i] == sp) ||
	 (!snd_ok(unsaved_edits_sounds[i]))) &&
	(XtIsManaged(unsaved_edits_dialogs[i])))
      XtUnmanageChild(unsaved_edits_dialogs[i]);
}


static void zero_edits(chan_info *cp)
{
  cp->edit_ctr = 0;
}

static void unsaved_edits_no_callback(Widget w, XtPointer context, XtPointer info)
{
  snd_info *sp = (snd_info *)context;
  for_each_sound_chan(sp, zero_edits);
  snd_close_file(sp);
}

static void unsaved_edits_yes_callback(Widget w, XtPointer context, XtPointer info)
{
  snd_info *sp = (snd_info *)context;
  save_edits(sp);
  snd_close_file(sp);
}

static void unsaved_edits_help_callback(Widget w, XtPointer context, XtPointer info)
{
  snd_help("save edits?", 
	   "You have set " S_ask_about_unsaved_edits " to " PROC_TRUE ", so you will be asked whether you want \
to save edits if you try to close a sound that has unsaved edits.",
	   WITH_WORD_WRAP);
}

void save_edits_now(snd_info *sp)
{
  char *question;
  XmString q;
  Widget dialog;

  question = mus_format("%s has unsaved edits.  Save them?", sp->short_filename);
  q = XmStringCreateLocalized(question);

  dialog = unsaved_edits_dialog(sp);
  if (!dialog)
    {
      Arg args[20];
      int n;
      XmString yes, no;

      yes = XmStringCreateLocalized((char *)"yes");
      no = XmStringCreateLocalized((char *)"no");

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNokLabelString, yes); n++;
      XtSetArg(args[n], XmNcancelLabelString, no); n++;
      XtSetArg(args[n], XmNmessageString, q); n++;
      dialog = XmCreateQuestionDialog(main_pane(ss), sp->filename, args, n);
      save_unsaved_edits_dialog(dialog, sp);

      XtAddCallback(dialog, XmNhelpCallback,   unsaved_edits_help_callback, (XtPointer)sp);
      XtAddCallback(dialog, XmNokCallback,     unsaved_edits_yes_callback,  (XtPointer)sp);
      XtAddCallback(dialog, XmNcancelCallback, unsaved_edits_no_callback,   (XtPointer)sp);

      XmStringFree(yes);
      XmStringFree(no);

      map_over_children(dialog, set_main_color_of_widget);
      XtVaSetValues(MSG_BOX(dialog, XmDIALOG_OK_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(dialog, XmDIALOG_OK_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(MSG_BOX(dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(MSG_BOX(dialog, XmDIALOG_HELP_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(dialog, XmDIALOG_HELP_BUTTON), XmNbackground, ss->highlight_color, NULL);
    }
  else
    {
      XtVaSetValues(dialog, XmNmessageString, q, NULL);
    }

  XmStringFree(q);
  free(question);
  XtManageChild(dialog);
}



/* ---------------- file has changed dialog ---------------- */

static int num_file_has_changed_dialogs = 0;
static Widget *file_has_changed_dialogs = NULL;
static snd_info **file_has_changed_sounds = NULL;

static Widget file_has_changed_dialog(snd_info *sp)
{
  int i;
  /* are there any such dialogs? */
  if (num_file_has_changed_dialogs == 0)
    return(NULL);

  /* now see if we've already prompted about this sound */
  for (i = 0; i < num_file_has_changed_dialogs; i++)
    if (file_has_changed_sounds[i] == sp)
      return(file_has_changed_dialogs[i]);

  /* try to find a free unmanaged dialog */
  for (i = 0; i < num_file_has_changed_dialogs; i++)
    if ((file_has_changed_dialogs[i]) &&
	(!XtIsManaged(file_has_changed_dialogs[i])))
      return(file_has_changed_dialogs[i]);

  return(NULL);
}

static void save_file_has_changed_dialog(Widget d, snd_info *sp)
{
  if (num_file_has_changed_dialogs == 0)
    {
      file_has_changed_dialogs = (Widget *)calloc(1, sizeof(Widget));
      file_has_changed_sounds = (snd_info **)calloc(1, sizeof(snd_info *));
    }
  else
    {
      file_has_changed_dialogs = (Widget *)realloc(file_has_changed_dialogs, (num_file_has_changed_dialogs + 1) * sizeof(Widget));
      file_has_changed_sounds = (snd_info **)realloc(file_has_changed_sounds, (num_file_has_changed_dialogs + 1) * sizeof(snd_info *));
    }

  file_has_changed_dialogs[num_file_has_changed_dialogs] = d;
  file_has_changed_sounds[num_file_has_changed_dialogs] = sp;
  num_file_has_changed_dialogs++;
}


void unpost_file_has_changed_if_any(snd_info *sp)
{
  int i;
  for (i = 0; i < num_file_has_changed_dialogs; i++)
    if (((file_has_changed_sounds[i] == sp) ||
	 (!snd_ok(file_has_changed_sounds[i]))) &&
	(XtIsManaged(file_has_changed_dialogs[i])))
      XtUnmanageChild(file_has_changed_dialogs[i]);
}


static void file_has_changed_no_callback(Widget w, XtPointer context, XtPointer info)
{
}

static void file_has_changed_yes_callback(Widget w, XtPointer context, XtPointer info)
{
  snd_info *sp = (snd_info *)context;
  save_edits_without_asking(sp);
  sp->need_update = false;
  stop_bomb(sp);                  /* in case Snd already noticed the problem */
  clear_status_area(sp);
}

static void file_has_changed_help_callback(Widget w, XtPointer context, XtPointer info)
{
  snd_help("save edits?", 
	   "The file has changed unexpectedly, so in most cases, saving the current edits can't do anything useful.  But you can try anyway!",
	   WITH_WORD_WRAP);
}

void changed_file_dialog(snd_info *sp)
{
  char *question;
  XmString q;
  Widget dialog;

  question = mus_format("%s has changed!  Save edits anyway?", sp->short_filename);
  q = XmStringCreateLocalized(question);

  dialog = file_has_changed_dialog(sp);
  if (!dialog)
    {
      Arg args[20];
      int n;
      XmString yes, no;

      yes = XmStringCreateLocalized((char *)"yes");
      no = XmStringCreateLocalized((char *)"no");

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNokLabelString, yes); n++;
      XtSetArg(args[n], XmNcancelLabelString, no); n++;
      XtSetArg(args[n], XmNmessageString, q); n++;
      dialog = XmCreateQuestionDialog(main_pane(ss), sp->filename, args, n);
      save_file_has_changed_dialog(dialog, sp);

      XtAddCallback(dialog, XmNhelpCallback,   file_has_changed_help_callback, (XtPointer)sp);
      XtAddCallback(dialog, XmNokCallback,     file_has_changed_yes_callback,  (XtPointer)sp);
      XtAddCallback(dialog, XmNcancelCallback, file_has_changed_no_callback,   (XtPointer)sp);

      XmStringFree(yes);
      XmStringFree(no);

      map_over_children(dialog, set_main_color_of_widget);
      XtVaSetValues(MSG_BOX(dialog, XmDIALOG_OK_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(dialog, XmDIALOG_OK_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(MSG_BOX(dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(MSG_BOX(dialog, XmDIALOG_HELP_BUTTON), XmNarmColor, ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(dialog, XmDIALOG_HELP_BUTTON), XmNbackground, ss->highlight_color, NULL);
    }
  else
    {
      XtVaSetValues(dialog, XmNmessageString, q, NULL);
    }

  XmStringFree(q);
  free(question);
  XtManageChild(dialog);
}



/* ---------------- view files dialog ---------------- */


/* -------- view files shared code -------- */

static void view_files_clear_selected_files(view_files_info *vdat);
static int view_files_add_selected_file(view_files_info *vdat, vf_row *r);
static int view_files_find_row(view_files_info *vdat, const char *name);
static int view_files_info_size = 0;
static view_files_info **view_files_infos = NULL;

static Xen vf_open_file_watcher(Xen hook_or_reason)
{
  int k;
  /* reasons are FILE_OPENED|CLOSED, but it's not worth the trouble of splitting them out here */
  
  /* loop through all vf dialogs ... */
  for (k = 0; k < view_files_info_size; k++)
    if ((view_files_infos[k]) &&
	(view_files_infos[k]->dialog) &&
	(widget_is_active(view_files_infos[k]->dialog)))
      {
	view_files_info *vdat;
	vdat = view_files_infos[k];
	vf_mix_insert_buttons_set_sensitive(vdat, 
					    ((vdat->currently_selected_files > 0) &&
					     (any_selected_sound())));
      }
  return(Xen_false);
}


int view_files_dialog_list_length(void)
{
  int i, n = 0;
  for (i = 0; i < view_files_info_size; i++)
    if ((view_files_infos[i]) &&
	(view_files_infos[i]->dialog))
      n++;
  return(n);
}


char **view_files_dialog_titles(void)
{
  int n;
  n = view_files_dialog_list_length();
  if (n > 0)
    {
      char **titles;
      int i, j = 0;
      titles = (char **)calloc(n + 1, sizeof(char *));
      for (i = 0; i < view_files_info_size; i++)
	if ((view_files_infos[i]) &&
	    (view_files_infos[i]->dialog))
	  titles[j++] = dialog_get_title(view_files_infos[i]->dialog);
      return(titles);
    }
  return(NULL);
}


void view_files_start_dialog_with_title(const char *title)
{
  int i;
  for (i = 0; i < view_files_info_size; i++)
    if ((view_files_infos[i]) &&
	(view_files_infos[i]->dialog))
      {
	char *dialog_title = NULL;
	dialog_title = dialog_get_title(view_files_infos[i]->dialog);
	if (mus_strcmp(title, dialog_title)) /* this includes NULL == NULL */
	  {
	    if (dialog_title) free(dialog_title);
	    make_view_files_dialog_1(view_files_infos[i], true);
	    return;
	  }
	if (dialog_title) free(dialog_title);
      }
}


static view_files_info *new_view_files_dialog(void)
{
  /* always returns a new (empty) file viewer -- changed 8-Jan-08 */
  int loc = -1;
  view_files_info *vdat;

  if (view_files_info_size == 0)
    {
      loc = 0;
      view_files_info_size = 4;
      view_files_infos = (view_files_info **)calloc(view_files_info_size, sizeof(view_files_info *));
    }
  else
    {
      int i;
      for (i = 0; i < view_files_info_size; i++)
	if (!view_files_infos[i])
	  {
	    loc = i;
	    break;
	  }
      if (loc == -1)
	{
	  loc = view_files_info_size;
	  view_files_info_size += 4;
	  view_files_infos = (view_files_info **)realloc(view_files_infos, view_files_info_size * sizeof(view_files_info *));
	  for (i = loc; i < view_files_info_size; i++) view_files_infos[i] = NULL;
	}
    }

  view_files_infos[loc] = (view_files_info *)calloc(1, sizeof(view_files_info));
  vdat = view_files_infos[loc];
  vdat->index = loc;
  vdat->dialog = NULL_WIDGET;
  vdat->file_list = NULL_WIDGET;
  vdat->file_list_holder = NULL_WIDGET;
  vdat->file_list_entries = NULL;
  vdat->size = 0;
  vdat->end = -1;
  vdat->names = NULL;
  vdat->full_names = NULL;
  vdat->selected_files = NULL;
  vdat->selected_files_size = 0;
  vdat->location_choice = VF_AT_CURSOR;
  vdat->has_error = false;
  vdat->need_update = false;
  vdat->dirs_size = 0;
  vdat->dirs = NULL;
  vdat->dir_names = NULL;
  vdat->amp = 1.0;
  vdat->speed = 1.0;
  vdat->amp_env = default_env(1.0, 1.0);
  vdat->sort_items_size = 0;
  vdat->sort_items = NULL;
  vdat->speed_style = speed_control_style(ss);

  /* don't clear at this point! */
  view_files_infos[loc]->currently_selected_files = 0;
  view_files_infos[loc]->sorter = view_files_sort(ss);
  return(view_files_infos[loc]);
}


static int vf_dialog_to_index(widget_t dialog)
{
  int i;
  if ((!dialog) &&
      (view_files_infos[0]) &&
      (view_files_infos[0]->dialog))
    return(0);
  for (i = 0; i < view_files_info_size; i++)
    if ((view_files_infos[i]) &&
	(view_files_infos[i]->dialog == dialog))
      return(i);
  return(-1);
}


static view_files_info *vf_dialog_to_info(widget_t dialog)
{
  int index;
  index = vf_dialog_to_index(dialog);
  if (index >= 0)
    return(view_files_infos[index]);
  return(NULL);
}


static char **vf_selected_files(view_files_info *vdat)
{
  int len;
  char **files = NULL;
  len = vdat->currently_selected_files;
  if (len > 0)
    {
      int i;
      files = (char **)calloc(len, sizeof(char *));
      for (i = 0; i < len; i++) 
	files[i] = mus_strdup(vdat->full_names[vdat->selected_files[i]]);
    }
  return(files);
}


static char **view_files_selected_files(widget_t dialog, int *len)
{
  /* free result */
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    {
      (*len) = vdat->currently_selected_files;
      return(vf_selected_files(vdat));
    }
  (*len) = 0;
  return(NULL);
}


static void view_files_run_select_hook(widget_t dialog, const char *selected_file);

static char **view_files_set_selected_files(widget_t dialog, char **files, int len)
{
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    {
      int i;
      view_files_clear_selected_files(vdat);
      for (i = 0; i < len; i++)
	if (files[i])
	  {
	    int loc;
	    loc = view_files_find_row(vdat, (const char *)(files[i]));
	    if (loc >= 0)
	      {
		view_files_add_selected_file(vdat, vdat->file_list_entries[loc]);
		view_files_run_select_hook(vdat->dialog, (const char *)(files[i]));
	      }
	  }
      vf_mix_insert_buttons_set_sensitive(vdat, 
					  ((vdat->currently_selected_files > 0) &&
					   (any_selected_sound())));
    }
  return(files);
}


static char **view_files_files(widget_t dialog, int *len)
{
  /* don't free result! */
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    {
      (*len) = vdat->end + 1;
      return(vdat->full_names);
    }
  (*len) = 0;
  return(NULL);
}


static void view_files_clear_list(view_files_info *vdat);

static char **view_files_set_files(widget_t dialog, char **files, int len)
{
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    {
      view_files_clear_selected_files(vdat);
      view_files_clear_list(vdat);
      if (len > 0)
	{
	  int i;
	  for (i = 0; i < len; i++)
	    if (files[i])
	      view_files_add_file_or_directory(vdat, (const char *)(files[i]));
	}
      view_files_display_list(vdat);
    }
  return(files);
}


static void vf_mix_insert_buttons_set_sensitive(view_files_info *vdat, bool sensitive)
{
  if (vdat->mixB)
    {
      set_sensitive(vdat->mixB, sensitive);
      set_sensitive(vdat->insertB, sensitive);
    }
}


static void view_files_clear_selected_files(view_files_info *vdat)
{
  int len;
  len = vdat->currently_selected_files;
  if (len > 0)
    {
      int i;
      for (i = 0; i < len; i++)
	{
	  vf_row *r;
	  r = vdat->file_list_entries[vdat->selected_files[i]];
	  if (r)
	    vf_unhighlight_row(r->nm, r->rw);
	}
    }
  vdat->currently_selected_files = 0;
  vf_mix_insert_buttons_set_sensitive(vdat, false);
}


static void view_files_unselect_file(view_files_info *vdat, vf_row *r)
{
  vf_unhighlight_row(r->nm, r->rw);
  if (vdat->currently_selected_files > 1)
    {
      /* need to fixup selected_files list */
      int i, new_loc = 0;
      for (i = 0; i < vdat->currently_selected_files; i++)
	if (vdat->selected_files[i] != r->pos)
	  vdat->selected_files[new_loc++] = vdat->selected_files[i];
    }
  vdat->currently_selected_files--;
  if (vdat->currently_selected_files < 0) 
    vdat->currently_selected_files = 0;
  if (vdat->currently_selected_files == 0)
    {
      vf_mix_insert_buttons_set_sensitive(vdat, false);
      vf_unpost_info(vdat);
    }
}


static int view_files_add_selected_file(view_files_info *vdat, vf_row *r)
{
  /* returns how many are now selected (counting new) */
  if (vdat->selected_files_size == 0)
    {
      vdat->selected_files_size = 4;
      vdat->selected_files = (int *)calloc(vdat->selected_files_size, sizeof(int));
      vdat->selected_files[0] = r->pos;
      vdat->currently_selected_files = 1;
    }
  else
    {
      if (vdat->currently_selected_files >= vdat->selected_files_size)
	{
	  vdat->selected_files_size += 4;
	  vdat->selected_files = (int *)realloc(vdat->selected_files, vdat->selected_files_size * sizeof(int));
	  vdat->selected_files[vdat->currently_selected_files++] = r->pos;
	}
      else 
	{
	  vdat->selected_files[vdat->currently_selected_files++] = r->pos;
	}
    }
  vf_highlight_row(r->nm, r->rw);
  return(vdat->currently_selected_files);
}


static void vf_fixup_selected_files(view_files_info *vdat, char **saved_selected_files, int len)
{
  /* various things change the order or contents of the files list, so the selected locs list needs to reflect that */
  int i, newly_selected = 0;
  for (i = 0; i < len; i++)
    {
      int j;
      for (j = 0; j <= vdat->end; j++)
	if ((vdat->full_names[j]) &&
	    (mus_strcmp(vdat->full_names[j], saved_selected_files[i])))
	  {
	    vf_row *old_r, *new_r;
	    /* fprintf(stderr,"old %d at %d -> %d at %d\n", vdat->selected_files[i], i, j, newly_selected); */
	    old_r = vdat->file_list_entries[vdat->selected_files[i]];
	    vdat->selected_files[newly_selected++] = j;
	    new_r = vdat->file_list_entries[j];
	    if (new_r != old_r)
	      {
		vf_highlight_row(new_r->nm, new_r->rw);
		vf_unhighlight_row(old_r->nm, old_r->rw);
	      }
	    break;
	  }
    }
  vdat->currently_selected_files = newly_selected;
}


static int view_files_find_row(view_files_info *vdat, const char *name)
{
  int i;
  if (vdat->names)
    for (i = 0; i <= vdat->end; i++)
      if ((vdat->names[i]) && 
	  (mus_strcmp(vdat->names[i], name)))
  	return(i);
  if (vdat->full_names)
    for (i = 0; i <= vdat->end; i++)
      if ((vdat->full_names[i]) && 
	  (mus_strcmp(vdat->full_names[i], name)))
	return(i);
  return(-1);
}


static void view_files_select(vf_row *r, bool add_to_selected)
{
  view_files_info *vdat = (view_files_info *)(r->vdat);
  int i, curloc = -1;

  for (i = 0; i < vdat->currently_selected_files; i++)
    if (vdat->selected_files[i] == r->pos)
      {
	curloc = r->pos;
	break;
      }
  if (curloc == -1)
    {
      /* file not currently selected */
      if (!add_to_selected)         /* not shift click, so remove all currently selected files first */
	view_files_clear_selected_files(vdat);
      view_files_add_selected_file(vdat, r);
      view_files_run_select_hook(vdat->dialog, vdat->full_names[r->pos]);
    }
  else
    {
      /* file already selected, so remove from selected files list */
      view_files_unselect_file(vdat, r);
    }

  if ((vdat->currently_selected_files == 0) ||
      ((vdat->currently_selected_files == 1) &&
       (!(is_plausible_sound_file(vdat->full_names[vdat->selected_files[0]])))))
    vf_unpost_info(vdat);
  else
    {
      if (vdat->currently_selected_files == 1)
	vf_post_info(vdat, vdat->selected_files[0]);
      else vf_post_selected_files_list(vdat);
    }
  vf_mix_insert_buttons_set_sensitive(vdat, 
				      ((vdat->currently_selected_files > 0) &&
				       (any_selected_sound())));
}


static bool view_files_play(view_files_info *vdat, int pos, bool play)
{
  static snd_info *play_sp;
  if (play)
    {
      if (play_sp)
	{
	  if (play_sp->playing) return(true); /* can't play two of these at once */
	  if ((!vdat->names[pos]) || 
	      (!mus_strcmp(play_sp->short_filename, vdat->names[pos])))
	    {
	      completely_free_snd_info(play_sp);
	      play_sp = NULL;
	    }
	}
      if ((!play_sp) && 
	  (vdat->full_names[pos]))
	play_sp = make_sound_readable(vdat->full_names[pos], false);
      if (play_sp)
	{
	  play_sp->short_filename = vdat->names[pos];
	  play_sp->filename = NULL;
	  /* pass view files dialog settings to play */
	  play_sp->speed_control = vdat->speed;
	  play_sp->amp_control = vdat->amp;
	  play_sound(play_sp, 0, NO_END_SPECIFIED);
	}
      else return(true); /* can't find or setup file */
    }
  else
    { /* play toggled off */
      if ((play_sp) && (play_sp->playing)) 
	{
	  stop_playing_sound(play_sp, PLAY_BUTTON_UNSET);
	  vdat->current_play_button = NULL_WIDGET;
	}
    }
  return(false);
}


void view_files_unplay(void)
{
  int k;
  for (k = 0; k < view_files_info_size; k++)
    if ((view_files_infos[k]) &&
	(view_files_infos[k]->dialog) &&
	(widget_is_active(view_files_infos[k]->dialog)))
      {
	view_files_info *vdat;
	vdat = view_files_infos[k];
	if ((vdat->current_play_button) &&
	    (XmToggleButtonGetState(vdat->current_play_button) != XmUNSET))
	  {
	    set_toggle_button(vdat->current_play_button, false, true, (void *)vdat);
	    vdat->current_play_button = NULL_WIDGET;
	  }
      }
}


static void view_files_reflect_sort_items(void)
{
  int i;
  view_files_info *vdat;
  int j = 0, k;

  if (view_files_info_size == 0) return;
  for (i = 0; i < ss->file_sorters_size; i++)
    {
      Xen ref;
      ref = Xen_vector_ref(ss->file_sorters, i);
      if (Xen_is_pair(ref))
	{
	  XmString s1;
	  s1 = XmStringCreateLocalized((char *)Xen_string_to_C_string(Xen_car(ref)));
	  for (k = 0; k < view_files_info_size; k++)
	    if ((view_files_infos[k]) &&
		(view_files_infos[k]->dialog))
	      {
		vdat = view_files_infos[k];
		if (j >= vdat->sort_items_size)
		  {
		    int n = 0, k, old_size;
		    Arg args[20];
		    old_size = vdat->sort_items_size;
		    XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
		    vdat->sort_items_size += 4;
		    vdat->sort_items = (Widget *)realloc(vdat->sort_items, vdat->sort_items_size * sizeof(Widget));
		    for (k = old_size; k < vdat->sort_items_size; k++)
		      vdat->sort_items[k] = XtCreateWidget("unused", xmPushButtonWidgetClass, vdat->smenu, args, n);
		  }
		XtVaSetValues(vdat->sort_items[j], 
			      XmNlabelString, s1,
			      XmNuserData, i + SORT_XEN, /* this is an index into the file_sorters list, not the widget list */
			      NULL);
		XtManageChild(vdat->sort_items[j]);
	      }
	  j++;
	  XmStringFree(s1);
	}
    }
}


/* (add-file-sorter "duration" 
		(lambda (lst)
		  (sort lst 
			(lambda (a b)
			  (> (mus-sound-duration a) (mus-sound-duration b))))))

 */


static void vf_add_file(view_files_info *vdat, const char *filename, const char *fullname)
{
  vdat->end++;
  if (vdat->end >= vdat->size)
    {
      int new_size;
      new_size = vdat->size + 32;
      if (vdat->size == 0)
	{
	  vdat->names = (char **)calloc(new_size, sizeof(char *));
	  vdat->full_names = (char **)calloc(new_size, sizeof(char *));
	}
      else
	{
	  int i;
	  vdat->names = (char **)realloc(vdat->names, new_size * sizeof(char *));
	  vdat->full_names = (char **)realloc(vdat->full_names, new_size * sizeof(char *));
	  for (i = vdat->size; i < new_size; i++) 
	    {
	      vdat->names[i] = NULL; 
	      vdat->full_names[i] = NULL; 
	    }
	}
      if (!vdat->file_list_entries)
	vdat->file_list_entries = (vf_row **)calloc(new_size, sizeof(vf_row *));
      else 
	{
	  int i;
	  vdat->file_list_entries = (vf_row **)realloc(vdat->file_list_entries, new_size * sizeof(vf_row *));
	  for (i = vdat->size; i < new_size; i++) vdat->file_list_entries[i] = NULL;
	}
      vdat->size = new_size;
    }
  vdat->names[vdat->end] = mus_strdup(filename);
  vdat->full_names[vdat->end] = mus_strdup(fullname);
}


static void add_file_to_view_files_list(view_files_info *vdat, const char *filename, const char *fullname)
{
  int row;

  row = view_files_find_row(vdat, filename);
  if (row != -1)
    {
      if ((vdat->dialog) &&
	  (widget_is_active(vdat->dialog)) &&
	  (vdat->file_list_entries[row]))
	{
	  ensure_scrolled_window_row_visible(vdat->file_list, row, vdat->end + 1);
	  vf_flash_row(vdat->file_list_entries[row]);
	}
      return;
    }

  errno = 0;
  if (!(mus_file_probe(fullname)))
    {
      if ((vdat->dialog) &&
	  (widget_is_active(vdat->dialog)))
	{
	  char *msg;
	  if (errno != 0)
	    msg = mus_format("%s: %s", filename, strerror(errno));
	  else msg = mus_format("%s does not exist", filename);
	  vf_post_add_error(msg, vdat);
	  free(msg);
	}
      return;
    }

  vf_add_file(vdat, filename, fullname);
}



/* what about temps coming and going -- should we just add a need-update switch for later remanage? */
/*   remanagement only through make_view_files_dialog -- this file */
/*   perhaps ss->making|deleting_temp_file -> ignore this fam event? */

static void add_directory_to_view_files_list(view_files_info *vdat, const char *dirname)
{
  /* I think all directory additions come through here */

  if ((dirname) && (dirname[strlen(dirname) - 1] != '/'))
    {
      char *add_slash;
      add_slash = mus_format("%s/", dirname);
      add_directory_to_view_files_list(vdat, add_slash);
      free(add_slash);
    }
  else
    {
      dir_info *sound_files = NULL;
#if (!USE_NO_GUI)
      view_files_monitor_directory(vdat, dirname);
#endif
      sound_files = find_sound_files_in_dir(dirname);
      if ((sound_files) && 
	  (sound_files->len > 0))
	{
	  int i, dirs = 0, len = 16;
	  for (i = 0; i < sound_files->len; i++) 
	    add_file_to_view_files_list(vdat, sound_files->files[i]->filename, sound_files->files[i]->full_filename);
	  sound_files = free_dir_info(sound_files);

	  /* fixup title */
	  for (i = 0; i < vdat->dirs_size; i++)
	    if (vdat->dir_names[i])
	      {
		dirs++;
		len += mus_strlen(just_filename(vdat->dir_names[i]));
	      }
	  if ((dirs < 4) &&
	      (len < 512))
	    {
	      int cur_dir = 0;
	      char *titlestr = NULL, *dirstr;
	      titlestr = (char *)calloc(len + dirs * 8, sizeof(char));
	      strcat(titlestr, "Files: ");
	      for (i = 0; i < vdat->dirs_size; i++)
		if (vdat->dir_names[i])
		  {
		    dirstr = just_filename(vdat->dir_names[i]);
		    strcat(titlestr, dirstr);
		    free(dirstr);
		    cur_dir++;
		    if (cur_dir < dirs)
		      strcat(titlestr, ", ");
		  }
	      dialog_set_title(vdat->dialog, titlestr);
	      free(titlestr);
	    }
	}
    }
}


static void view_files_sort_list(view_files_info *vdat)
{
  if (vdat->end >= 0)
    {
      sort_info **data;
      int i, len;

      len = vdat->end + 1;
      data = (sort_info **)calloc(len, sizeof(sort_info *));

      for (i = 0; i < len; i++)
	{
	  data[i] = (sort_info *)calloc(1, sizeof(sort_info));
	  data[i]->filename = vdat->names[i];
	  data[i]->full_filename = vdat->full_names[i];
	}

      snd_sort(vdat->sorter, data, len);

      for (i = 0; i < len; i++)
	{
	  vdat->names[i] = data[i]->filename;
	  vdat->full_names[i] = data[i]->full_filename;
	  free(data[i]);
	}
      free(data);
    }
}


static void view_files_display_list(view_files_info *vdat)
{
  int i;
  vf_row *r;

  if (!vdat) return;
  if (!(vdat->dialog)) return;

  if (vdat->end >= 0)
    {
      int i, old_len;
      char **old_names = NULL;
      widget_t last_row = NULL_WIDGET; 

      old_len = vdat->currently_selected_files;
      if (old_len > 0)
	old_names = vf_selected_files(vdat);

      view_files_sort_list(vdat);
      for (i = 0; i <= vdat->end; i++)
	{
	  r = vdat->file_list_entries[i];
	  if (!r)
	    {
	      r = view_files_make_row(vdat, last_row);
	      vdat->file_list_entries[i] = r;
	      r->pos = i;
	    }
	  set_button_label(r->nm, vdat->names[r->pos]);
#if WITH_AUDIO
	  set_toggle_button(r->pl, false, false, (void *)vdat);
#endif
	  if (!(widget_is_active(r->rw))) activate_widget(r->rw);
	  last_row = r->rw;
	}

      if (old_names)
	{
	  vf_fixup_selected_files(vdat, old_names, old_len);
	  for (i = 0; i < old_len; i++) free(old_names[i]);
	  free(old_names);
	}
    }

  for (i = vdat->end + 1; i < vdat->size; i++)
    {
      r = vdat->file_list_entries[i];
      if (r)
	{
	  if (widget_is_active(r->rw)) 
	    deactivate_widget(r->rw);
	}
    }

  if (!(widget_is_active(vdat->file_list))) 
    activate_widget(vdat->file_list);
}


static void view_files_clear_list(view_files_info *vdat)
{
#if (!USE_NO_GUI)
  view_files_unmonitor_directories(vdat);
#endif
  if (vdat->names)
    {
      int i;
      for (i = 0; i < vdat->size; i++)
	if (vdat->names[i]) 
	  {
	    free(vdat->names[i]); 
	    vdat->names[i] = NULL;
	    free(vdat->full_names[i]); 
	    vdat->full_names[i] = NULL;
	  }
      vdat->end = -1;
      vdat->currently_selected_files = 0;
    }
}

#if 0
static void view_files_update_list(view_files_info *vdat)
{
  /* here we need the file's full name */
  int i, old_len;
  char **old_names = NULL;

  old_len = vdat->currently_selected_files;
  if (old_len > 0) 
    old_names = vf_selected_files(vdat);

  if (vdat->names)
    {
      int i, j;
      for (i = 0; i <= vdat->end; i++)
	if (vdat->names[i]) 
	  {
	    if (!(mus_file_probe(vdat->full_names[i])))
	      {
		free(vdat->names[i]); 
		vdat->names[i] = NULL;
		free(vdat->full_names[i]); 
		vdat->full_names[i] = NULL;
	      }
	  }

      for (i = 0, j = 0; i <= vdat->end; i++)
	if (vdat->names[i])
	  {
	    if (i != j) 
	      {
		vdat->names[j] = vdat->names[i]; 
		vdat->names[i] = NULL;
		vdat->full_names[j] = vdat->full_names[i];
		vdat->full_names[i] = NULL;
	      }
	    j++;
	  }
      vdat->end = j - 1;
    }

  if (old_names)
    {
      vf_fixup_selected_files(vdat, old_names, old_len);
      for (i = 0; i < old_len; i++) free(old_names[i]);
      free(old_names);
    }
}
#endif


static void vf_clear_error(view_files_info *vdat)
{
  if (vdat->currently_selected_files == 1)
    vf_post_info(vdat, vdat->selected_files[0]);
  else
    {
      if (vdat->currently_selected_files == 0)
	vf_unpost_info(vdat);
      else vf_post_selected_files_list(vdat);
    }
  vdat->has_error = false;
}


static int vf_mix(view_files_info *vdat)
{
  int len, id_or_error = 0;
  snd_info *sp;

  sp = any_selected_sound();
  len = vdat->currently_selected_files;

  if ((len == 1) &&
      (snd_feq(vdat->amp, 1.0)) &&
      (snd_feq(vdat->speed, 1.0)) &&
      (is_default_env(vdat->amp_env)))
    id_or_error = mix_complete_file(sp, vdat->beg, 
				    vdat->full_names[vdat->selected_files[0]], 
				    with_mix_tags(ss), DONT_DELETE_ME, MIX_SETS_SYNC_LOCALLY, NULL);
  else
    {
      int i;
      bool err = false;
      char *tempfile;
      char **selected_files;

      selected_files = vf_selected_files(vdat);
      tempfile = scale_and_src(selected_files, len, sp->nchans, vdat->amp, vdat->speed, vdat->amp_env, &err);

      if (err)
	{
	  vf_post_error(tempfile, vdat);
	  id_or_error = MIX_FILE_NO_TEMP_FILE;
	}
      else
	{ 
	  if (sp->nchans > 1)
	    remember_temp(tempfile, sp->nchans);
	  id_or_error = mix_complete_file(sp, vdat->beg, tempfile,
					  with_mix_tags(ss), 
					  (sp->nchans > 1) ? MULTICHANNEL_DELETION : DELETE_ME,
					  MIX_SETS_SYNC_LOCALLY, NULL);
	}
      free(tempfile);
      for (i = 0; i < len; i++)
	free(selected_files[i]);
      free(selected_files);
    }
  return(id_or_error);
}


static void view_files_mix_selected_files(widget_t w, view_files_info *vdat)
{
  vdat->has_error = false;
  redirect_snd_error_to(redirect_vf_post_location_error, (void *)vdat);
  vdat->beg = vf_location(vdat);
  redirect_snd_error_to(NULL, NULL);

  if (!(vdat->has_error))
    {
      int id_or_error = 0;

      redirect_snd_error_to(redirect_vf_post_error, (void *)vdat);
      ss->requestor_dialog = w;
      ss->open_requestor_data = (void *)vdat;
      ss->open_requestor = FROM_VIEW_FILES_MIX_DIALOG;
      id_or_error = vf_mix(vdat);

      /* "id_or_error" here is either one of the mix id's or an error indication such as MIX_FILE_NO_MIX */
      /*    the possible error conditions have been checked already, or go through snd_error */

      redirect_snd_error_to(NULL, NULL);
      if (id_or_error >= 0)
	{
	  char *msg;
	  if (vdat->currently_selected_files == 1)
	    msg = mus_format("%s mixed in at %" print_mus_long, vdat->names[vdat->selected_files[0]], vdat->beg);
	  else msg = mus_format("selected files mixed in at %" print_mus_long, vdat->beg);
	  vf_post_error(msg, vdat);
	  vdat->has_error = false;
	  free(msg);
	}
    }
}


static bool vf_insert(view_files_info *vdat)
{
  int len;
  bool ok = false;
  snd_info *sp;
  sp = any_selected_sound();

  len = vdat->currently_selected_files;
  if ((len == 1) &&
      (snd_feq(vdat->amp, 1.0)) &&
      (snd_feq(vdat->speed, 1.0)) &&
      (is_default_env(vdat->amp_env)))
    ok = insert_complete_file(sp, 
			      vdat->full_names[vdat->selected_files[0]], 
			      vdat->beg,
			      DONT_DELETE_ME);
  else
    {
      int i;
      bool err = false;
      char *tempfile;
      char **selected_files;

      selected_files = vf_selected_files(vdat);
      tempfile = scale_and_src(selected_files, len, sp->nchans, vdat->amp, vdat->speed, vdat->amp_env, &err);

      if (err)
	{
	  vf_post_error(tempfile, vdat);
	  ok = false;
	}
      else
	{
	  vf_clear_error(vdat);
	  if (sp->nchans > 1)
	    remember_temp(tempfile, sp->nchans);
	  ok = insert_complete_file(sp, 
				    tempfile,
				    vdat->beg,
				    (sp->nchans > 1) ? MULTICHANNEL_DELETION : DELETE_ME);
	}
      free(tempfile);
      for (i = 0; i < len; i++)
	free(selected_files[i]);
      free(selected_files);
    }
  return(ok);
}


static void view_files_insert_selected_files(widget_t w, view_files_info *vdat)
{
  vdat->has_error = false;
  redirect_snd_error_to(redirect_vf_post_location_error, (void *)vdat);
  vdat->beg = vf_location(vdat);
  redirect_snd_error_to(NULL, NULL);

  if (!(vdat->has_error))
    {
      bool ok;

      redirect_snd_error_to(redirect_vf_post_error, (void *)vdat);
      redirect_snd_warning_to(redirect_vf_post_error, (void *)vdat);
      ss->requestor_dialog = w;
      ss->open_requestor = FROM_VIEW_FILES_INSERT_DIALOG;
      ss->open_requestor_data = (void *)vdat;
      ok = vf_insert(vdat);
      redirect_snd_error_to(NULL, NULL);
      redirect_snd_warning_to(NULL, NULL);

      if (ok)
	{
	  char *msg;
	  if (vdat->currently_selected_files == 1)
	    msg = mus_format("%s inserted at %" print_mus_long, vdat->names[vdat->selected_files[0]], vdat->beg);
	  else msg = mus_format("selected files inserted at %" print_mus_long, vdat->beg);
	  vf_post_error(msg, vdat);
	  vdat->has_error = false;
	  free(msg);
	}
      /* else we've already posted whatever went wrong (make_file_info etc) */
    }
}


static mus_float_t view_files_amp(widget_t dialog)
{
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    return(vdat->amp);
  return(0.0);
}


static mus_float_t view_files_set_amp(widget_t dialog, mus_float_t new_amp)
{
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    vf_set_amp(vdat, new_amp);
  return(new_amp);
}


static mus_float_t view_files_speed(widget_t dialog)
{
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    return(vdat->speed);
  return(1.0);
}


static mus_float_t view_files_set_speed(widget_t dialog, mus_float_t new_speed)
{
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    vf_set_speed(vdat, new_speed);
  return(new_speed);
}


static speed_style_t view_files_speed_style(widget_t dialog)
{
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    return(vdat->speed_style);
  return(SPEED_CONTROL_AS_FLOAT);
}


static speed_style_t view_files_set_speed_style(widget_t dialog, speed_style_t speed_style)
{
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    {
      vdat->speed_style = speed_style;
      vf_set_speed(vdat, vdat->speed); /* update label etc */
    }
  return(speed_style);
}


static env *view_files_amp_env(widget_t dialog)
{
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    return(vdat->amp_env);
  return(NULL);
}


static void view_files_set_amp_env(widget_t dialog, env *new_e)
{
  vf_set_amp_env(vf_dialog_to_info(dialog), new_e);
}


static int view_files_local_sort(widget_t dialog)
{
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    return(vdat->sorter);
  return(-1);
}


static int view_files_set_local_sort(widget_t dialog, int sort_choice)
{
  view_files_info *vdat;
  vdat = vf_dialog_to_info(dialog);
  if (vdat)
    {
      vdat->sorter = sort_choice;
      view_files_display_list(vdat);
      vf_reflect_sort_choice_in_menu(vdat);
    }
  return(sort_choice);
}


static view_files_info *view_files_find_dialog(widget_t dialog)
{
  int i;
  for (i = 0; i < view_files_info_size; i++)
    if ((view_files_infos[i]) &&
	(view_files_infos[i]->dialog == dialog))
      return(view_files_infos[i]);
  return(NULL);
}


widget_t make_view_files_dialog(bool managed, bool make_new)
{
  int i;
  view_files_info *vdat = NULL;

  if (make_new)
    return(make_view_files_dialog_1(new_view_files_dialog(), managed));

  for (i = 0; i < view_files_info_size; i++)
    if ((view_files_infos[i]) &&
	(view_files_infos[i]->dialog))
      {
	vdat = view_files_infos[i];
	if (widget_is_active(vdat->dialog))
	  break;
      }

  if (vdat)
    return(make_view_files_dialog_1(vdat, managed));
  return(make_view_files_dialog_1(new_view_files_dialog(), managed));
}


void save_view_files_dialogs(FILE *fd) 
{
#if HAVE_EXTENSION_LANGUAGE
  int i;
  view_files_info *vdat;
  for (i = 0; i < view_files_info_size; i++)
    if ((view_files_infos[i]) &&
	(view_files_infos[i]->dialog) &&
	(widget_is_active(view_files_infos[i]->dialog)))
      {
	vdat = view_files_infos[i];

#if HAVE_SCHEME
	fprintf(fd, "(let ((vf (" S_view_files_dialog " #t #t)))\n");

	if (vdat->full_names)
	  {
	    int k;
	    fprintf(fd, "  (set! (" S_view_files_files " vf) (list");
	    for (k = 0; k <= vdat->end; k++)
	      fprintf(fd, " \"%s\"", vdat->full_names[k]);
	    fprintf(fd, "))\n");
	    if (vdat->currently_selected_files > 0)
	      {
		fprintf(fd, "  (set! (" S_view_files_selected_files " vf) (list");
		for (k = 0; k < vdat->currently_selected_files; k++)
		  fprintf(fd, " \"%s\"", vdat->full_names[vdat->selected_files[k]]);
		fprintf(fd, "))\n");
	      }
	  }
	if (!(snd_feq(vdat->amp, 1.0)))
	  fprintf(fd, "  (set! (" S_view_files_amp " vf) %.3f)\n", vdat->amp);

	if (!(snd_feq(vdat->speed, 1.0)))
	  fprintf(fd, "  (set! (" S_view_files_speed " vf) %.3f)\n", vdat->speed);

	if (!(is_default_env(vdat->amp_env)))
	  fprintf(fd, "  (set! (" S_view_files_amp_env " vf) %s)\n", env_to_string(vdat->amp_env));

	/* assume file-sorters are set up already */
	fprintf(fd, "  (set! (" S_view_files_sort " vf) %d)\n", vdat->sorter);	    
	fprintf(fd, ")\n");
#endif

#if HAVE_RUBY
	fprintf(fd, "vf = view_files_dialog(true, true)\n");

	if (vdat->full_names)
	  {
	    int k;
	    fprintf(fd, "  set_view_files_files(vf, [");
	    for (k = 0; k < vdat->end; k++)
	      fprintf(fd, "\"%s\", ", vdat->full_names[k]);
	    fprintf(fd, "\"%s\"])\n", vdat->full_names[vdat->end]);
	    if (vdat->currently_selected_files > 0)
	      {
		fprintf(fd, "  set_view_files_selected_files(vf, [");
		for (k = 0; k < vdat->currently_selected_files - 1; k++)
		  fprintf(fd, "\"%s\", ", vdat->full_names[vdat->selected_files[k]]);
		fprintf(fd, "\"%s\"])\n", vdat->full_names[vdat->selected_files[vdat->currently_selected_files]]);
	      }
	  }
	if (!(snd_feq(vdat->amp, 1.0)))
	  fprintf(fd, "  set_view_files_amp(vf, %.3f)\n", vdat->amp);

	if (!(snd_feq(vdat->speed, 1.0)))
	  fprintf(fd, "  set_view_files_speed(vf, %.3f)\n", vdat->speed);

	if (!(is_default_env(vdat->amp_env)))
	  fprintf(fd, "  set_view_files_amp_env(vf, %s)\n", env_to_string(vdat->amp_env));

	/* assume file-sorters are set up already */
	fprintf(fd, "  set_view_files_sort(vf, %d)\n", vdat->sorter);	    
	fprintf(fd, "\n");
#endif

#if HAVE_FORTH
	fprintf(fd, "#t #t view-files-dialog value vf\n");

	if (vdat->full_names)
	  {
	    int k;
	    fprintf(fd, "  vf '(");
	    for (k = 0; k <= vdat->end; k++)
	      fprintf(fd, " \"%s\"", vdat->full_names[k]);
	    fprintf(fd, " ) set-view-files-files drop\n");
	    if (vdat->currently_selected_files > 0)
	      {
		fprintf(fd, "  vf '(");
		for (k = 0; k <= vdat->currently_selected_files; k++)
		  fprintf(fd, " \"%s\"", vdat->full_names[vdat->selected_files[k]]);
		fprintf(fd, " ) set-view-files-selected-files drop\n");
	      }
	  }
	if (!(snd_feq(vdat->amp, 1.0)))
	  fprintf(fd, "  vf %.3f set-view-files-amp drop\n", vdat->amp);

	if (!(snd_feq(vdat->speed, 1.0)))
	  fprintf(fd, "  vf %.3f set-view-files-speed drop\n", vdat->speed);

	if (!(is_default_env(vdat->amp_env)))
	  fprintf(fd, "  vf %s set-view-files-amp-env drop\n", env_to_string(vdat->amp_env));

	/* assume file-sorters are set up already */
	fprintf(fd, "  vf %d set-view-files-sort drop\n\n", vdat->sorter);
#endif
      }
#endif
}


void view_files_add_directory(widget_t dialog, const char *dirname) 
{
  view_files_info *vdat = NULL;

  if (dialog)
    vdat = view_files_find_dialog(dialog);
  else 
    {
      if (view_files_info_size > 0)
	vdat = view_files_infos[0];
      else 
	{
	  vdat = new_view_files_dialog();
	  make_view_files_dialog_1(vdat, false);
	}
    }

  if (vdat)
    {
      char *full_filename;
      full_filename = mus_expand_filename((const char *)dirname);
      if (!(mus_file_probe(full_filename)))
	{
	  if ((vdat->dialog) &&
	      (widget_is_active(vdat->dialog)))
	    {
	      char *msg;
	      if (errno != 0)
		msg = mus_format("%s: %s", full_filename, strerror(errno));
	      else msg = mus_format("%s does not exist", full_filename);
	      vf_post_add_error(msg, vdat);
	      free(msg);
	    }
	}
      else
	{
	  add_directory_to_view_files_list(vdat, full_filename);
	}
      free(full_filename);
    }
}


static void view_files_add_file(widget_t dialog, const char *filename)
{
  view_files_info *vdat = NULL;

  if (dialog)
    vdat = view_files_find_dialog(dialog);
  else 
    {
      if (view_files_info_size > 0)
	vdat = view_files_infos[0];
      else 
	{
	  vdat = new_view_files_dialog();
	  make_view_files_dialog_1(vdat, false);
	}
    }

  if (vdat)
    {
      char *full_filename;
      full_filename = mus_expand_filename((const char *)filename);
      add_file_to_view_files_list(vdat, filename, full_filename);
      free(full_filename);
    }
}


static void view_files_open_selected_files(view_files_info *vdat)
{
  ss->open_requestor = FROM_VIEW_FILES;

  if (vdat->currently_selected_files > 0)
    {
      int i;
      snd_info *sp = NULL;
      for (i = 0; i < vdat->currently_selected_files; i++)
	sp = snd_open_file(vdat->full_names[vdat->selected_files[i]], FILE_READ_WRITE);
      if (sp) select_channel(sp, 0); 
    }
}


char *view_files_find_any_directory(void)
{
  /* find any active directory in any vf dialog */
  if (view_files_info_size > 0)
    {
      int j;
      for (j = 0; j < view_files_info_size; j++)
	{
	  view_files_info *vdat;
	  vdat = view_files_infos[j];
	  if ((vdat) && 
	      (vdat->dir_names))
	    {
	      int i;
	      for (i = 0; i < vdat->dirs_size; i++)
		if (vdat->dir_names[i])
		  return(vdat->dir_names[i]);
	    }
	}
    }
  return(NULL);
}



static Xen mouse_enter_label_hook;
static Xen mouse_leave_label_hook;

static char *vf_row_get_label(void *ur)
{
  vf_row *r = (vf_row *)ur;
  return(((view_files_info *)(r->vdat))->full_names[r->pos]);
}


static int vf_row_get_pos(void *ur)
{
  vf_row *r = (vf_row *)ur;
  return(r->pos);
}


static void mouse_enter_or_leave_label(void *r, int type, Xen hook, const char *caller)
{
  if ((r) &&
      (Xen_hook_has_list(hook)))
    {
      char *label = NULL;
      bool need_free = false;
      if (type == FILE_VIEWER)
	label = vf_row_get_label(r);
      else
	{
	  label = regrow_get_label(r);
	  if (label) need_free = true;
	}
      if (label)
	run_hook(hook,
		 Xen_list_3(C_int_to_Xen_integer(type),
			    C_int_to_Xen_integer((type == FILE_VIEWER) ? (vf_row_get_pos(r)) : (regrow_get_pos(r))),
			    C_string_to_Xen_string(label)),
		 caller);
      if (need_free) XtFree(label);
    }
}


void mouse_leave_label(void *r, int type)
{
  mouse_enter_or_leave_label(r, type, mouse_leave_label_hook, S_mouse_leave_label_hook);
}


void mouse_enter_label(void *r, int type)
{
  mouse_enter_or_leave_label(r, type, mouse_enter_label_hook, S_mouse_enter_label_hook);
}


static void vf_mouse_enter_label(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  mouse_enter_label(context, FILE_VIEWER);
}


static void vf_mouse_leave_label(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  mouse_leave_label(context, FILE_VIEWER);
}


static vf_row *make_vf_row(view_files_info *vdat, 
			   Widget last_row, 
			   XtCallbackProc play_callback, XtCallbackProc name_callback)
{
  int n;
  Arg args[32];
  vf_row *r;
  XmString s1;
#if WITH_AUDIO
  XtCallbackList n1;
#endif
  XtCallbackList n3;

  s1 = XmStringCreateLocalized((char *)"");
  r = (vf_row *)calloc(1, sizeof(vf_row));
  r->vdat = (void *)vdat;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, (last_row) ? XmATTACH_WIDGET : XmATTACH_FORM); n++;
  if (last_row) {XtSetArg(args[n], XmNtopWidget, last_row); n++;}
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNheight, 18); n++; 
  r->rw = XtCreateWidget("rw", xmFormWidgetClass, vdat->file_list_holder, args, n);

#if WITH_AUDIO
  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
  XtSetArg(args[n], XmNlabelString, s1); n++;
  XtSetArg(args[n], XmNvalueChangedCallback, n1 = make_callback_list(play_callback, (XtPointer)r)); n++;
  if (ss->toggle_size > 0) {XtSetArg(args[n], XmNindicatorSize, ss->toggle_size); n++;}
  XtSetArg(args[n], XmNmarginWidth, 8); n++;
  r->pl = make_togglebutton_widget("pl", r->rw, args, n);
#endif

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
#if WITH_AUDIO
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, r->pl); n++;
#else
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
#endif
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNshadowThickness, 0); n++;
  XtSetArg(args[n], XmNhighlightThickness, 0); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
  XtSetArg(args[n], XmNfillOnArm, false); n++;
  XtSetArg(args[n], XmNrecomputeSize, false); n++;
  XtSetArg(args[n], XmNwidth, 500); n++;                /* this sets the max name length indirectly -- was 300 which truncates some long file names (29-Oct-07) */
  XtSetArg(args[n], XmNactivateCallback, n3 = make_callback_list(name_callback, (XtPointer)r)); n++;
  r->nm = XtCreateManagedWidget("nm", xmPushButtonWidgetClass, r->rw, args, n);
  XmStringFree(s1);

  XtAddEventHandler(r->nm, EnterWindowMask, false, vf_mouse_enter_label, (XtPointer)r);
  XtAddEventHandler(r->nm, LeaveWindowMask, false, vf_mouse_leave_label, (XtPointer)r);

#if WITH_AUDIO
  free(n1);
#endif
  free(n3);
  return(r);
}


static void vf_unhighlight_row(widget_t nm, widget_t rw)
{
  XtVaSetValues(rw, XmNbackground, ss->highlight_color, NULL);
  XtVaSetValues(nm, XmNbackground, ss->highlight_color, NULL);
}


static void vf_highlight_row(widget_t nm, widget_t rw)
{
  XtVaSetValues(rw, XmNbackground, ss->zoom_color, NULL);
  XtVaSetValues(nm, XmNbackground, ss->zoom_color, NULL);
}


typedef struct {
  vf_row *r;
  color_t old_color;
} vf_flash_data;


static void vf_unflash_row(XtPointer data, XtIntervalId *id)
{
  vf_flash_data *v = (vf_flash_data *)data;
  XtVaSetValues(v->r->rw, XmNbackground, v->old_color, NULL);
  XtVaSetValues(v->r->nm, XmNbackground, v->old_color, NULL);
  free(v);
}


static void vf_flash_row(vf_row *r)
{
  vf_flash_data *v;
  v = (vf_flash_data *)calloc(1, sizeof(vf_flash_data));
  v->r = r;
  XtVaGetValues(r->rw, XmNbackground, &(v->old_color), NULL);
  XtVaSetValues(r->rw, XmNbackground, ss->light_blue, NULL);
  XtVaSetValues(r->nm, XmNbackground, ss->light_blue, NULL);
  XtAppAddTimeOut(main_app(ss),
		  500,
		  (XtTimerCallbackProc)vf_unflash_row,
		  (void *)v);
}


static void vf_post_info(view_files_info *vdat, int pos)
{
  char *title;
  XmString s3;
  title = mus_format("%s:", vdat->names[pos]);
  s3 = XmStringCreateLocalized(title);
  XtVaSetValues(vdat->left_title,
		XmNlabelString, s3,
		NULL);
  XmStringFree(s3);
  free(title);
  post_sound_info(vdat->info1, vdat->info2, vdat->full_names[pos], false);
}


static void vf_post_selected_files_list(view_files_info *vdat)
{
  int len;
  char *msg1 = NULL, *msg2 = NULL, *title;
  XmString s1, s2, s3;
  len = vdat->currently_selected_files;

  title = mus_strdup("selected files:");
  s3 = XmStringCreateLocalized(title);
  XtVaSetValues(vdat->left_title,
		XmNlabelString, s3,
		NULL);
  XmStringFree(s3);
  free(title);

  if (len == 2)
    {
      msg1 = mus_strdup(vdat->names[vdat->selected_files[0]]);
      msg2 = mus_strdup(vdat->names[vdat->selected_files[1]]);
    }
  else
    {
      if (len == 3)
	{
	  msg1 = mus_format("%s, %s", vdat->names[vdat->selected_files[0]], vdat->names[vdat->selected_files[1]]);
	  msg2 = mus_strdup(vdat->names[vdat->selected_files[2]]);
	}
      else
	{
	  msg1 = mus_format("%s, %s", vdat->names[vdat->selected_files[0]], vdat->names[vdat->selected_files[1]]);
	  msg2 = mus_format("%s, %s%s", vdat->names[vdat->selected_files[2]], vdat->names[vdat->selected_files[3]],
			    (len == 4) ? "" : "...");
	}
    }

  s1 = XmStringCreateLocalized(msg1);
  s2 = XmStringCreateLocalized(msg2);
  XtVaSetValues(vdat->info1, XmNlabelString, s1, NULL);
  XtVaSetValues(vdat->info2, XmNlabelString, s2, NULL);
  XmStringFree(s1);
  XmStringFree(s2);
  free(msg1);
  free(msg2);
}


static void vf_unpost_info(view_files_info *vdat)
{
  XmString s1, s2, s3;
  char *title;

  title = mus_strdup("(no files selected)");
  s3 = XmStringCreateLocalized(title);
  XtVaSetValues(vdat->left_title,
		XmNlabelString, s3,
		NULL);
  XmStringFree(s3);
  free(title);

  s1 = XmStringCreateLocalized((char *)"|");
  s2 = XmStringCreateLocalized((char *)"|");
  XtVaSetValues(vdat->info1, XmNlabelString, s1, NULL);
  XtVaSetValues(vdat->info2, XmNlabelString, s2, NULL);
  XmStringFree(s1);
  XmStringFree(s2);
}


static void view_files_select_callback(Widget w, XtPointer context, XtPointer info) 
{
  static oclock_t mouse_down_time = 0;
  XmPushButtonCallbackStruct *cb = (XmPushButtonCallbackStruct *)info;
  XButtonEvent *ev;
  ev = (XButtonEvent *)(cb->event);

  /* cb->click_count is always 1 so we can't detect a double-click that way
   *   ss->click_time has the (very short!) multiclick time
   */

  if (mouse_down_time != 0)
    {
      if ((ev->time - mouse_down_time) < ss->click_time) /* open file if double clicked */
	{
	  mouse_down_time = ev->time;
	  view_files_open_selected_files((view_files_info *)(((vf_row *)context)->vdat));
	  return;
	}
    }
  mouse_down_time = ev->time;
  view_files_select((vf_row *)context, ev->state & ShiftMask);
}


static void view_files_play_callback(Widget w, XtPointer context, XtPointer info) 
{
#if WITH_AUDIO
  /* open and play -- close at end or when button off toggled */
  vf_row *r = (vf_row *)context;
  view_files_info *vdat;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  vdat = (view_files_info *)(r->vdat);
  if (view_files_play(vdat, r->pos, cb->set))
    XmToggleButtonSetState(w, false, false);
  else vdat->current_play_button = w;
#endif
}


static vf_row *view_files_make_row(view_files_info *vdat, widget_t last_row)
{
  return(make_vf_row(vdat, last_row, view_files_play_callback, view_files_select_callback));
}


static void view_files_help_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_dialog_help();
}


static void view_files_quit_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  XtUnmanageChild(vdat->dialog);
}


static void view_files_new_viewer_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  if ((vdat) && 
      (vdat->dialog) &&
      (XtIsManaged(vdat->dialog)) &&
      (XmGetFocusWidget(vdat->dialog) == XmMessageBoxGetChild(vdat->dialog, XmDIALOG_OK_BUTTON)))
    {
      Position x = 0, y = 0;

      /* jog the current one over a bit -- otherwise the new one lands exactly on top of the old! */
      XtVaGetValues(vdat->dialog, XmNx, &x, XmNy, &y, NULL);
      XtVaSetValues(vdat->dialog, XmNx, x + 30, XmNy, y - 30, NULL);

      vdat = new_view_files_dialog();
      make_view_files_dialog_1(vdat, true);
    }
}


static void sort_vf(view_files_info *vdat, int sort_choice)
{
  vdat->sorter = sort_choice;
  vf_reflect_sort_choice_in_menu(vdat);
  view_files_display_list(vdat);
}


static void sort_view_files_a_to_z(Widget w, XtPointer context, XtPointer info) 
{
  sort_vf((view_files_info *)context, SORT_A_TO_Z);
}


static void sort_view_files_z_to_a(Widget w, XtPointer context, XtPointer info) 
{
  sort_vf((view_files_info *)context, SORT_Z_TO_A);
}


static void sort_view_files_new_to_old(Widget w, XtPointer context, XtPointer info) 
{
  sort_vf((view_files_info *)context, SORT_NEW_TO_OLD);
}


static void sort_view_files_old_to_new(Widget w, XtPointer context, XtPointer info) 
{
  sort_vf((view_files_info *)context, SORT_OLD_TO_NEW);
}


static void sort_view_files_big_to_small(Widget w, XtPointer context, XtPointer info) 
{
  sort_vf((view_files_info *)context, SORT_BIG_TO_SMALL);
}


static void sort_view_files_small_to_big(Widget w, XtPointer context, XtPointer info) 
{
  sort_vf((view_files_info *)context, SORT_SMALL_TO_BIG);
}


static void sort_view_files_xen(Widget w, XtPointer context, XtPointer info) 
{
  intptr_t index;
  XtVaGetValues(w, XmNuserData, &index, NULL); /* index is location in list of file-sorters */
  sort_vf((view_files_info *)context, (int)index);
}


static void vf_reflect_sort_choice_in_menu(view_files_info *vdat)
{
  int i;
  set_sensitive(vdat->a_to_z, vdat->sorter != SORT_A_TO_Z);
  set_sensitive(vdat->z_to_a, vdat->sorter != SORT_Z_TO_A);
  set_sensitive(vdat->new_to_old, vdat->sorter != SORT_NEW_TO_OLD);
  set_sensitive(vdat->old_to_new, vdat->sorter != SORT_OLD_TO_NEW);
  set_sensitive(vdat->small_to_big, vdat->sorter != SORT_SMALL_TO_BIG);
  set_sensitive(vdat->big_to_small, vdat->sorter != SORT_BIG_TO_SMALL);
  for (i = 0; i < vdat->sort_items_size; i++)
    if (XtIsManaged(vdat->sort_items[i]))
      set_sensitive(vdat->sort_items[i], vdat->sorter != (SORT_XEN + i));
}


static void view_files_add_file_or_directory(view_files_info *vdat, const char *file_or_dir)
{
  char *filename;
  filename = mus_expand_filename((const char *)file_or_dir);
  if (filename)
    {
      int len;
      len = strlen(filename);
      if (filename[len - 1] == '*')
	filename[len - 1] = 0;
      if (is_directory(filename))
	add_directory_to_view_files_list(vdat, (const char *)filename);
      else add_file_to_view_files_list(vdat, file_or_dir, filename);
      free(filename);
    }
}


static void view_files_add_files(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  char *file_or_dir;
  file_or_dir = XmTextFieldGetString(w);
  if ((file_or_dir) && (*file_or_dir))
    {
      view_files_add_file_or_directory(vdat, (const char *)file_or_dir);
      XtFree(file_or_dir);
      view_files_display_list(vdat);
    }
}


static void view_files_drop_watcher(Widget w, const char *str, Position x, Position y, void *context)
{
  view_files_info *vdat = (view_files_info *)context;
  /* incoming str is a single filename (drop watcher code splits the possible list and calls us on each one) */
  view_files_add_file_or_directory(vdat, str);
  view_files_display_list(vdat);
}


static void view_files_drag_watcher(Widget w, const char *str, Position x, Position y, drag_style_t dtype, void *context)
{
  view_files_info *vdat = (view_files_info *)context;
  switch (dtype)
    {
    case DRAG_ENTER:
      XmChangeColor(vdat->file_list, ss->selection_color);
      break;

    case DRAG_LEAVE:
      XmChangeColor(vdat->file_list, ss->basic_color);
      break;

    default:
      break;
    }
}


static mus_long_t vf_location(view_files_info *vdat)
{
  mus_long_t pos = 0;
  snd_info *sp;
  chan_info *cp;
  char *str;
  switch (vdat->location_choice)
    {
    case VF_AT_CURSOR:
      sp = any_selected_sound();
      if (sp)
	{
	  cp = any_selected_channel(sp);
	  return(cursor_sample(cp));
	}
      break;

    case VF_AT_END:
      sp = any_selected_sound();
      if (sp)
	{
	  cp = any_selected_channel(sp);
	  return(current_samples(cp));
	}
      break;

    case VF_AT_BEGINNING:
      return(0);
      break;

    case VF_AT_MARK:
      str = XmTextGetString(vdat->at_mark_text);
      if ((str) && (*str))
	{
	  pos = mark_id_to_sample(string_to_int(str, 0, "mark"));
	  XtFree(str);
	  if (pos < 0)
	    snd_error_without_format("no such mark");
	}
      else snd_error_without_format("no mark?");
      break;

    case VF_AT_SAMPLE:
      str = XmTextGetString(vdat->at_sample_text);
      if ((str) && (*str))
	{
	  pos = string_to_mus_long_t(str, 0, "sample"); 
	  XtFree(str);
	  /* pos already checked for lower bound */
	}
      else snd_error_without_format("no sample number?");
      break;
    }
  return(pos);
}


static void vf_clear_sample(view_files_info *vdat);

static void vf_sample_button_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  vf_clear_sample((view_files_info *)context);
} 


static void vf_sample_text_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)info;
  vf_clear_sample((view_files_info *)context);
  cbs->doit = true;
} 


static void vf_clear_sample(view_files_info *vdat)
{
  vf_clear_error(vdat);
  XtRemoveCallback(vdat->at_sample_text, XmNmodifyVerifyCallback, vf_sample_text_modify_callback, (XtPointer)vdat);
  XtRemoveCallback(vdat->at_sample_button, XmNvalueChangedCallback, vf_sample_button_modify_callback, (XtPointer)vdat);
}


static void vf_clear_mark(view_files_info *vdat);

static void vf_mark_button_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  vf_clear_mark((view_files_info *)context);
}


static void vf_mark_text_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)info;
  vf_clear_mark((view_files_info *)context);
  cbs->doit = true;
}


static void vf_clear_mark(view_files_info *vdat)
{
  vf_clear_error(vdat);
  XtRemoveCallback(vdat->at_mark_text, XmNmodifyVerifyCallback, vf_mark_text_modify_callback, (XtPointer)vdat);
  XtRemoveCallback(vdat->at_mark_button, XmNvalueChangedCallback, vf_mark_button_modify_callback, (XtPointer)vdat);
}


static void vf_add_text_modify_callback(Widget w, XtPointer context, XtPointer info);

static void remove_all_pending_clear_callbacks(view_files_info *vdat)
{
  /* docs say this is a no-op if the indicated callback does not exist (i.e. not an error or segfault) */
  XtRemoveCallback(vdat->at_mark_text, XmNmodifyVerifyCallback, vf_mark_text_modify_callback, (XtPointer)vdat);
  XtRemoveCallback(vdat->at_mark_button, XmNvalueChangedCallback, vf_mark_button_modify_callback, (XtPointer)vdat);
  XtRemoveCallback(vdat->at_sample_text, XmNmodifyVerifyCallback, vf_sample_text_modify_callback, (XtPointer)vdat);
  XtRemoveCallback(vdat->at_sample_button, XmNvalueChangedCallback, vf_sample_button_modify_callback, (XtPointer)vdat);
  XtRemoveCallback(vdat->add_text, XmNmodifyVerifyCallback, vf_add_text_modify_callback, (XtPointer)vdat);
}


static void vf_post_error(const char *error_msg, view_files_info *vdat)
{
  XmString msg;
  remove_all_pending_clear_callbacks(vdat);
  vdat->has_error = true;
  msg = XmStringCreateLocalized((char *)error_msg);
  XtVaSetValues(vdat->info1,
		XmNlabelString, msg, 
		NULL);
  XmStringFree(msg);
  msg = XmStringCreateLocalized((char *)"");
  XtVaSetValues(vdat->info2,
		XmNlabelString, msg, 
		NULL);
  XmStringFree(msg);
}


static void redirect_vf_post_error(const char *error_msg, void *vdat)
{
  vf_post_error(error_msg, (view_files_info *)vdat);
}


static void redirect_vf_post_location_error(const char *error_msg, void *data)
{
  view_files_info *vdat = (view_files_info *)data;
  vf_post_error(error_msg, vdat);
  if (vdat->location_choice == VF_AT_SAMPLE)
    {
      /* watch at_sample_text or button (undo) */
      XtAddCallback(vdat->at_sample_text, XmNmodifyVerifyCallback, vf_sample_text_modify_callback, (XtPointer)vdat);
      XtAddCallback(vdat->at_sample_button, XmNvalueChangedCallback, vf_sample_button_modify_callback, (XtPointer)vdat);
    }
  else
    {
      /* watch at_mark_text or button */
      XtAddCallback(vdat->at_mark_text, XmNmodifyVerifyCallback, vf_mark_text_modify_callback, (XtPointer)vdat);
      XtAddCallback(vdat->at_mark_button, XmNvalueChangedCallback, vf_mark_button_modify_callback, (XtPointer)vdat);
    }
}


static void vf_add_text_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)info;
  view_files_info *vdat = (view_files_info *)context;
  vf_clear_error(vdat);
  XtRemoveCallback(vdat->add_text, XmNmodifyVerifyCallback, vf_add_text_modify_callback, (XtPointer)vdat);
  cbs->doit = true;
}


static void vf_post_add_error(const char *error_msg, view_files_info *vdat)
{
  vf_post_error(error_msg, vdat);
  XtAddCallback(vdat->add_text, XmNmodifyVerifyCallback, vf_add_text_modify_callback, (XtPointer)vdat);
  /* what about other clearing actions? */
}


static void view_files_mix_selected_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_mix_selected_files(w, (view_files_info *)context);
}


static void view_files_insert_selected_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_insert_selected_files(w, (view_files_info *)context);
}


static void view_files_at_cursor_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  if (vdat->has_error)
    {
      if (vdat->location_choice == VF_AT_SAMPLE)
	vf_clear_sample(vdat);
      else vf_clear_mark(vdat);
    }
  XmToggleButtonSetState(vdat->at_cursor_button, true, false);
  XmToggleButtonSetState(vdat->at_end_button, false, false);
  XmToggleButtonSetState(vdat->at_beginning_button, false, false);
  XmToggleButtonSetState(vdat->at_mark_button, false, false);
  XmToggleButtonSetState(vdat->at_sample_button, false, false);
  vdat->location_choice = VF_AT_CURSOR;
}


static void view_files_at_end_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  if (vdat->has_error)
    {
      if (vdat->location_choice == VF_AT_SAMPLE)
	vf_clear_sample(vdat);
      else vf_clear_mark(vdat);
    }
  XmToggleButtonSetState(vdat->at_cursor_button, false, false);
  XmToggleButtonSetState(vdat->at_end_button, true, false);
  XmToggleButtonSetState(vdat->at_beginning_button, false, false);
  XmToggleButtonSetState(vdat->at_mark_button, false, false);
  XmToggleButtonSetState(vdat->at_sample_button, false, false);
  vdat->location_choice = VF_AT_END;
}


static void view_files_at_beginning_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  if (vdat->has_error)
    {
      if (vdat->location_choice == VF_AT_SAMPLE)
	vf_clear_sample(vdat);
      else vf_clear_mark(vdat);
    }
  XmToggleButtonSetState(vdat->at_cursor_button, false, false);
  XmToggleButtonSetState(vdat->at_end_button, false, false);
  XmToggleButtonSetState(vdat->at_beginning_button, true, false);
  XmToggleButtonSetState(vdat->at_mark_button, false, false);
  XmToggleButtonSetState(vdat->at_sample_button, false, false);
  vdat->location_choice = VF_AT_BEGINNING;
}


static void view_files_at_sample_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  if ((vdat->has_error) && 
      (vdat->location_choice == VF_AT_MARK))
      vf_clear_mark(vdat);
  XmToggleButtonSetState(vdat->at_cursor_button, false, false);
  XmToggleButtonSetState(vdat->at_end_button, false, false);
  XmToggleButtonSetState(vdat->at_beginning_button, false, false);
  XmToggleButtonSetState(vdat->at_mark_button, false, false);
  XmToggleButtonSetState(vdat->at_sample_button, true, false);
  vdat->location_choice = VF_AT_SAMPLE;
}


static void view_files_at_mark_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  if ((vdat->has_error) &&
      (vdat->location_choice == VF_AT_SAMPLE))
    vf_clear_sample(vdat);
  XmToggleButtonSetState(vdat->at_cursor_button, false, false);
  XmToggleButtonSetState(vdat->at_end_button, false, false);
  XmToggleButtonSetState(vdat->at_beginning_button, false, false);
  XmToggleButtonSetState(vdat->at_mark_button, true, false);
  XmToggleButtonSetState(vdat->at_sample_button, false, false);
  vdat->location_choice = VF_AT_MARK;
}



/* -------- speed -------- */

static int vf_speed_to_scroll(mus_float_t minval, mus_float_t val, mus_float_t maxval)
{
  if (val <= minval) return(0);
  if (val >= maxval) return((int)(0.9 * SCROLLBAR_MAX));
  return(snd_round(0.9 * SCROLLBAR_MAX * ((log(val) - log(minval)) / (log(maxval) - log(minval)))));
}


static void vf_set_speed(view_files_info *vdat, mus_float_t val)
{
  char speed_number_buffer[6];
  vdat->speed = speed_changed(val,
			      speed_number_buffer,
			      vdat->speed_style,
			      speed_control_tones(ss),
			      6);
  set_label(vdat->speed_number, speed_number_buffer);
  XtVaSetValues(vdat->speed_scrollbar, 
		XmNvalue, vf_speed_to_scroll(speed_control_min(ss), val, speed_control_max(ss)), 
		NULL);
}


static void vf_speed_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  vf_set_speed(vdat, 1.0);
  XtVaSetValues(vdat->speed_scrollbar, 
		XmNvalue, vf_speed_to_scroll(speed_control_min(ss), 1.0, speed_control_max(ss)), 
		NULL);
}


static void vf_speed_label_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  char speed_number_buffer[6];
  view_files_info *vdat = (view_files_info *)context;

  switch (vdat->speed_style)
    {
    default:
    case SPEED_CONTROL_AS_FLOAT:    vdat->speed_style = SPEED_CONTROL_AS_RATIO;    break;
    case SPEED_CONTROL_AS_RATIO:    vdat->speed_style = SPEED_CONTROL_AS_SEMITONE; break;
    case SPEED_CONTROL_AS_SEMITONE: vdat->speed_style = SPEED_CONTROL_AS_FLOAT;    break;
    }
  speed_changed(vdat->speed,
		speed_number_buffer,
		vdat->speed_style,
		speed_control_tones(ss),
		6);
  set_label(vdat->speed_number, speed_number_buffer);
}


static void vf_speed_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  XmScrollBarCallbackStruct *cb = (XmScrollBarCallbackStruct *)info;
  vf_set_speed(vdat, exp((cb->value * 
			  (log(speed_control_max(ss)) - log(speed_control_min(ss))) / 
			  (0.9 * SCROLLBAR_MAX)) + log(speed_control_min(ss))));
}


static void vf_speed_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  XmScrollBarCallbackStruct *cb = (XmScrollBarCallbackStruct *)info;
  vf_set_speed(vdat, exp((cb->value * 
			  (log(speed_control_max(ss)) - log(speed_control_min(ss))) / 
			  (0.9 * SCROLLBAR_MAX)) + log(speed_control_min(ss))));
}



/* -------- amp -------- */

static mus_float_t vf_scroll_to_amp(int val)
{
  if (val <= 0) 
    return(amp_control_min(ss));
  if (val >= (0.9 * SCROLLBAR_MAX)) 
    return(amp_control_max(ss));
  if (val > (0.5 * 0.9 * SCROLLBAR_MAX))
    return((((val / (0.5 * 0.9 * SCROLLBAR_MAX)) - 1.0) * (amp_control_max(ss) - 1.0)) + 1.0);
  else return((val * (1.0 - amp_control_min(ss)) / (0.5 * 0.9 * SCROLLBAR_MAX)) + amp_control_min(ss));
}


static int vf_amp_to_scroll(mus_float_t amp)
{
  return(amp_to_scroll(amp_control_min(ss), amp, amp_control_max(ss)));
}


static void vf_set_amp(view_files_info *vdat, mus_float_t val)
{
  char sfs[6];
  vdat->amp = val;
  snprintf(sfs, 6, "%.2f", val);
  set_label(vdat->amp_number, sfs);
  XtVaSetValues(vdat->amp_scrollbar, 
		XmNvalue, amp_to_scroll(amp_control_min(ss), val, amp_control_max(ss)), 
		NULL);
}


static void vf_amp_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  vf_set_amp((view_files_info *)context, 1.0);
}


static void vf_amp_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  vf_set_amp((view_files_info *)context, 
	     vf_scroll_to_amp(((XmScrollBarCallbackStruct *)info)->value));
}


static void vf_amp_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  vf_set_amp((view_files_info *)context, 
	     vf_scroll_to_amp(((XmScrollBarCallbackStruct *)info)->value));
}




/* -------- amp-envs -------- */

static void vf_amp_env_resize(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  if (!vdat->env_ax)
    {
      XGCValues gv;
      gv.function = GXcopy;
      XtVaGetValues(vdat->env_drawer, XmNbackground, &gv.background, XmNforeground, &gv.foreground, NULL);
      vdat->env_gc = XtGetGC(vdat->env_drawer, GCForeground | GCFunction, &gv);
      vdat->env_ax = (graphics_context *)calloc(1, sizeof(graphics_context));
      vdat->env_ax->wn = XtWindow(vdat->env_drawer);
      vdat->env_ax->dp = XtDisplay(vdat->env_drawer);
      vdat->env_ax->gc = vdat->env_gc;
      if (!(vdat->env_ax->wn)) return;
    }
  else 
    {
      if (!(vdat->env_ax->wn))
	{
	  vdat->env_ax->wn = XtWindow(vdat->env_drawer); /* sometimes the dialog window is not ready when display_env gets called */
	  if (!(vdat->env_ax->wn)) return;
	}
      clear_window(vdat->env_ax);
    }
  vdat->spf->with_dots = true;
  env_editor_display_env(vdat->spf, vdat->amp_env, vdat->env_ax, "amp env", 
			 0, 0,
			 widget_width(w), widget_height(w), 
			 NOT_PRINTING);
  /* it might be nice to show the sound data in the background, but there are
   *   complications involving multichannel and multiselection cases, also
   *   how to get the "peak-func" and how to call g_channel_amp_envs.
   * Too many problems...
   *   but perhaps something like the region browser display would work:
   *   label saying file+chan and up/down arrows to see the rest + off button
   */
}


static void vf_amp_env_redraw(Widget w, view_files_info *vdat)
{
  vf_amp_env_resize(w, (void *)vdat, NULL);
}


static void vf_drawer_button_motion(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  view_files_info *vdat = (view_files_info *)context;
  XMotionEvent *ev = (XMotionEvent *)event;
  /* mus_float_t pos; */

#ifdef __APPLE__
  if ((press_x == ev->x) && (press_y == ev->y)) return;
#endif

  /* pos = (mus_float_t)(ev->x) / (mus_float_t)widget_width(w); */
  env_editor_button_motion(vdat->spf, ev->x, ev->y, ev->time, vdat->amp_env);
  vf_amp_env_resize(w, context, NULL);
}


static void vf_drawer_button_press(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  view_files_info *vdat = (view_files_info *)context;
  XButtonEvent *ev = (XButtonEvent *)event;
  /* mus_float_t pos; */

#ifdef __APPLE__
  press_x = ev->x;
  press_y = ev->y;
#endif

  /* pos = (mus_float_t)(ev->x) / (mus_float_t)widget_width(w); */
  if (env_editor_button_press(vdat->spf, ev->x, ev->y, ev->time, vdat->amp_env))
    vf_amp_env_resize(w, context, NULL);
}


static void vf_drawer_button_release(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  view_files_info *vdat = (view_files_info *)context;
  /* XButtonEvent *ev = (XButtonEvent *)event; */
  /* mus_float_t pos; */

  /* pos = (mus_float_t)(ev->x) / (mus_float_t)widget_width(w); */
  env_editor_button_release(vdat->spf, vdat->amp_env);
  vf_amp_env_resize(w, context, NULL);
}


static void vf_set_amp_env(view_files_info *vdat, env *new_e)
{
  if (!vdat) return;
  if (vdat->amp_env) free_env(vdat->amp_env);
  vdat->amp_env = copy_env(new_e);
  if ((vdat->dialog) &&
      (widget_is_active(vdat->dialog)))
    vf_amp_env_redraw(vdat->env_drawer, vdat);
}


static void blue_textfield_unfocus_callback(Widget w, XtPointer context, XtPointer info)
{
  XtVaSetValues(w, XmNbackground, ss->lighter_blue, NULL);
  XtVaSetValues(w, XmNcursorPositionVisible, false, NULL);
}


static void blue_mouse_leave_text_callback(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  XtVaSetValues(w, XmNbackground, ss->lighter_blue, NULL);
  XtVaSetValues(w, XmNcursorPositionVisible, false, NULL);
}


static void white_mouse_enter_text_callback(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  XtVaSetValues(w, XmNbackground, ss->text_focus_color, NULL);
  XtVaSetValues(w, XmNcursorPositionVisible, true, NULL);
}


static void view_files_reset_callback(Widget w, XtPointer context, XtPointer info) 
{
  view_files_info *vdat = (view_files_info *)context;
  env *e;
  vf_set_amp(vdat, 1.0);
  vf_set_speed(vdat, 1.0);
  vf_set_amp_env(vdat, e = default_env(1.0, 1.0)); /* vf_set_amp_env copies the envelope */
  free_env(e);
  sort_vf(vdat, view_files_sort(ss));
}



static widget_t make_view_files_dialog_1(view_files_info *vdat, bool managed)
{
  if (!(vdat->dialog))
    {
      int i, n;
      Arg args[20];
      XmString go_away, xhelp, titlestr, new_viewer_str, s1, bstr;
      Widget mainform, viewform, leftform, reset_button, new_viewer_button;
      Widget left_title_sep, add_label, sep1, sep3, sep4, sep6, sep7;
#if WITH_AUDIO
      Widget plw;
#endif
      Widget rlw, sbar;
      XtCallbackList n1, n2, n3, n4;
      Widget amp_label, speed_label, env_frame;
      Widget bframe, bform;

      go_away = XmStringCreateLocalized((char *)I_GO_AWAY);
      xhelp = XmStringCreateLocalized((char *)I_HELP);
      new_viewer_str = XmStringCreateLocalized((char *)"New Viewer");

      {
	char *filestr = NULL;
	filestr = mus_format("%s %d", "Files", vdat->index + 1);
	titlestr = XmStringCreateLocalized(filestr);
	free(filestr);
      }

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNhelpLabelString, xhelp); n++;
      XtSetArg(args[n], XmNokLabelString, new_viewer_str); n++;
      XtSetArg(args[n], XmNcancelLabelString, go_away); n++;
      XtSetArg(args[n], XmNautoUnmanage, false); n++;
      XtSetArg(args[n], XmNdialogTitle, titlestr); n++;
      XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
      XtSetArg(args[n], XmNnoResize, false); n++;
      XtSetArg(args[n], XmNtransient, false); n++;
      vdat->dialog = XmCreateTemplateDialog(main_shell(ss), (char *)"Files", args, n);
      new_viewer_button = MSG_BOX(vdat->dialog, XmDIALOG_OK_BUTTON);

      XtAddCallback(vdat->dialog, XmNhelpCallback,   view_files_help_callback,       (XtPointer)vdat);
      /* XtAddCallback(vdat->dialog, XmNokCallback,     view_files_new_viewer_callback,    (XtPointer)vdat); */
      XtAddCallback(new_viewer_button, XmNactivateCallback, view_files_new_viewer_callback, (XtPointer)vdat);
      XtAddCallback(vdat->dialog, XmNcancelCallback, view_files_quit_callback, (XtPointer)vdat);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      reset_button = XtCreateManagedWidget("Reset", xmPushButtonGadgetClass, vdat->dialog, args, n);
      XtAddCallback(reset_button, XmNactivateCallback, view_files_reset_callback, (XtPointer)vdat);

      XmStringFree(xhelp);
      XmStringFree(go_away);
      XmStringFree(titlestr);
      XmStringFree(new_viewer_str);

      XtVaSetValues(MSG_BOX(vdat->dialog, XmDIALOG_OK_BUTTON),     XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(vdat->dialog, XmDIALOG_HELP_BUTTON),   XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(vdat->dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor,   ss->selection_color,  NULL);
      XtVaSetValues(MSG_BOX(vdat->dialog, XmDIALOG_OK_BUTTON),     XmNbackground, ss->highlight_color,   NULL);
      XtVaSetValues(MSG_BOX(vdat->dialog, XmDIALOG_HELP_BUTTON),   XmNbackground, ss->highlight_color,   NULL);
      XtVaSetValues(MSG_BOX(vdat->dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color,  NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, MSG_BOX(vdat->dialog, XmDIALOG_SEPARATOR)); n++;
      XtSetArg(args[n], XmNsashIndent, 2); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNspacing, 24); n++;
      XtSetArg(args[n], XmNpaneMaximum, LOTSA_PIXELS); n++; 
      mainform = XtCreateManagedWidget("formd", xmPanedWindowWidgetClass, vdat->dialog, args, n);

      /* -------- left side controls -------- */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      leftform = XtCreateManagedWidget("leftform", xmFormWidgetClass, mainform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;	
      vdat->left_title = XtCreateManagedWidget("(no files selected)", xmLabelWidgetClass, leftform, args, n);
      
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->white); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, vdat->left_title); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNseparatorType, XmDOUBLE_LINE); n++;
      left_title_sep = XtCreateManagedWidget("sep", xmSeparatorWidgetClass, leftform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, left_title_sep); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      vdat->info1 = XtCreateManagedWidget("|", xmLabelWidgetClass, leftform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, vdat->info1); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      vdat->info2 = XtCreateManagedWidget("|", xmLabelWidgetClass, leftform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, vdat->info2); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNheight, 8); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      sep6 = XtCreateManagedWidget("dialog-sep1", xmSeparatorWidgetClass, leftform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->zoom_color); n++;
      XtSetArg(args[n], XmNborderColor, ss->zoom_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep6); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNborderWidth, 2); n++;
      bframe = XtCreateManagedWidget("bframe", xmFrameWidgetClass, leftform, args, n);      

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->position_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      bform = XtCreateManagedWidget("bform", xmFormWidgetClass, bframe, args, n);      

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNmarginTop, 0); n++;
      XtSetArg(args[n], XmNmarginBottom, 0); n++;
      XtSetArg(args[n], XmNshadowThickness, 1); n++;
      XtSetArg(args[n], XmNhighlightThickness, 1); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 50); n++;
      vdat->mixB = XtCreateManagedWidget("Mix", xmPushButtonWidgetClass, bform, args, n);
      XtAddCallback(vdat->mixB, XmNactivateCallback, view_files_mix_selected_callback, (XtPointer)vdat);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNmarginTop, 0); n++;
      XtSetArg(args[n], XmNmarginBottom, 0); n++;
      XtSetArg(args[n], XmNshadowThickness, 1); n++;
      XtSetArg(args[n], XmNhighlightThickness, 1); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, vdat->mixB); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      vdat->insertB = XtCreateManagedWidget("Insert", xmPushButtonWidgetClass, bform, args, n);
      XtAddCallback(vdat->insertB, XmNactivateCallback, view_files_insert_selected_callback, (XtPointer)vdat);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"at cursor");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, vdat->mixB); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY); n++;
      XtSetArg(args[n], XmNset, XmSET); n++;
      vdat->at_cursor_button = make_togglebutton_widget("at-cursor-button", bform, args, n);
      XtAddCallback(vdat->at_cursor_button, XmNdisarmCallback, view_files_at_cursor_callback, (XtPointer)vdat);
      XmStringFree(bstr);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"at end");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, vdat->at_cursor_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY); n++;
      vdat->at_end_button = make_togglebutton_widget("at-end-button", bform, args, n);
      XtAddCallback(vdat->at_end_button, XmNdisarmCallback, view_files_at_end_callback, (XtPointer)vdat);
      XmStringFree(bstr);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"at beginning");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, vdat->at_end_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY); n++;
      vdat->at_beginning_button = make_togglebutton_widget("at-beginning-button", bform, args, n);
      XtAddCallback(vdat->at_beginning_button, XmNdisarmCallback, view_files_at_beginning_callback, (XtPointer)vdat);
      XmStringFree(bstr);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"at sample");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 50); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, vdat->at_beginning_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY); n++;
      vdat->at_sample_button = make_togglebutton_widget("at-sample-button", bform, args, n);
      XtAddCallback(vdat->at_sample_button, XmNdisarmCallback, view_files_at_sample_callback, (XtPointer)vdat);
      XmStringFree(bstr);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNborderWidth, 0); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, vdat->at_beginning_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, vdat->at_sample_button); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, vdat->at_sample_button); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      vdat->at_sample_text = make_textfield_widget("at-sample-text", bform, args, n, NOT_ACTIVATABLE, NO_COMPLETER);
      XtRemoveCallback(vdat->at_sample_text, XmNlosingFocusCallback, textfield_unfocus_callback, NULL);
      XtAddCallback(vdat->at_sample_text, XmNlosingFocusCallback, blue_textfield_unfocus_callback, NULL);
      XtAddEventHandler(vdat->at_sample_text, LeaveWindowMask, false, blue_mouse_leave_text_callback, NULL);
      XtAddEventHandler(vdat->at_sample_text, EnterWindowMask, false, white_mouse_enter_text_callback, NULL);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNselectColor, ss->red); n++;
      bstr = XmStringCreateLocalized((char *)"at mark");
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 50); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, vdat->at_sample_button); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNlabelString, bstr); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      XtSetArg(args[n], XmNindicatorType, XmONE_OF_MANY); n++;
      vdat->at_mark_button = make_togglebutton_widget("at-mark-button", bform, args, n);
      XtAddCallback(vdat->at_mark_button, XmNdisarmCallback, view_files_at_mark_callback, (XtPointer)vdat);
      XmStringFree(bstr);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->lighter_blue); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, vdat->at_sample_text); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, vdat->at_mark_button); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNborderWidth, 0); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      vdat->at_mark_text = make_textfield_widget("at-mark-text", bform, args, n, NOT_ACTIVATABLE, NO_COMPLETER);
      XtRemoveCallback(vdat->at_mark_text, XmNlosingFocusCallback, textfield_unfocus_callback, NULL);
      XtAddCallback(vdat->at_mark_text, XmNlosingFocusCallback, blue_textfield_unfocus_callback, NULL);
      XtAddEventHandler(vdat->at_mark_text, LeaveWindowMask, false, blue_mouse_leave_text_callback, NULL);
      XtAddEventHandler(vdat->at_mark_text, EnterWindowMask, false, white_mouse_enter_text_callback, NULL);


      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, bframe); n++;

      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      XtSetArg(args[n], XmNheight, 8); n++;
      sep4 = XtCreateManagedWidget("sep4", xmSeparatorWidgetClass, leftform, args, n);

      n = 0;      
      /* AMP */
      s1 = XmStringCreateLocalized((char *)"amp:");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep4); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      /* XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++; */
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      amp_label = make_pushbutton_widget("amp-label", leftform, args, n);
      XtAddCallback(amp_label, XmNactivateCallback, vf_amp_click_callback, (XtPointer)vdat);
      XmStringFree(s1);

      n = 0;
      s1 = XmStringCreateLocalized((char *)"1.0 ");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep4); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, amp_label); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      /* XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++; */
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      /* XtSetArg(args[n], XmNmarginRight, 3); n++; */
      vdat->amp_number = XtCreateManagedWidget("amp-number", xmLabelWidgetClass, leftform, args, n);
      XmStringFree(s1);

      n = 0;      
      XtSetArg(args[n], XmNbackground, ss->position_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep4); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, vdat->amp_number); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNvalue, vf_amp_to_scroll(1.0)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n2 = make_callback_list(vf_amp_valuechanged_callback, (XtPointer)vdat)); n++;
      XtSetArg(args[n], XmNdragCallback, n3 = make_callback_list(vf_amp_drag_callback, (XtPointer)vdat)); n++;
      vdat->amp_scrollbar = XtCreateManagedWidget("amp-scroll", xmScrollBarWidgetClass, leftform, args, n);

      n = 0;
      /* SPEED */
      s1 = XmStringCreateLocalized((char *)"speed:");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, amp_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      /* XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;  */
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      speed_label = make_pushbutton_widget("speed-label", leftform, args, n);
      XtAddCallback(speed_label, XmNactivateCallback, vf_speed_click_callback, (XtPointer)vdat);
      XmStringFree(s1);

      n = 0;
      s1 = initial_speed_label(speed_control_style(ss));
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, speed_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, speed_label); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      /* XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++; */
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      /* XtSetArg(args[n], XmNmarginRight, 3); n++; */
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      vdat->speed_number = make_pushbutton_widget("speed-number", leftform, args, n);
      XtAddCallback(vdat->speed_number, XmNactivateCallback, vf_speed_label_click_callback, (XtPointer)vdat);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->position_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, vdat->speed_number); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, vdat->speed_number); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNvalue, vf_speed_to_scroll(speed_control_min(ss), 1.0, speed_control_max(ss))); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n4 = make_callback_list(vf_speed_valuechanged_callback, (XtPointer)vdat)); n++;
      XtSetArg(args[n], XmNdragCallback, n1 = make_callback_list(vf_speed_drag_callback, (XtPointer)vdat)); n++;
      vdat->speed_scrollbar = XtCreateManagedWidget("speed-scroll", xmScrollBarWidgetClass, leftform, args, n);


      /* separator before envelope */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, speed_label); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNheight, 8); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      sep7 = XtCreateManagedWidget("dialog-sep1", xmSeparatorWidgetClass, leftform, args, n);


      /* amp env */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep7); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNleftPosition, 4); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 98); n++;
      XtSetArg(args[n], XmNheight, 100); n++;
      XtSetArg(args[n], XmNallowResize, true); n++;
      XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_IN); n++;
      XtSetArg(args[n], XmNshadowThickness, 4); n++;
      env_frame = XtCreateManagedWidget("amp-env-frame", xmFrameWidgetClass, leftform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNallowResize, true); n++;
      XtSetArg(args[n], XmNheight, 100); n++;
      vdat->env_drawer = XtCreateManagedWidget("amp-env-window", xmDrawingAreaWidgetClass, env_frame, args, n);

      /* right side */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      viewform = XtCreateManagedWidget("viewform", xmFormWidgetClass, mainform, args, n);

      /* Add dir/file text entry at bottom */
      n = 0;
      s1 = XmStringCreateLocalized((char *)"add:");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      add_label = XtCreateManagedWidget("add", xmLabelWidgetClass, viewform, args, n);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, add_label); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      vdat->add_text = make_textfield_widget("add-text", viewform, args, n, ACTIVATABLE, add_completer_func(filename_completer, NULL));
      XtAddCallback(vdat->add_text, XmNactivateCallback, view_files_add_files, (XtPointer)vdat);
      
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, vdat->add_text); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
      XtSetArg(args[n], XmNheight, 4); n++;
      sep3 = XtCreateManagedWidget("sep3", xmSeparatorWidgetClass, viewform, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_CENTER); n++;	
      rlw = XtCreateManagedWidget("files", xmLabelWidgetClass, viewform, args, n);
      
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->white); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, rlw); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNseparatorType, XmDOUBLE_LINE); n++;
      sep1 = XtCreateManagedWidget("sep1", xmSeparatorWidgetClass, viewform, args, n);

#if WITH_AUDIO
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNleftPosition, 5); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep1); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      plw = XtCreateManagedWidget("play", xmLabelWidgetClass, viewform, args, n);
#endif

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep1); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNmarginHeight, 0); n++;
      sbar = XmCreateMenuBar(viewform, (char *)"menuBar", args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      vdat->smenu = XmCreatePulldownMenu(sbar, (char *)"sort-menu", args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNsubMenuId, vdat->smenu); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNmarginHeight, 1); n++;
      XtCreateManagedWidget("sort", xmCascadeButtonWidgetClass, sbar, args, n);
      
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      vdat->a_to_z =        XtCreateManagedWidget("a..z",       xmPushButtonWidgetClass, vdat->smenu, args, n);
      vdat->z_to_a =        XtCreateManagedWidget("z..a",       xmPushButtonWidgetClass, vdat->smenu, args, n);
      vdat->new_to_old =    XtCreateManagedWidget("new..old",   xmPushButtonWidgetClass, vdat->smenu, args, n);
      vdat->old_to_new =    XtCreateManagedWidget("old..new",   xmPushButtonWidgetClass, vdat->smenu, args, n);
      vdat->small_to_big =  XtCreateManagedWidget("small..big", xmPushButtonWidgetClass, vdat->smenu, args, n);
      vdat->big_to_small =  XtCreateManagedWidget("big..small", xmPushButtonWidgetClass, vdat->smenu, args, n);

      vdat->sort_items_size = 4;
      vdat->sort_items = (Widget *)calloc(vdat->sort_items_size, sizeof(Widget));
      for (i = 0; i < vdat->sort_items_size; i++)
	vdat->sort_items[i] = XtCreateWidget("unused", xmPushButtonWidgetClass, vdat->smenu, args, n);

      XtManageChild(sbar);

      n = 0;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNleftPosition, 5); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
#if WITH_AUDIO
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, plw); n++;
#else
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, sep1); n++;
#endif
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, sep3); n++;
      XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC); n++;
      XtSetArg(args[n], XmNscrollBarDisplayPolicy, XmSTATIC); n++;
      vdat->file_list = XmCreateScrolledWindow(viewform, (char *)"file_list", args, n);

      n = attach_all_sides(args, 0);
      vdat->file_list_holder = XtCreateManagedWidget("file_list_holder", xmRowColumnWidgetClass, vdat->file_list, args, n);
      XtVaSetValues(vdat->file_list, 
		    XmNworkWindow, vdat->file_list_holder, 
		    NULL);
      add_drag_and_drop(vdat->file_list, view_files_drop_watcher, view_files_drag_watcher, (void *)vdat);

      if (managed) view_files_display_list(vdat);

      XtAddCallback(vdat->a_to_z,       XmNactivateCallback, sort_view_files_a_to_z,       (XtPointer)vdat);
      XtAddCallback(vdat->z_to_a,       XmNactivateCallback, sort_view_files_z_to_a,       (XtPointer)vdat);
      XtAddCallback(vdat->new_to_old,   XmNactivateCallback, sort_view_files_new_to_old,   (XtPointer)vdat);
      XtAddCallback(vdat->old_to_new,   XmNactivateCallback, sort_view_files_old_to_new,   (XtPointer)vdat);
      XtAddCallback(vdat->small_to_big, XmNactivateCallback, sort_view_files_small_to_big, (XtPointer)vdat);
      XtAddCallback(vdat->big_to_small, XmNactivateCallback, sort_view_files_big_to_small, (XtPointer)vdat);
      vf_reflect_sort_choice_in_menu(vdat);

      {
	int i;
	for (i = 0; i < vdat->sort_items_size; i++)
	  XtAddCallback(vdat->sort_items[i], XmNactivateCallback, sort_view_files_xen, (XtPointer)vdat);
      }

      map_over_children(vdat->file_list, set_main_color_of_widget);
      set_dialog_widget(VIEW_FILES_DIALOG, vdat->dialog);

      if (managed)
	XtManageChild(vdat->dialog);

      XtAddCallback(vdat->env_drawer, XmNresizeCallback, vf_amp_env_resize, (XtPointer)vdat);
      XtAddCallback(vdat->env_drawer, XmNexposeCallback, vf_amp_env_resize, (XtPointer)vdat);

      vdat->spf = new_env_editor(); /* one global amp env */

      XtAddEventHandler(vdat->env_drawer, ButtonPressMask, false, vf_drawer_button_press, (XtPointer)vdat);
      XtAddEventHandler(vdat->env_drawer, ButtonMotionMask, false, vf_drawer_button_motion, (XtPointer)vdat);
      XtAddEventHandler(vdat->env_drawer, ButtonReleaseMask, false, vf_drawer_button_release, (XtPointer)vdat);

      free(n1);
      free(n2);
      free(n3);
      free(n4);

      vf_mix_insert_buttons_set_sensitive(vdat, false);
    }
  else
    {
      if (managed) 
	{
	  if (!XtIsManaged(vdat->dialog)) 
	    XtManageChild(vdat->dialog);
	  raise_dialog(vdat->dialog);
	  view_files_display_list(vdat);
	}
    }
  if (managed)
    {
      vf_amp_env_resize(vdat->env_drawer, (XtPointer)vdat, NULL);
      view_files_reflect_sort_items();
    }
  return(vdat->dialog);
}


/* -------- view-files variables -------- */

static Xen g_view_files_dialog(Xen managed, Xen make_new)
{
  #define H_view_files_dialog "(" S_view_files_dialog " :optional managed create-new-dialog): start the View Files dialog"
  Xen_check_type(Xen_is_boolean_or_unbound(managed), managed, 1, S_view_files_dialog, "a boolean");
  return(Xen_wrap_widget(make_view_files_dialog(Xen_boolean_to_C_bool(managed), Xen_is_true(make_new))));
}


static Xen g_add_directory_to_view_files_list(Xen directory, Xen dialog) 
{
  #define H_add_directory_to_view_files_list "(" S_add_directory_to_view_files_list " dir :optional w): adds any sound files in 'dir' to the View:Files dialog"
  
  Xen_check_type(Xen_is_string(directory), directory, 1, S_add_directory_to_view_files_list, "a string");
  Xen_check_type(Xen_is_widget(dialog) || !Xen_is_bound(dialog), dialog, 2, S_add_directory_to_view_files_list, "a view-files dialog widget"); 

  if (!Xen_is_bound(dialog))
    view_files_add_directory(NULL_WIDGET, Xen_string_to_C_string(directory));
  else view_files_add_directory((widget_t)(Xen_unwrap_widget(dialog)), Xen_string_to_C_string(directory));
  return(directory);
}


static Xen g_add_file_to_view_files_list(Xen file, Xen dialog) 
{
  #define H_add_file_to_view_files_list "(" S_add_file_to_view_files_list " file :optional w): adds file to the View:Files dialog's list"
  char *name = NULL;

  Xen_check_type(Xen_is_string(file), file, 1, S_add_file_to_view_files_list, "a string");
  Xen_check_type(Xen_is_widget(dialog) || !Xen_is_bound(dialog), dialog, 2, S_add_file_to_view_files_list, "a view-files dialog widget"); 

  name = mus_expand_filename(Xen_string_to_C_string(file));
  if (mus_file_probe(name))
    {
      if (!Xen_is_bound(dialog))
	view_files_add_file(NULL_WIDGET, name);
      else view_files_add_file((widget_t)(Xen_unwrap_widget(dialog)), name);
    }
  if (name) free(name);
  return(file);
}


static Xen g_view_files_sort(Xen dialog) 
{
  #define H_view_files_sort "(" S_view_files_sort " :optional dialog): sort choice in View:files dialog."
  if (Xen_is_bound(dialog))
    {
      Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_view_files_sort, "a view-files dialog widget"); 
      return(C_int_to_Xen_integer(view_files_local_sort((widget_t)(Xen_unwrap_widget(dialog)))));
    }
  return(C_int_to_Xen_integer(view_files_sort(ss)));
}


static Xen g_set_view_files_sort(Xen dialog, Xen val) 
{
  int choice;
  Xen sort_choice;

  if (Xen_is_bound(val)) sort_choice = val; else sort_choice = dialog;
  Xen_check_type(Xen_is_integer(sort_choice), sort_choice, 1, S_set S_view_files_sort, "an integer"); 

  choice = Xen_integer_to_C_int(sort_choice);
  if ((choice < 0) ||
      (choice >= (ss->file_sorters_size + SORT_XEN)))
    Xen_out_of_range_error(S_set S_view_files_sort, 2, sort_choice, "must be a valid file-sorter index");

  if (Xen_is_bound(val))
    {
      widget_t w;
      Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_set S_view_files_sort, "a view-files dialog widget"); 
      w = (widget_t)(Xen_unwrap_widget(dialog));
      view_files_set_local_sort(w, choice);
      return(C_int_to_Xen_integer((int)view_files_sort(ss)));
    }
  /* else set global (default) sort choice */
  set_view_files_sort(choice);
  return(C_int_to_Xen_integer((int)view_files_sort(ss)));
}


static Xen g_view_files_amp(Xen dialog)
{
  #define H_view_files_amp "(" S_view_files_amp " dialog): amp setting in the given View:Files dialog"
  Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_view_files_amp, "a view-files dialog widget"); 
  return(C_double_to_Xen_real(view_files_amp((widget_t)(Xen_unwrap_widget(dialog)))));
}


static Xen g_view_files_set_amp(Xen dialog, Xen amp)
{
  Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_set S_view_files_amp, "a view-files dialog widget"); 
  Xen_check_type(Xen_is_number(amp), amp, 2, S_set S_view_files_amp, "a number");
  view_files_set_amp((widget_t)(Xen_unwrap_widget(dialog)), Xen_real_to_C_double(amp));
  return(amp);
}


static Xen g_view_files_speed(Xen dialog)
{
  #define H_view_files_speed "(" S_view_files_speed " dialog): speed setting in the given View:Files dialog"
  Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_view_files_speed, "a view-files dialog widget"); 
  return(C_double_to_Xen_real(view_files_speed((widget_t)(Xen_unwrap_widget(dialog)))));
}


static Xen g_view_files_set_speed(Xen dialog, Xen speed)
{
  Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_set S_view_files_speed, "a view-files dialog widget"); 
  Xen_check_type(Xen_is_number(speed), speed, 2, S_set S_view_files_speed, "a number");
  view_files_set_speed((widget_t)(Xen_unwrap_widget(dialog)), Xen_real_to_C_double(speed));
  return(speed);
}


static Xen g_view_files_amp_env(Xen dialog)
{
  #define H_view_files_amp_env "(" S_view_files_amp_env " dialog): amp env breakpoints in the given View:Files dialog"
  Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_view_files_amp_env, "a view-files dialog widget"); 
  return(env_to_xen(view_files_amp_env((widget_t)(Xen_unwrap_widget(dialog)))));
}


static Xen g_view_files_set_amp_env(Xen dialog, Xen amp_env)
{
  Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_set S_view_files_amp_env, "a view-files dialog widget"); 
  Xen_check_type(Xen_is_list(amp_env), amp_env, 2, S_set S_view_files_amp_env, "an envelope");
  view_files_set_amp_env((widget_t)(Xen_unwrap_widget(dialog)), xen_to_env(amp_env));
  return(amp_env);
}


static Xen g_view_files_speed_style(Xen dialog)
{
  #define H_view_files_speed_style "(" S_view_files_speed_style " dialog): speed_style in use in the given View:Files dialog"
  Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_view_files_speed_style, "a view-files dialog widget"); 
  return(C_int_to_Xen_integer((int)(view_files_speed_style((widget_t)(Xen_unwrap_widget(dialog))))));
}


static Xen g_view_files_set_speed_style(Xen dialog, Xen speed_style)
{
  Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_set S_view_files_speed_style, "a view-files dialog widget"); 
  Xen_check_type(Xen_is_integer(speed_style), speed_style, 2, S_set S_view_files_speed_style, "an int");
  view_files_set_speed_style((widget_t)(Xen_unwrap_widget(dialog)), (speed_style_t)(Xen_integer_to_C_int(speed_style)));
  return(speed_style);
}


static Xen g_view_files_selected_files(Xen dialog)
{
  #define H_view_files_selected_files "(" S_view_files_selected_files " dialog): list of files currently selected in the given View:Files dialog"
  Xen result = Xen_empty_list;
  char **selected_files;
  int len = 0;

  Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_view_files_selected_files, "a view-files dialog widget"); 

  selected_files = view_files_selected_files((widget_t)(Xen_unwrap_widget(dialog)), &len);
  if ((selected_files) && (len > 0))
    {
      int i;
      for (i = 0; i < len; i++)
	{
	  result = Xen_cons(C_string_to_Xen_string(selected_files[i]), result);
	  free(selected_files[i]);
	}
      free(selected_files);
    }
  return(result);
}


static Xen g_view_files_set_selected_files(Xen dialog, Xen files)
{
  int len;

  Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_set S_view_files_selected_files, "a view-files dialog widget");   
  Xen_check_type(Xen_is_list(files), files, 2, S_set S_view_files_selected_files, "a list of files or directories");

  len = Xen_list_length(files);
  if (len > 0)
    {
      char **cfiles = NULL;
      int i;
      for (i = 0; i < len; i++)
	if (!(Xen_is_string(Xen_list_ref(files, i))))
	  {
	    Xen_check_type(false, Xen_list_ref(files, i), i, S_set S_view_files_selected_files, "a filename (string)");
	    return(Xen_false);
	  }
      cfiles = (char **)calloc(len, sizeof(char *));
      for (i = 0; i < len; i++)
	cfiles[i] = (char *)Xen_string_to_C_string(Xen_list_ref(files, i));
      view_files_set_selected_files((widget_t)(Xen_unwrap_widget(dialog)), cfiles, len);
      free(cfiles);
    }
  return(files);
}


static Xen g_view_files_files(Xen dialog)
{
  #define H_view_files_files "(" S_view_files_files " dialog): list of files currently available in the given View:Files dialog"
  Xen result = Xen_empty_list;
  char **files;
  int i, len = 0;

  Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_view_files_files, "a view-files dialog widget"); 

  files = view_files_files((widget_t)(Xen_unwrap_widget(dialog)), &len);
  if ((files) && (len > 0))
    for (i = 0; i < len; i++)
      result = Xen_cons(C_string_to_Xen_string(files[i]), result);
  return(result);
}


static Xen g_view_files_set_files(Xen dialog, Xen files)
{
  int len = 0;
  char **cfiles = NULL;

  Xen_check_type(Xen_is_widget(dialog), dialog, 1, S_set S_view_files_files, "a view-files dialog widget");   
  Xen_check_type(Xen_is_list(files), files, 2, S_set S_view_files_files, "a list of files or directories");

  len = Xen_list_length(files);
  if (len > 0)
    {
      int i;
      for (i = 0; i < len; i++)
	if (!(Xen_is_string(Xen_list_ref(files, i))))
	  {
	    Xen_check_type(false, Xen_list_ref(files, i), i, S_set S_view_files_files, "a filename (string)");
	    return(Xen_false);
	  }
      cfiles = (char **)calloc(len, sizeof(char *));
      for (i = 0; i < len; i++)
	cfiles[i] = (char *)Xen_string_to_C_string(Xen_list_ref(files, i));
    }
  view_files_set_files((widget_t)(Xen_unwrap_widget(dialog)), cfiles, len);
  if (cfiles) free(cfiles);
  return(files);
}


static Xen view_files_select_hook;

static void view_files_run_select_hook(widget_t dialog, const char *selected_file)
{
  if (Xen_hook_has_list(view_files_select_hook))
    run_hook(view_files_select_hook,
	     Xen_list_2(Xen_wrap_widget(dialog),
			C_string_to_Xen_string(selected_file)),
	     S_view_files_select_hook);
}

/* -------- file-filters and file-sorters -------- */

#define INITIAL_FILE_SORTERS_SIZE 4

static bool file_sorter_ok(Xen name, Xen proc, const char *caller)
{
  char *errmsg;
  Xen_check_type(Xen_is_string(name), name, 1, caller, "a string");   
  Xen_check_type(Xen_is_procedure(proc), proc, 2, caller, "a procedure of 2 args (file1 and file2)");
  errmsg = procedure_ok(proc, 2, caller, "file sort", 2);
  if (errmsg)
    {
      Xen errstr;
      errstr = C_string_to_Xen_string(errmsg);
      free(errmsg);
      snd_bad_arity_error(caller, errstr, proc);
      return(false);
    }
  return(true);
}


static Xen g_add_file_sorter(Xen name, Xen proc)
{
  #define H_add_file_sorter "(" S_add_file_sorter " name proc) -- add proc with identifier name to file sorter list, returns its index"
  int choice = -1;
  /* type checks are redundant here */

  if (file_sorter_ok(name, proc, S_add_file_sorter))
    {
      int i, len;
      len = ss->file_sorters_size;
      for (i = 0; i < len; i++)
	{
	  if (Xen_is_false(Xen_vector_ref(ss->file_sorters, i)))
	    {
	      Xen_vector_set(ss->file_sorters, i, Xen_list_2(name, proc));
	      choice = i;
	      break;
	    }
	}
      if (choice == -1)
	{
	  ss->file_sorters_size = len * 2;
	  ss->file_sorters = g_expand_vector(ss->file_sorters, ss->file_sorters_size);
	  Xen_vector_set(ss->file_sorters, len, Xen_list_2(name, proc));
	  choice = len;
	}
      view_files_reflect_sort_items();
    }
  return(C_int_to_Xen_integer(choice + SORT_XEN));
}


static Xen g_delete_file_sorter(Xen index)
{
  #define H_delete_file_sorter "(" S_delete_file_sorter " index) -- delete proc with identifier name from file sorter list"
  int pos;
  Xen_check_type(Xen_is_integer(index), index, 1, S_delete_file_sorter, "a file-sorter index");   
  pos = Xen_integer_to_C_int(index);
  if ((pos >= SORT_XEN) &&
      ((pos - SORT_XEN) < ss->file_sorters_size))
    Xen_vector_set(ss->file_sorters, pos - SORT_XEN, Xen_false);
  view_files_reflect_sort_items();
  return(index);
}



/* -------- drop watcher lists -------- */

/* the rigamarole for dealing with a drop is so messed up that I think it's
 *   worth the trouble of setting up a separate callback list -- hence the
 *   drop watchers
 */

typedef struct {
  void (*drop_watcher)(Widget w, const char *message, Position x, Position y, void *data);
  void (*drag_watcher)(Widget w, const char *message, Position x, Position y, drag_style_t dtype, void *data);
  Widget caller;
  void *context;
} drop_watcher_t;

static drop_watcher_t **drop_watchers = NULL;
static int drop_watchers_size = 0;

#define DROP_WATCHER_SIZE_INCREMENT 2


static int add_drop_watcher(Widget w, 
			    void (*drop_watcher)(Widget w, const char *message, Position x, Position y, void *data), 
			    void (*drag_watcher)(Widget w, const char *message, Position x, Position y, drag_style_t dtype, void *data), 
			    void *context)
{
  int loc = -1;
  if (!(drop_watchers))
    {
      loc = 0;
      drop_watchers_size = DROP_WATCHER_SIZE_INCREMENT;
      drop_watchers = (drop_watcher_t **)calloc(drop_watchers_size, sizeof(drop_watcher_t *));
    }
  else
    {
      int i;
      for (i = 0; i < drop_watchers_size; i++)
	if (!(drop_watchers[i]))
	  {
	    loc = i;
	    break;
	  }
      if (loc == -1)
	{
	  loc = drop_watchers_size;
	  drop_watchers_size += DROP_WATCHER_SIZE_INCREMENT;
	  drop_watchers = (drop_watcher_t **)realloc(drop_watchers, drop_watchers_size * sizeof(drop_watcher_t *));
	  for (i = loc; i < drop_watchers_size; i++) drop_watchers[i] = NULL;
	}
    }
  drop_watchers[loc] = (drop_watcher_t *)calloc(1, sizeof(drop_watcher_t));
  drop_watchers[loc]->drop_watcher = drop_watcher;
  drop_watchers[loc]->drag_watcher = drag_watcher;
  drop_watchers[loc]->context = context;
  drop_watchers[loc]->caller = w;
  return(loc);
}


#if 0
static bool remove_drop_watcher(int loc)
{
  if ((drop_watchers) &&
      (loc < drop_watchers_size) &&
      (loc >= 0) &&
      (drop_watchers[loc]))
    {
      free(drop_watchers[loc]);
      drop_watchers[loc] = NULL;
      return(true);
    }
  return(false);
}
#endif


static drop_watcher_t *find_drop_watcher(Widget caller)
{
  if (drop_watchers)
    {
      int i;
      for (i = 0; i < drop_watchers_size; i++)
	{
	  if (drop_watchers[i])
	    {
	      drop_watcher_t *d;
	      d = drop_watchers[i];
	      if (d->caller == caller)
		return(d);
	    }
	}
    }
  return(NULL);
}


/* can't move axes if icon dragged to end of graph because the entire system freezes! */

static Atom FILE_NAME;               /* Sun uses this, SGI uses STRING */
static Atom COMPOUND_TEXT;           /* various Motif widgets use this and the next */
static Atom _MOTIF_COMPOUND_STRING;
static Atom text_plain;
static Atom uri_list;                /* rox uses this -- looks just like text/plain to me */
static Atom TEXT;                    /* ditto */

static Xen drop_hook;


static char *atom_to_string(Atom type, XtPointer value, unsigned long length)
{
  char *str = NULL;
  if ((type == XA_STRING) || (type == FILE_NAME) || (type == text_plain) || (type == uri_list) || (type == TEXT))
    {
      unsigned long i;
      str = (char *)calloc(length + 1, sizeof(char));
      for (i = 0; i < length; i++)
	str[i] = ((char *)value)[i];
    }
  else
    {
      if ((type == COMPOUND_TEXT) || (type == _MOTIF_COMPOUND_STRING))
	{
	  char *temp;
	  XmString cvt, tmp;
	  XmParseTable parser = (XmParseTable)XtCalloc(1, sizeof(XmParseMapping));
	  int n;
	  Arg args[12];

	  /* create parse table to catch separator in XmString and insert "\n" in output */
	  /*   multiple file names are passed this way in Motif */
	  tmp = XmStringSeparatorCreate();
	  n = 0;
	  XtSetArg(args[n], XmNincludeStatus, XmINSERT); n++;
	  XtSetArg(args[n], XmNsubstitute, tmp); n++;
	  XtSetArg(args[n], XmNpattern, "\n"); n++;
	  parser[0] = XmParseMappingCreate(args, n);

	  if (type == _MOTIF_COMPOUND_STRING)
	    cvt = XmCvtByteStreamToXmString((unsigned char *)value);
	  else cvt = XmCvtCTToXmString((char *)value);
	  temp = (char *)XmStringUnparse(cvt, NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, parser, 1, XmOUTPUT_ALL);

	  XmParseTableFree(parser, 1);
	  XmStringFree(cvt);
	  str = mus_strdup(temp);
	  XtFree(temp);
	}
    }
  return(str);
}


static Position mx, my;

static void massage_selection(Widget w, XtPointer context, Atom *selection, Atom *type, XtPointer value, unsigned long *length, int *format)
{
  char *str = NULL;
  str = atom_to_string(*type, value, *length);
  /* str can contain more than one name (separated by cr) */
  if (str)
    {
      if ((!(Xen_hook_has_list(drop_hook))) || 
	  (!(Xen_is_true(run_or_hook(drop_hook,
				    Xen_list_1(C_string_to_Xen_string(str)),
				    S_drop_hook)))))
	{
	  Widget caller; /* "w" above is the transfer control widget, not the drop-receiver */
	  drop_watcher_t *d;
	  caller = (Widget)((XmDropTransferEntry)context)->client_data;
	  d = find_drop_watcher(caller);
	  if (d)
	    {
	      /* loop through possible list of filenames, calling watcher on each */
	      char *filename;
	      int len = 0, i, j = 0;
	      len = mus_strlen(str);
	      filename = (char *)calloc(len, sizeof(char));
	      for (i = 0; i < len; i++)
		{
		  if ((str[i] == '\n') || (str[i] == '\r')) /* apparently the only space chars here are \n and \r? */
		    {
		      if (j > 0)
			{
			  filename[j] = '\0';
			  if (strncmp(filename, "file://", 7) == 0)
			    {
			      char *tmp;
			      tmp = (char *)(filename + 7);
			      (*(d->drop_watcher))(caller, (const char *)tmp, mx, my, d->context);
			    }
			  else (*(d->drop_watcher))(caller, (const char *)filename, mx, my, d->context);
			  j = 0;
			}
		      /* else ignore extra white space chars */
		    }
		  else
		    {
		      filename[j++] = str[i];
		    }
		}
	      free(filename);
	    }
	}
      free(str);
    }
}


static void handle_drop(Widget w, XtPointer context, XtPointer info) 
{
  XmDropProcCallbackStruct *cb = (XmDropProcCallbackStruct *)info;
  Arg args[12];
  int n, i, num_targets, k;
  Atom *targets;
  XmDropTransferEntryRec entries[2];

  if ((cb->dropAction != XmDROP) || 
      ((cb->operation != XmDROP_COPY) &&
       (cb->operation != XmDROP_LINK)))
    {
      cb->dropSiteStatus = XmINVALID_DROP_SITE;
      return;
    }

  k = -1;
  XtVaGetValues(cb->dragContext, 
		XmNexportTargets, &targets, 
		XmNnumExportTargets, &num_targets, 
		NULL);

  for (i = 0; i < num_targets; i++) 
    if ((targets[i] == XA_STRING) || 
	(targets[i] == FILE_NAME) ||
	(targets[i] == COMPOUND_TEXT) ||
	(targets[i] == _MOTIF_COMPOUND_STRING) ||
	(targets[i] == TEXT) ||
	(targets[i] == text_plain) ||
	(targets[i] == uri_list))
      {
	k = i; 
	break;
      }
  if (k == -1)
    {
#if 0
      fprintf(stderr, "failed drop attempt:\n");
      for (i = 0; i < num_targets; i++) 
	fprintf(stderr, "  target %d = %s\n", i, 
		XGetAtomName(main_display(ss),
			     targets[i]));
#endif
      cb->dropSiteStatus = XmINVALID_DROP_SITE;
      cb->operation = XmDROP_NOOP;
      n = 0;
      XtSetArg(args[n], XmNnumDropTransfers, 0); n++;
      XtSetArg(args[n], XmNtransferStatus, XmTRANSFER_FAILURE); n++;
      XmDropTransferStart(cb->dragContext, args, n);
      return;
    }
  
  mx = cb->x;
  my = cb->y;
  entries[0].target = targets[k];
  entries[0].client_data = (XtPointer)w;
  n = 0;
  XtSetArg(args[n], XmNdropTransfers, entries); n++;
  XtSetArg(args[n], XmNnumDropTransfers, 1); n++;
  XtSetArg(args[n], XmNtransferProc, massage_selection); n++;
  /* cb->operation = XmDROP_COPY; */

  XmDropTransferStart(cb->dragContext, args, n);
}


static void handle_drag(Widget w, XtPointer context, XtPointer info)
{
  XmDragProcCallbackStruct *cb = (XmDragProcCallbackStruct *)info;
  drop_watcher_t *d;
  d = find_drop_watcher(w);
  if ((d) && (d->drag_watcher))
    {
      switch (cb->reason)
	{ 
	case XmCR_DROP_SITE_MOTION_MESSAGE:
	  (*(d->drag_watcher))(w, NULL, cb->x, cb->y, DRAG_MOTION, d->context);
	  break;
	case XmCR_DROP_SITE_ENTER_MESSAGE:
	  (*(d->drag_watcher))(w, NULL, cb->x, cb->y, DRAG_ENTER, d->context);
	  break;
	case XmCR_DROP_SITE_LEAVE_MESSAGE: 
	  (*(d->drag_watcher))(w, NULL, cb->x, cb->y, DRAG_LEAVE, d->context);
	  break;
	}
    }
}

#define NUM_TARGETS 7
void add_drag_and_drop(Widget w, 
		       void (*drop_watcher)(Widget w, const char *message, Position x, Position y, void *data), 
		       void (*drag_watcher)(Widget w, const char *message, Position x, Position y, drag_style_t dtype, void *data), 
		       void *context)
{
  Display *dpy;
  int n;
  Atom targets[NUM_TARGETS];
  Arg args[12];
  dpy = main_display(ss);
  targets[0] = XA_STRING;
  FILE_NAME = XInternAtom(dpy, "FILE_NAME", false);
  targets[1] = FILE_NAME;
  COMPOUND_TEXT = XInternAtom(dpy, "COMPOUND_TEXT", false);
  targets[2] = COMPOUND_TEXT;
  _MOTIF_COMPOUND_STRING = XInternAtom(dpy, "_MOTIF_COMPOUND_STRING", false);
  targets[3] = _MOTIF_COMPOUND_STRING;
  text_plain = XInternAtom(dpy, "text/plain", false);
  targets[4] = text_plain;
  TEXT = XInternAtom(dpy, "TEXT", false);
  targets[5] = TEXT;
  uri_list = XInternAtom(dpy, "text/uri-list", false);
  targets[6] = uri_list;
  n = 0;
  XtSetArg(args[n], XmNdropSiteOperations, XmDROP_COPY | XmDROP_LINK); n++;
  XtSetArg(args[n], XmNimportTargets, targets); n++;
  XtSetArg(args[n], XmNnumImportTargets, NUM_TARGETS); n++;
  XtSetArg(args[n], XmNdropProc, handle_drop); n++;
  XtSetArg(args[n], XmNdragProc, handle_drag); n++;
  XmDropSiteRegister(w, args, n);
  add_drop_watcher(w, drop_watcher, drag_watcher, context);
}



/* -------------------------------------------------------------------------------- */


#include "sndlib-strings.h"

/* preferences dialog; layout design taken from webmail
 */

static Widget preferences_dialog = NULL, load_path_text_widget = NULL;
static bool prefs_unsaved = false;
static char *prefs_saved_filename = NULL;
static char *include_load_path = NULL;

#define MID_POSITION 50
#define PREFS_COLOR_POSITION 62  /* COLOR_POSITION is slider_choice_t in snd-0.h */
#define FIRST_COLOR_POSITION 6
#define SECOND_COLOR_POSITION 30
#define THIRD_COLOR_POSITION 55

#define MID_SPACE 16
#define INTER_TOPIC_SPACE 3
#define INTER_VARIABLE_SPACE 2

#define POWER_WAIT_TIME 100
#define POWER_INITIAL_WAIT_TIME 500
/* "power" is an old-timey name for auto-repeat */
#define ERROR_WAIT_TIME 5000

#define STARTUP_WIDTH 925
#define STARTUP_HEIGHT 800


typedef struct prefs_info {
  Widget label, text, arrow_up, arrow_down, arrow_right, error, toggle, scale, toggle2, toggle3;
  Widget color, rscl, gscl, bscl, rtxt, gtxt, btxt, list_menu, radio_button;
  Widget *radio_buttons;
  bool got_error;
  timeout_result_t power_id;
  const char *var_name, *saved_label;
  int num_buttons;
  mus_float_t scale_max;
  void (*toggle_func)(struct prefs_info *prf);
  void (*toggle2_func)(struct prefs_info *prf);
  void (*scale_func)(struct prefs_info *prf);
  void (*arrow_up_func)(struct prefs_info *prf);
  void (*arrow_down_func)(struct prefs_info *prf);
  void (*text_func)(struct prefs_info *prf);
  void (*list_func)(struct prefs_info *prf, char *value);
  void (*color_func)(struct prefs_info *prf, double r, double g, double b);
  void (*reflect_func)(struct prefs_info *prf);
  void (*save_func)(struct prefs_info *prf, FILE *fd);
  const char *(*help_func)(struct prefs_info *prf);
  void (*clear_func)(struct prefs_info *prf);
  void (*revert_func)(struct prefs_info *prf);
} prefs_info;


static void prefs_set_dialog_title(const char *filename);
static void reflect_key(prefs_info *prf, const char *key_name);
static void save_key(prefs_info *prf, FILE *fd, char *(*binder)(char *key, bool c, bool m, bool x));
static void key_bind(prefs_info *prf, char *(*binder)(char *key, bool c, bool m, bool x));
static void clear_prefs_dialog_error(void);
static void scale_set_color(prefs_info *prf, color_t pixel);
static color_t rgb_to_color(mus_float_t r, mus_float_t g, mus_float_t b);
static void post_prefs_error(const char *msg, prefs_info *data);
#ifdef __GNUC__
  static void va_post_prefs_error(const char *msg, prefs_info *data, ...) __attribute__ ((format (printf, 1, 0)));
#else
  static void va_post_prefs_error(const char *msg, prefs_info *data, ...);
#endif

/* used in snd-prefs */
#define GET_TOGGLE(Toggle)        (XmToggleButtonGetState(Toggle) == XmSET)
#define SET_TOGGLE(Toggle, Value) XmToggleButtonSetState(Toggle, Value, false)
#define GET_TEXT(Text)            XmTextFieldGetString(Text)
#define SET_TEXT(Text, Val)       XmTextFieldSetString(Text, (char *)Val)
#define free_TEXT(Val)            XtFree(Val)
#define SET_SCALE(Value)          XmScaleSetValue(prf->scale, (int)(100 * Value))
#define SET_SENSITIVE(Wid, Val)   XtSetSensitive(Wid, Val)
#define black_text(Prf)           XtVaSetValues(Prf->label, XmNforeground, ss->black, NULL)
#define red_text(Prf)             XtVaSetValues(Prf->label, XmNforeground, ss->red, NULL)

#define TIMEOUT(Func)             XtAppAddTimeOut(main_app(ss), ERROR_WAIT_TIME, Func, (XtPointer)prf)


static int get_scale_1(Widget scale)
{
  int val = 0;
  XmScaleGetValue(scale, &val);
  return(val);
}

#define GET_SCALE()               (get_scale_1(prf->scale) * 0.01)


static void set_radio_button(prefs_info *prf, int which)
{
  if ((which >= 0) && (which < prf->num_buttons))
    {
      int i;
      for (i = 0; i < prf->num_buttons; i++)
	{
	  if ((prf->radio_buttons[i]) &&
	      (XmIsToggleButton(prf->radio_buttons[i])))
	    XmToggleButtonSetState(prf->radio_buttons[i], (i == which), false);
	}
      prf->radio_button = prf->radio_buttons[which];
    }
}


static int which_radio_button(prefs_info *prf)
{
  intptr_t which = 0;
  XtVaGetValues(prf->radio_button, XmNuserData, &which, NULL);
  return(which);
}


#include "snd-prefs.c"

static bool prefs_dialog_error_is_posted = false;

static void post_prefs_dialog_error(const char *message, void *data)
{
  XmString title;
  title = XmStringCreateLocalized((char *)message);
  XtVaSetValues(preferences_dialog, 
		XmNmessageString, title, 
		NULL);
  XmStringFree(title);
  prefs_dialog_error_is_posted = (bool)message;
}


static void clear_prefs_dialog_error(void)
{
  if (prefs_dialog_error_is_posted)
    {
      prefs_dialog_error_is_posted = false;
      post_prefs_dialog_error(NULL, NULL);
    }
}


static void prefs_change_callback(Widget w, XtPointer context, XtPointer info)
{
  prefs_unsaved = true;
  prefs_set_dialog_title(NULL);
  clear_prefs_dialog_error();
}


/* ---------------- row (main) label widget ---------------- */

static Widget make_row_label(prefs_info *prf, const char *label, Widget box, Widget top_widget)
{
  Widget w;
  Arg args[20];
  int n;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
  XtSetArg(args[n], XmNrightPosition, MID_POSITION); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_END); n++;
  w = XtCreateManagedWidget(label, xmLabelWidgetClass, box, args, n);
  prf->saved_label = label;
  return(w);
}


/* ---------------- row inner label widget ---------------- */

static Widget make_row_inner_label(prefs_info *prf, const char *label, Widget left_widget, Widget box, Widget top_widget)
{
  Widget w;
  Arg args[20];
  int n;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, left_widget); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  w = XtCreateManagedWidget(label, xmLabelWidgetClass, box, args, n);
  return(w);
}


/* ---------------- row middle separator widget ---------------- */

static Widget make_row_middle_separator(Widget label, Widget box, Widget top_widget)
{
  Arg args[20];
  int n;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
  XtSetArg(args[n], XmNbottomWidget, label); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, label); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
  XtSetArg(args[n], XmNwidth, MID_SPACE); n++;
  XtSetArg(args[n], XmNseparatorType, XmSHADOW_ETCHED_OUT); n++;
  return(XtCreateManagedWidget("sep", xmSeparatorWidgetClass, box, args, n));
}


/* ---------------- row inner separator widget ---------------- */

static Widget make_row_inner_separator(int width, Widget left_widget, Widget box, Widget top_widget)
{
  Arg args[20];
  int n;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, left_widget); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
  XtSetArg(args[n], XmNwidth, width); n++;
  XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
  return(XtCreateManagedWidget("sep1", xmSeparatorWidgetClass, box, args, n));
}


static Widget make_row_error(prefs_info *prf, Widget box, Widget left_widget, Widget top_widget)
{
  Arg args[20];
  int n;
  Widget w;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
  XtSetArg(args[n], XmNbottomWidget, left_widget); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, left_widget); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_END); n++;
  w = XtCreateManagedWidget("", xmLabelWidgetClass, box, args, n);
  return(w);
}


/* ---------------- row toggle widget ---------------- */

static Widget make_row_toggle_with_label(prefs_info *prf, bool current_value, Widget left_widget, Widget box, Widget top_widget, const char *label)
{
  Widget w;
  Arg args[20];
  int n;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, left_widget); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNset, (current_value) ? XmSET : XmUNSET); n++;
  XtSetArg(args[n], XmNborderWidth, 0); n++;
  XtSetArg(args[n], XmNborderColor, ss->white); n++;
  XtSetArg(args[n], XmNmarginHeight, 0); n++;
  XtSetArg(args[n], XmNindicatorOn, XmINDICATOR_FILL); n++;
  XtSetArg(args[n], XmNindicatorSize, 14); n++;
  w = XtCreateManagedWidget(label, xmToggleButtonWidgetClass, box, args, n);
  return(w);
}


static Widget make_row_toggle(prefs_info *prf, bool current_value, Widget left_widget, Widget box, Widget top_widget)
{
  return(make_row_toggle_with_label(prf, current_value, left_widget, box, top_widget, " "));
}



/* ---------------- row arrows ---------------- */

static void remove_arrow_func(Widget w, XtPointer context, XtPointer info)
{
  prefs_info *prf = (prefs_info *)context;
  if (prf->power_id != 0)
    {
      XtRemoveTimeOut(prf->power_id);
      prf->power_id = 0;
    }
}


static void arrow_func_up(XtPointer context, XtIntervalId *id)
{
  prefs_info *prf = (prefs_info *)context;
  if (XtIsSensitive(prf->arrow_up))
    {
      if ((prf) && (prf->arrow_up_func))
	{
	  (*(prf->arrow_up_func))(prf);
	  prf->power_id = XtAppAddTimeOut(main_app(ss),
					  POWER_WAIT_TIME,
					  arrow_func_up,
					  (XtPointer)prf);
	}
      else prf->power_id = 0;
    }
}


static void arrow_func_down(XtPointer context, XtIntervalId *id)
{
  prefs_info *prf = (prefs_info *)context;
  if (XtIsSensitive(prf->arrow_down))
    {
      if ((prf) && (prf->arrow_down_func))
	{
	  (*(prf->arrow_down_func))(prf);
	  prf->power_id = XtAppAddTimeOut(main_app(ss),
					  POWER_WAIT_TIME,
					  arrow_func_down,
					  (XtPointer)prf);
	}
      else prf->power_id = 0;
    }
}


static void call_arrow_down_press(Widget w, XtPointer context, XtPointer info) 
{
  prefs_info *prf = (prefs_info *)context;
  if ((prf) && (prf->arrow_down_func))
    {
      (*(prf->arrow_down_func))(prf);
      if (XtIsSensitive(w))
	prf->power_id = XtAppAddTimeOut(main_app(ss),
					POWER_INITIAL_WAIT_TIME,
					arrow_func_down,
					(XtPointer)prf);
      else prf->power_id = 0;
    }
}


static void call_arrow_up_press(Widget w, XtPointer context, XtPointer info) 
{
  prefs_info *prf = (prefs_info *)context;
  if ((prf) && (prf->arrow_up_func))
    {
      (*(prf->arrow_up_func))(prf);
      if (XtIsSensitive(w))
	prf->power_id = XtAppAddTimeOut(main_app(ss),
					POWER_INITIAL_WAIT_TIME,
					arrow_func_up,
					(XtPointer)prf);
      else prf->power_id = 0;
    }
}


static Widget make_row_arrows(prefs_info *prf, Widget box, Widget left_widget, Widget top_widget)
{
  Arg args[20];
  int n;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, left_widget); n++;
  XtSetArg(args[n], XmNarrowDirection, XmARROW_DOWN); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  prf->arrow_down = XtCreateManagedWidget("arrow-down", xmArrowButtonWidgetClass, box, args, n);
  
  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, prf->arrow_down); n++;
  XtSetArg(args[n], XmNarrowDirection, XmARROW_UP); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  prf->arrow_up = XtCreateManagedWidget("arrow-up", xmArrowButtonWidgetClass, box, args, n);

  XtAddCallback(prf->arrow_down, XmNarmCallback, call_arrow_down_press, (XtPointer)prf);
  XtAddCallback(prf->arrow_down, XmNdisarmCallback, remove_arrow_func, (XtPointer)prf);
  XtAddCallback(prf->arrow_up, XmNarmCallback, call_arrow_up_press, (XtPointer)prf);
  XtAddCallback(prf->arrow_up, XmNdisarmCallback, remove_arrow_func, (XtPointer)prf);

  XtAddCallback(prf->arrow_up, XmNactivateCallback, prefs_change_callback, NULL);
  XtAddCallback(prf->arrow_down, XmNactivateCallback, prefs_change_callback, NULL);

  return(prf->arrow_up);
}


/* ---------------- bool row ---------------- */

static void call_toggle_func(Widget w, XtPointer context, XtPointer info)
{
  prefs_info *prf = (prefs_info *)context;
  if ((prf) && (prf->toggle_func))
    (*(prf->toggle_func))(prf);
}


static prefs_info *prefs_row_with_toggle(const char *label, const char *varname, bool current_value,
					 Widget box, Widget top_widget, 
					 void (*toggle_func)(prefs_info *prf))
{
  prefs_info *prf = NULL;
  Widget sep;
  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;
  prf->toggle_func = toggle_func;

  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);
  prf->toggle = make_row_toggle(prf, current_value, sep, box, top_widget);
  
  XtAddCallback(prf->toggle, XmNvalueChangedCallback, call_toggle_func, (XtPointer)prf);
  return(prf);
}


/* ---------------- two toggles ---------------- */

static void call_toggle2_func(Widget w, XtPointer context, XtPointer info)
{
  prefs_info *prf = (prefs_info *)context;
  if ((prf) && (prf->toggle2_func))
    (*(prf->toggle2_func))(prf);
}


static prefs_info *prefs_row_with_two_toggles(const char *label, const char *varname, 
					      const char *label1, bool value1,
					      const char *label2, bool value2,
					      Widget box, Widget top_widget, 
					      void (*toggle_func)(prefs_info *prf),
					      void (*toggle2_func)(prefs_info *prf))
{
  prefs_info *prf = NULL;
  Widget sep, sep1;
  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;
  prf->toggle_func = toggle_func;
  prf->toggle2_func = toggle2_func;

  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);
  prf->toggle = make_row_toggle_with_label(prf, value1, sep, box, top_widget, label1);
  sep1 = make_row_inner_separator(20, prf->toggle, box, top_widget);
  prf->toggle2 = make_row_toggle_with_label(prf, value2, sep1, box, top_widget, label2);

  XtAddCallback(prf->toggle, XmNvalueChangedCallback, call_toggle_func, (XtPointer)prf);
  XtAddCallback(prf->toggle2, XmNvalueChangedCallback, call_toggle2_func, (XtPointer)prf);
  return(prf);
}


/* ---------------- toggle with text ---------------- */

static void call_text_func(Widget w, XtPointer context, XtPointer info) 
{
  prefs_info *prf = (prefs_info *)context;
  if ((prf) && (prf->text_func))
    (*(prf->text_func))(prf);
}


/* earlier versions of this dialog assumed the user would type <cr> to save a new value
 *    typed in a text widget, but no one does that anymore, so now, to catch these changes
 *    but not be confused by automatic changes (such as in a scale widget's label),
 *    we'll use XmNfocusCallback to set a text_focussed flag, XmNlosingFocusCallback to
 *    unset it, XmNvalueChangedCallback to set a text_changed flag, XmNactivateCallback
 *    to clear text_changed, and if XmNlosingFocusCallback sees that flag set, it
 *    calls the activate function and unsets the flag. 
 */

typedef struct {
  bool text_focussed, text_changed;
  prefs_info *prf;
} text_info;


static void text_change_callback(Widget w, XtPointer context, XtPointer info)
{
  text_info *data = (text_info *)context;
  if (data->text_focussed) /* try to omit non-user actions that change the value */
    data->text_changed = true;
}


static void text_activate_callback(Widget w, XtPointer context, XtPointer info)
{
  text_info *data = (text_info *)context;
  data->text_changed = false;
}


static void text_grab_focus_callback(Widget w, XtPointer context, XtPointer info)
{
  text_info *data = (text_info *)context;
  data->text_focussed = true;
}


static void text_lose_focus_callback(Widget w, XtPointer context, XtPointer info)
{
  text_info *data = (text_info *)context;
  if ((data->text_focussed) &&
      (data->text_changed) &&
      (data->prf) &&
      (data->prf->text_func))
    {
      (*(data->prf->text_func))(data->prf);
      data->text_changed = false;
    }
}


static Widget make_row_text(prefs_info *prf, const char *text_value, int cols, Widget left_widget, Widget box, Widget top_widget)
{
  Widget w;
  int n;
  Arg args[30];
  text_info *info;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, left_widget); n++;
  if (cols != 0)
    {
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNcolumns, cols); n++;
    }
  else
    {
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    }
  if (text_value)
    {
      XtSetArg(args[n], XmNvalue, text_value); n++;
    }
  XtSetArg(args[n], XmNmarginHeight, 1); n++;
  XtSetArg(args[n], XmNborderWidth, 0); n++;
  XtSetArg(args[n], XmNborderColor, ss->white); n++;
  XtSetArg(args[n], XmNbottomShadowColor, ss->white); n++;
  XtSetArg(args[n], XmNshadowThickness, 0); n++;
  XtSetArg(args[n], XmNtopShadowColor, ss->white); n++;
  w = make_textfield_widget("text", box, args, n, ACTIVATABLE_BUT_NOT_FOCUSED, NO_COMPLETER);

  XtAddCallback(w, XmNactivateCallback, prefs_change_callback, NULL);

  info = (text_info *)calloc(1, sizeof(text_info));
  info->prf = prf;

  XtAddCallback(w, XmNactivateCallback, text_activate_callback, (XtPointer)info);
  XtAddCallback(w, XmNvalueChangedCallback, text_change_callback, (XtPointer)info);
  XtAddCallback(w, XmNfocusCallback, text_grab_focus_callback, (XtPointer)info);
  XtAddCallback(w, XmNlosingFocusCallback, text_lose_focus_callback, (XtPointer)info);

  return(w);
}


static prefs_info *prefs_row_with_toggle_with_text(const char *label, const char *varname, bool current_value,
						   const char *text_label, const char *text_value, int cols,
						   Widget box, Widget top_widget, 
						   void (*toggle_func)(prefs_info *prf),
						   void (*text_func)(prefs_info *prf))
{
  prefs_info *prf = NULL;
  Widget sep, sep1, lab1;
  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;
  prf->toggle_func = toggle_func;
  prf->text_func = text_func;

  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);
  prf->toggle = make_row_toggle(prf, current_value, sep, box, top_widget);
  sep1 = make_row_inner_separator(16, prf->toggle, box, top_widget);
  lab1 = make_row_inner_label(prf, text_label, sep1, box, top_widget);
  prf->text = make_row_text(prf, text_value, cols, lab1, box, top_widget);
  
  XtAddCallback(prf->toggle, XmNvalueChangedCallback, call_toggle_func, (XtPointer)prf);
  XtAddCallback(prf->text, XmNactivateCallback, call_text_func, (XtPointer)prf);

  return(prf);
}


static prefs_info *prefs_row_with_toggle_with_two_texts(const char *label, const char *varname, bool current_value,
							const char *label1, const char *text1, 
							const char *label2, const char *text2, int cols,
							Widget box, Widget top_widget,
							void (*toggle_func)(prefs_info *prf),
							void (*text_func)(prefs_info *prf))
{
  prefs_info *prf = NULL;
  Widget sep, lab1, lab2, sep1;
  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;
  prf->toggle_func = toggle_func;
  prf->text_func = text_func;

  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);
  prf->toggle = make_row_toggle(prf, current_value, sep, box, top_widget);
  sep1 = make_row_inner_separator(16, prf->toggle, box, top_widget);
  lab1 = make_row_inner_label(prf, label1, sep1, box, top_widget);
  prf->text = make_row_text(prf, text1, cols, lab1, box, top_widget);
  lab2 = make_row_inner_label(prf, label2, prf->text, box, top_widget);  
  prf->rtxt = make_row_text(prf, text2, cols, lab2, box, top_widget);

  XtAddCallback(prf->toggle, XmNvalueChangedCallback, call_toggle_func, (XtPointer)prf);
  XtAddCallback(prf->text, XmNactivateCallback, call_text_func, (XtPointer)prf);
  XtAddCallback(prf->rtxt, XmNactivateCallback, call_text_func, (XtPointer)prf);

  return(prf);
}


/* ---------------- text with toggle ---------------- */

static prefs_info *prefs_row_with_text_with_toggle(const char *label, const char *varname, bool current_value,
						   const char *toggle_label, const char *text_value, int cols,
						   Widget box, Widget top_widget, 
						   void (*toggle_func)(prefs_info *prf),
						   void (*text_func)(prefs_info *prf))
{
  prefs_info *prf = NULL;
  Widget sep, sep1, lab1;
  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;
  prf->toggle_func = toggle_func;
  prf->text_func = text_func;

  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);
  prf->text = make_row_text(prf, text_value, cols, sep, box, top_widget);
  sep1 = make_row_inner_separator(8, prf->text, box, top_widget);
  lab1 = make_row_inner_label(prf, toggle_label, sep1, box, top_widget);
  prf->toggle = make_row_toggle(prf, current_value, lab1, box, top_widget);  
  
  XtAddCallback(prf->toggle, XmNvalueChangedCallback, call_toggle_func, (XtPointer)prf);
  XtAddCallback(prf->text, XmNactivateCallback, call_text_func, (XtPointer)prf);

  return(prf);
}


/* ---------------- text with toggle ---------------- */

static prefs_info *prefs_row_with_text_and_three_toggles(const char *label, const char *varname,
							 const char *text_label, int cols,
							 const char *toggle1_label, const char *toggle2_label, const char *toggle3_label,
							 const char *text_value, 
							 bool toggle1_value, bool toggle2_value, bool toggle3_value,
							 Widget box, Widget top_widget, 
							 void (*text_func)(prefs_info *prf))
{
  prefs_info *prf = NULL;
  Widget sep, sep1, lab1, sep2, lab2, lab3, sep3, lab4;
  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;
  prf->text_func = text_func;

  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);
  lab3 = make_row_inner_label(prf, text_label, sep, box, top_widget);
  prf->text = make_row_text(prf, text_value, cols, lab3, box, top_widget);
  sep1 = make_row_inner_separator(12, prf->text, box, top_widget);
  lab1 = make_row_inner_label(prf, toggle1_label, sep1, box, top_widget);
  prf->toggle = make_row_toggle(prf, toggle1_value, lab1, box, top_widget);  
  sep2 = make_row_inner_separator(4, prf->toggle, box, top_widget);
  lab2 = make_row_inner_label(prf, toggle2_label, sep2, box, top_widget);
  prf->toggle2 = make_row_toggle(prf, toggle2_value, lab2, box, top_widget);
  sep3 = make_row_inner_separator(4, prf->toggle2, box, top_widget);
  lab4 = make_row_inner_label(prf, toggle3_label, sep3, box, top_widget);
  prf->toggle3 = make_row_toggle(prf, toggle3_value, lab4, box, top_widget);
  
  XtAddCallback(prf->text, XmNactivateCallback, call_text_func, (XtPointer)prf);

  return(prf);
}


/* ---------------- radio row ---------------- */

static void call_radio_func(Widget w, XtPointer context, XtPointer info)
{
  prefs_info *prf = (prefs_info *)context;
  if ((prf) && (prf->toggle_func))
    {
      prf->radio_button = w;
      (*(prf->toggle_func))(prf);
    }
}


static Widget make_row_radio_box(prefs_info *prf,
				 const char **labels, int num_labels, int current_value,
				 Widget box, Widget left_widget, Widget top_widget)
{
  Arg args[20];
  int i, n;
  Widget w;

  prf->radio_buttons = (Widget *)calloc(num_labels, sizeof(Widget));
  prf->num_buttons = num_labels;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, left_widget); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNborderWidth, 0); n++;
  XtSetArg(args[n], XmNborderColor, ss->white); n++;
  XtSetArg(args[n], XmNmarginHeight, 0); n++;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
  XtSetArg(args[n], XmNpacking, XmPACK_TIGHT); n++;
  w = XmCreateRadioBox(box, (char *)"radio-box", args, n);
  XtManageChild(w);

  for (i = 0; i < num_labels; i++)
    {
      Widget button;

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->white); n++;
      XtSetArg(args[n], XmNset, (i == current_value) ? XmSET : XmUNSET); n++;
      XtSetArg(args[n], XmNborderWidth, 0); n++;
      XtSetArg(args[n], XmNborderColor, ss->white); n++;
      XtSetArg(args[n], XmNmarginHeight, 0); n++;
      XtSetArg(args[n], XmNindicatorOn, XmINDICATOR_FILL); n++;
      XtSetArg(args[n], XmNindicatorSize, 14); n++;
      XtSetArg(args[n], XmNselectColor, ss->green); n++;
      XtSetArg(args[n], XmNuserData, i); n++;
      button = XtCreateManagedWidget(labels[i], xmToggleButtonWidgetClass, w, args, n);
      prf->radio_buttons[i] = button;

      XtAddCallback(button, XmNvalueChangedCallback, call_radio_func, (XtPointer)prf);
      XtAddCallback(button, XmNvalueChangedCallback, prefs_change_callback, NULL);
    }
  return(w);
}


static prefs_info *prefs_row_with_radio_box(const char *label, const char *varname, 
					    const char **labels, int num_labels, int current_value,
					    Widget box, Widget top_widget, 
					    void (*toggle_func)(prefs_info *prf))
{
  prefs_info *prf = NULL;
  Widget sep;
  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;
  prf->toggle_func = toggle_func;
  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);
  prf->toggle = make_row_radio_box(prf, labels, num_labels, current_value, box, sep, top_widget);
  return(prf);
}


/* ---------------- radio box + number ---------------- */

static prefs_info *prefs_row_with_radio_box_and_number(const char *label, const char *varname, 
						       const char **labels, int num_labels, int current_value,
						       const char *text_value, int text_cols,
						       Widget box, Widget top_widget, 
						       void (*toggle_func)(prefs_info *prf),
						       void (*arrow_up_func)(prefs_info *prf), void (*arrow_down_func)(prefs_info *prf), 
						       void (*text_func)(prefs_info *prf))
{
  prefs_info *prf = NULL;
  Widget sep;
  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;
  prf->toggle_func = toggle_func;
  prf->text_func = text_func;
  prf->arrow_up_func = arrow_up_func;
  prf->arrow_down_func = arrow_down_func;
  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);
  prf->toggle = make_row_radio_box(prf, labels, num_labels, current_value, box, sep, top_widget);
  prf->text = make_row_text(prf, text_value, text_cols, prf->toggle, box, top_widget);
  prf->arrow_up = make_row_arrows(prf, box, prf->text, top_widget);

  XtAddCallback(prf->text, XmNactivateCallback, call_text_func, (XtPointer)prf);
  return(prf);
}


/* ---------------- scale row ---------------- */

static void call_scale_func(Widget w, XtPointer context, XtPointer info)
{
  prefs_info *prf = (prefs_info *)context;
  if ((prf) && (prf->scale_func))
    (*(prf->scale_func))(prf);
}


static void call_scale_text_func(Widget w, XtPointer context, XtPointer info)
{
  prefs_info *prf = (prefs_info *)context;
  if ((prf) && (prf->text_func))
    (*(prf->text_func))(prf);
}


static void prefs_scale_callback(Widget w, XtPointer context, XtPointer info)
{
  prefs_info *prf = (prefs_info *)context;
  XmScaleCallbackStruct *cb = (XmScaleCallbackStruct *)info;
  float_to_textfield(prf->text, (cb->value * prf->scale_max) / 100.0);
}


static prefs_info *prefs_row_with_scale(const char *label, const char *varname, 
					mus_float_t max_val, mus_float_t current_value,
					Widget box, Widget top_widget, 
					void (*scale_func)(prefs_info *prf),
					void (*text_func)(prefs_info *prf))
{
  Arg args[20];
  int n;
  prefs_info *prf = NULL;
  Widget sep;
  XtCallbackList n1, n2;
  char *str;

  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;
  prf->scale_max = max_val;

  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);
  
  str = (char *)calloc(12, sizeof(char));
  snprintf(str, 12, "%.3f", current_value);
  prf->text = make_row_text(prf, str, 6, sep, box, top_widget);
  free(str);
  
  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, prf->text); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNborderWidth, 0); n++;
  XtSetArg(args[n], XmNborderColor, ss->white); n++;
  XtSetArg(args[n], XmNmarginHeight, 0); n++;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
  XtSetArg(args[n], XmNshowValue, XmNONE); n++;
  XtSetArg(args[n], XmNvalue, (int)(100 * current_value / max_val)); n++;
  XtSetArg(args[n], XmNdragCallback, n1 = make_callback_list(prefs_scale_callback, (XtPointer)prf)); n++;
  XtSetArg(args[n], XmNvalueChangedCallback, n2 = make_callback_list(prefs_scale_callback, (XtPointer)prf)); n++;
  prf->scale = XtCreateManagedWidget("", xmScaleWidgetClass, box, args, n);

  prf->scale_func = scale_func;
  prf->text_func = text_func;

  XtAddCallback(prf->scale, XmNvalueChangedCallback, call_scale_func, (XtPointer)prf);
  XtAddCallback(prf->text, XmNactivateCallback, call_scale_text_func, (XtPointer)prf);
  XtAddCallback(prf->scale, XmNvalueChangedCallback, prefs_change_callback, NULL);

  free(n1);
  free(n2);

  return(prf);
}


/* ---------------- text row ---------------- */

static prefs_info *prefs_row_with_text(const char *label, const char *varname, const char *value,
				       Widget box, Widget top_widget,
				       void (*text_func)(prefs_info *prf))
{
  prefs_info *prf = NULL;
  Widget sep;
  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;

  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);
  prf->text = make_row_text(prf, value, 0, sep, box, top_widget);

  prf->text_func = text_func;
  XtAddCallback(prf->text, XmNactivateCallback, call_text_func, (XtPointer)prf);
  return(prf);
}



/* ---------------- two texts in a row ---------------- */

static prefs_info *prefs_row_with_two_texts(const char *label, const char *varname,
					    const char *label1, const char *text1, const char *label2, const char *text2, int cols,
					    Widget box, Widget top_widget,
					    void (*text_func)(prefs_info *prf))
{
  prefs_info *prf = NULL;
  Widget sep, lab1, lab2;
  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;

  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);
  lab1 = make_row_inner_label(prf, label1, sep, box, top_widget);
  prf->text = make_row_text(prf, text1, cols, lab1, box, top_widget);
  lab2 = make_row_inner_label(prf, label2, prf->text, box, top_widget);  
  prf->rtxt = make_row_text(prf, text2, cols, lab2, box, top_widget);

  prf->text_func = text_func;
  XtAddCallback(prf->text, XmNactivateCallback, call_text_func, (XtPointer)prf);
  XtAddCallback(prf->rtxt, XmNactivateCallback, call_text_func, (XtPointer)prf);

  return(prf);
}


/* ---------------- number row ---------------- */

static prefs_info *prefs_row_with_number(const char *label, const char *varname, const char *value, int cols,
					 Widget box, Widget top_widget,
 					 void (*arrow_up_func)(prefs_info *prf), void (*arrow_down_func)(prefs_info *prf), 
					 void (*text_func)(prefs_info *prf))
{
  prefs_info *prf = NULL;
  Widget sep;
  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;

  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);
  prf->text = make_row_text(prf, value, cols, sep, box, top_widget);
  prf->arrow_up = make_row_arrows(prf, box, prf->text, top_widget);
  prf->error = make_row_error(prf, box, prf->arrow_up, top_widget);

  prf->text_func = text_func;
  prf->arrow_up_func = arrow_up_func;
  prf->arrow_down_func = arrow_down_func;

  XtAddCallback(prf->text, XmNactivateCallback, call_text_func, (XtPointer)prf);

  return(prf);
}


/* ---------------- list row ---------------- */

typedef struct {
  prefs_info *prf;
  char *value;
} list_entry;

static list_entry *make_list_entry(prefs_info *prf, const char *value)
{
  list_entry *le;
  le = (list_entry *)calloc(1, sizeof(list_entry));
  le->prf = prf;
  le->value = (char *)value;
  return(le);
}


static void prefs_list_callback(Widget w, XtPointer context, XtPointer info)
{
  list_entry *le = (list_entry *)context;
  if ((le) && (le->prf->list_func))
    (*(le->prf->list_func))(le->prf, le->value);
}


static prefs_info *prefs_row_with_list(const char *label, const char *varname, const char *value,
				       const char **values, int num_values,
				       Widget box, Widget top_widget,
				       void (*text_func)(prefs_info *prf),
				       char *(*completion_func)(widget_t w, const char *text, void *context), void *completion_context,
				       void (*list_func)(prefs_info *prf, char *value))
{
  Arg args[20];
  int n, i, cols = 0;
  prefs_info *prf = NULL;
  Widget sep, sbar;
  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;

  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);  
  
  /* get text widget size */
  for (i = 0; i < num_values; i++)
    if (values[i])
      {
	int len;
	len = strlen(values[i]);
	if (len > cols) cols = len;
      }

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, sep); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNcolumns, cols + 1); n++;
  XtSetArg(args[n], XmNvalue, value); n++;
  XtSetArg(args[n], XmNmarginHeight, 1); n++;
  XtSetArg(args[n], XmNborderWidth, 0); n++;
  XtSetArg(args[n], XmNborderColor, ss->white); n++;
  XtSetArg(args[n], XmNbottomShadowColor, ss->white); n++;
  XtSetArg(args[n], XmNshadowThickness, 0); n++;
  XtSetArg(args[n], XmNtopShadowColor, ss->white); n++;
  if (completion_func)
    prf->text = make_textfield_widget("text", box, args, n, ACTIVATABLE_BUT_NOT_FOCUSED, add_completer_func(completion_func, completion_context));
  else prf->text = make_textfield_widget("text", box, args, n, ACTIVATABLE_BUT_NOT_FOCUSED, NO_COMPLETER);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, prf->text); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNshadowThickness, 0); n++;
  XtSetArg(args[n], XmNhighlightThickness, 0); n++;
  XtSetArg(args[n], XmNmarginHeight, 0); n++;
  sbar = XmCreateMenuBar(box, (char *)"menuBar", args, n);
  XtManageChild(sbar);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  prf->list_menu = XmCreatePulldownMenu(sbar, (char *)"sort-menu", args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNsubMenuId, prf->list_menu); n++;
  XtSetArg(args[n], XmNshadowThickness, 0); n++;
  XtSetArg(args[n], XmNhighlightThickness, 0); n++;
  XtSetArg(args[n], XmNmarginHeight, 1); n++;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, prf->text); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
  prf->arrow_right = XtCreateManagedWidget(">", xmCascadeButtonWidgetClass, sbar, args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  for (i = 0; i < num_values; i++)
    if (values[i])
      {
	Widget tmp;
	tmp = XtCreateManagedWidget(values[i],  xmPushButtonWidgetClass, prf->list_menu, args, n);
	XtAddCallback(tmp, XmNactivateCallback, prefs_list_callback, make_list_entry(prf, values[i]));
	XtAddCallback(tmp, XmNactivateCallback, prefs_change_callback, NULL);
      }

  prf->error = make_row_error(prf, box, prf->arrow_right, top_widget);
  prf->text_func = text_func;
  XtAddCallback(prf->text, XmNactivateCallback, call_text_func, (XtPointer)prf);
  XtAddCallback(prf->text, XmNactivateCallback, prefs_change_callback, NULL);
  XtAddCallback(prf->arrow_right, XmNactivateCallback, prefs_change_callback, NULL);

  prf->list_func = list_func;
  return(prf);
}


/* ---------------- color selector row(s) ---------------- */

static XColor *rgb_to_color_1(mus_float_t r, mus_float_t g, mus_float_t b)
{
  Display *dpy;
  XColor *new_color;
  new_color = (XColor *)calloc(1, sizeof(XColor));
  new_color->flags = DoRed | DoGreen | DoBlue;
  new_color->red = float_to_rgb(r);
  new_color->green = float_to_rgb(g);
  new_color->blue = float_to_rgb(b);
  dpy = main_display(ss);
  XAllocColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)), new_color);
  return(new_color);
}


#define COLOR_MAX 90
#define COLOR_MAXF 90.0
#define COLOR_MARGIN 1

static color_t rgb_to_color(mus_float_t r, mus_float_t g, mus_float_t b)
{
  color_t temp;
  XColor *color;
  color = rgb_to_color_1(r, g, b);
  temp = color->pixel;
  free(color);
  return(temp);
}


static void pixel_to_rgb(Pixel pix, double *r, double *g, double *b)
{
  XColor tmp_color;
  Display *dpy;
  dpy = XtDisplay(main_shell(ss));
  tmp_color.flags = DoRed | DoGreen | DoBlue;
  tmp_color.pixel = pix;
  XQueryColor(dpy, DefaultColormap(dpy, DefaultScreen(dpy)), &tmp_color);
  (*r) = rgb_to_float(tmp_color.red);
  (*g) = rgb_to_float(tmp_color.green);
  (*b) = rgb_to_float(tmp_color.blue);
}


static void XmScrollBarGetValue(Widget w, int *val)
{
  XtVaGetValues(w, XmNvalue, val, NULL);
}


static void XmScrollBarSetValue(Widget w, int val)
{
  XtVaSetValues(w, XmNvalue, val, NULL);
}


static void reflect_color(prefs_info *prf)
{
  int ir = 0, ig = 0, ib = 0;
  mus_float_t r, g, b;
  XColor *current_color;
  Pixel pixel;

  XmScrollBarGetValue(prf->rscl, &ir);
  XmScrollBarGetValue(prf->gscl, &ig);
  XmScrollBarGetValue(prf->bscl, &ib);

  current_color = rgb_to_color_1(ir / COLOR_MAXF, ig / COLOR_MAXF, ib / COLOR_MAXF);
  r = rgb_to_float(current_color->red);
  g = rgb_to_float(current_color->green);
  b = rgb_to_float(current_color->blue);

  pixel = current_color->pixel;
  free(current_color);
  current_color = NULL;

  XtVaSetValues(prf->color, XmNbackground, pixel, NULL);
  float_to_textfield(prf->rtxt, r);
  float_to_textfield(prf->gtxt, g);
  float_to_textfield(prf->btxt, b);
}


static void prefs_color_callback(Widget w, XtPointer context, XtPointer info)
{
  reflect_color((prefs_info *)context);
}


static void unpost_color_error(XtPointer data, XtIntervalId *id)
{
  prefs_info *prf = (prefs_info *)data;
  reflect_color(prf);
  prf->got_error = false;
  black_text(prf);
  set_label(prf->label, prf->saved_label);
}


static void errors_to_color_text(const char *msg, void *data)
{
  prefs_info *prf = (prefs_info *)data;
  prf->got_error = true;
  red_text(prf);
  set_label(prf->label, msg);
  XtAppAddTimeOut(main_app(ss),
		  ERROR_WAIT_TIME,
		  (XtTimerCallbackProc)unpost_color_error,
		  data);
}


static void prefs_r_callback(Widget w, XtPointer context, XtPointer info)
{
  prefs_info *prf = (prefs_info *)context;
  char *str;
  double r;
  str = XmTextFieldGetString(w);
  redirect_errors_to(errors_to_color_text, (void *)prf);
  r = (double)string_to_mus_float_t(str, 0.0, "red amount");
  redirect_errors_to(NULL, NULL);

  XmScrollBarSetValue(prf->rscl, mus_iclamp(0, (int)(COLOR_MAX * r), COLOR_MAX));

  if (!(prf->got_error)) reflect_color(prf);
  if (str) XtFree(str);
}


static void prefs_g_callback(Widget w, XtPointer context, XtPointer info)
{
  prefs_info *prf = (prefs_info *)context;
  char *str;
  double r;
  str = XmTextFieldGetString(w);
  redirect_errors_to(errors_to_color_text, (void *)prf);
  r = (double)string_to_mus_float_t(str, 0.0, "green amount");
  redirect_errors_to(NULL, NULL);

  XmScrollBarSetValue(prf->gscl, mus_iclamp(0, (int)(COLOR_MAX * r), COLOR_MAX));

  if (!(prf->got_error)) reflect_color(prf);
  if (str) XtFree(str);
}


static void prefs_b_callback(Widget w, XtPointer context, XtPointer info)
{
  prefs_info *prf = (prefs_info *)context;
  char *str;
  double r;
  str = XmTextFieldGetString(w);
  redirect_errors_to(errors_to_color_text, (void *)prf);
  r = (double)string_to_mus_float_t(str, 0.0, "blue amount");
  redirect_errors_to(NULL, NULL);

  XmScrollBarSetValue(prf->bscl, mus_iclamp(0, (int)(COLOR_MAX * r), COLOR_MAX));

  if (!(prf->got_error)) reflect_color(prf);
  if (str) XtFree(str);
}


static void prefs_call_color_func_callback(Widget w, XtPointer context, XtPointer info)
{
  prefs_info *prf = (prefs_info *)context;
  if ((prf) && (prf->color_func))
    {
      int ir = 0, ig = 0, ib = 0;

      XmScrollBarGetValue(prf->rscl, &ir);
      XmScrollBarGetValue(prf->gscl, &ig);
      XmScrollBarGetValue(prf->bscl, &ib);

      (*(prf->color_func))(prf, (double)ir / COLOR_MAXF, (double)ig / COLOR_MAXF, (double)ib / COLOR_MAXF);
    }
}


static void scale_set_color(prefs_info *prf, color_t pixel)
{
  double r = 0.0, g = 0.0, b = 0.0;
  pixel_to_rgb(pixel, &r, &g, &b);
  float_to_textfield(prf->rtxt, r);
  XmScrollBarSetValue(prf->rscl, (int)(COLOR_MAX * r));
  float_to_textfield(prf->gtxt, g);
  XmScrollBarSetValue(prf->gscl, (int)(COLOR_MAX * g));
  float_to_textfield(prf->btxt, b);
  XmScrollBarSetValue(prf->bscl, (int)(COLOR_MAX * b));
  XtVaSetValues(prf->color, XmNbackground, pixel, NULL);
}


static Pixel red, green, blue;

static prefs_info *prefs_color_selector_row(const char *label, const char *varname, 
					    Pixel current_pixel,
					    Widget box, Widget top_widget,
					    void (*color_func)(prefs_info *prf, double r, double g, double b))
{
  Arg args[20];
  int n;
  prefs_info *prf = NULL;
  Widget sep, sep1, frame;
  XtCallbackList n1;
  double r = 0.0, g = 0.0, b = 0.0;

  prf = (prefs_info *)calloc(1, sizeof(prefs_info));
  prf->var_name = varname;
  pixel_to_rgb(current_pixel, &r, &g, &b);

  prf->label = make_row_label(prf, label, box, top_widget);
  sep = make_row_middle_separator(prf->label, box, top_widget);    

  n = 0;
  XtSetArg(args[n], XmNbackground, current_pixel); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
  XtSetArg(args[n], XmNbottomWidget, sep); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNleftWidget, sep); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
  XtSetArg(args[n], XmNrightPosition, PREFS_COLOR_POSITION); n++;
  XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_IN); n++;
  frame = XtCreateManagedWidget("frame", xmFrameWidgetClass, box, args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, current_pixel); n++;
  prf->color = XtCreateManagedWidget("", xmLabelWidgetClass, frame, args, n);

  sep1 = make_row_inner_separator(8, prf->color, box, top_widget);
  
  prf->rtxt = make_row_text(prf, NULL, 6, sep1, box, top_widget);
  float_to_textfield(prf->rtxt, r);

  prf->gtxt = make_row_text(prf, NULL, 6, prf->rtxt, box, top_widget);
  float_to_textfield(prf->gtxt, g);

  prf->btxt = make_row_text(prf, NULL, 6, prf->gtxt, box, top_widget);
  float_to_textfield(prf->btxt, b);

  /* second row = 3 scales */
  n1 = make_callback_list(prefs_color_callback, (XtPointer)prf);
  
  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNforeground, red); n++;
  XtSetArg(args[n], XmNsliderVisual, XmFOREGROUND_COLOR); n++;
  XtSetArg(args[n], XmNsliderMark, XmTHUMB_MARK); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, prf->label); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  if (FIRST_COLOR_POSITION == 0)
    {
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    }
  else
    {
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNleftPosition, FIRST_COLOR_POSITION); n++;
    }
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
  XtSetArg(args[n], XmNrightPosition, SECOND_COLOR_POSITION - COLOR_MARGIN); n++;
  XtSetArg(args[n], XmNmarginHeight, 0); n++;
  /* scale widget borders are messed up in some Motifs -- they aren't erased correctly in a scrolled window 
   *   so, try to use a scrollbar instead.
   */
  XtSetArg(args[n], XmNmaximum, 100); n++;
  XtSetArg(args[n], XmNheight, 16); n++;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
  XtSetArg(args[n], XmNshowArrows, XmNONE); n++;
  XtSetArg(args[n], XmNvalue, (int)(COLOR_MAX * r)); n++;
  XtSetArg(args[n], XmNdragCallback, n1); n++;
  XtSetArg(args[n], XmNvalueChangedCallback, n1); n++;
  prf->rscl = XtCreateManagedWidget("", xmScrollBarWidgetClass, box, args, n);


  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNforeground, green); n++;
  XtSetArg(args[n], XmNsliderVisual, XmFOREGROUND_COLOR); n++;
  XtSetArg(args[n], XmNsliderMark, XmTHUMB_MARK); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, prf->label); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
  XtSetArg(args[n], XmNleftPosition, SECOND_COLOR_POSITION + COLOR_MARGIN); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
  XtSetArg(args[n], XmNrightPosition, THIRD_COLOR_POSITION - COLOR_MARGIN); n++;
  XtSetArg(args[n], XmNmarginHeight, 0); n++;
  XtSetArg(args[n], XmNmaximum, 100); n++;
  XtSetArg(args[n], XmNheight, 16); n++;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
  XtSetArg(args[n], XmNshowArrows, XmNONE); n++;
  XtSetArg(args[n], XmNvalue, (int)(COLOR_MAX * g)); n++;
  XtSetArg(args[n], XmNdragCallback, n1); n++;
  XtSetArg(args[n], XmNvalueChangedCallback, n1); n++;
  prf->gscl = XtCreateManagedWidget("", xmScrollBarWidgetClass, box, args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNforeground, blue); n++;
  XtSetArg(args[n], XmNsliderVisual, XmFOREGROUND_COLOR); n++;
  XtSetArg(args[n], XmNsliderMark, XmTHUMB_MARK); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, prf->label); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
  XtSetArg(args[n], XmNleftPosition, THIRD_COLOR_POSITION + COLOR_MARGIN); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
  XtSetArg(args[n], XmNrightPosition, 80); n++;
  XtSetArg(args[n], XmNmarginHeight, 0); n++;
  XtSetArg(args[n], XmNmaximum, 100); n++;
  XtSetArg(args[n], XmNheight, 16); n++;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
  XtSetArg(args[n], XmNshowArrows, XmNONE); n++;
  XtSetArg(args[n], XmNvalue, (int)(COLOR_MAX * b)); n++;
  XtSetArg(args[n], XmNdragCallback, n1); n++;
  XtSetArg(args[n], XmNvalueChangedCallback, n1); n++;
  prf->bscl = XtCreateManagedWidget("", xmScrollBarWidgetClass, box, args, n);

  XtAddCallback(prf->rtxt, XmNactivateCallback, prefs_r_callback, (XtPointer)prf);
  XtAddCallback(prf->gtxt, XmNactivateCallback, prefs_g_callback, (XtPointer)prf);
  XtAddCallback(prf->btxt, XmNactivateCallback, prefs_b_callback, (XtPointer)prf);

  XtAddCallback(prf->rscl, XmNvalueChangedCallback, prefs_call_color_func_callback, (XtPointer)prf);
  XtAddCallback(prf->gscl, XmNvalueChangedCallback, prefs_call_color_func_callback, (XtPointer)prf);
  XtAddCallback(prf->bscl, XmNvalueChangedCallback, prefs_call_color_func_callback, (XtPointer)prf);

  XtAddCallback(prf->rscl, XmNvalueChangedCallback, prefs_change_callback, NULL);
  XtAddCallback(prf->gscl, XmNvalueChangedCallback, prefs_change_callback, NULL);
  XtAddCallback(prf->bscl, XmNvalueChangedCallback, prefs_change_callback, NULL);

  prf->color_func = color_func;
  free(n1);
  return(prf);
}


/* ---------------- topic separator ---------------- */

static Widget make_inter_topic_separator(Widget topics)
{
  int n;
  Arg args[20];
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
  XtSetArg(args[n], XmNheight, INTER_TOPIC_SPACE); n++;
  XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
  return(XtCreateManagedWidget("sep", xmSeparatorWidgetClass, topics, args, n));
}


/* ---------------- variable separator ---------------- */

static Widget make_inter_variable_separator(Widget topics, Widget top_widget)
{
  int n;
  Arg args[20];
  n = 0;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
  XtSetArg(args[n], XmNheight, INTER_VARIABLE_SPACE); n++;
  XtSetArg(args[n], XmNseparatorType, XmNO_LINE); n++;
  return(XtCreateManagedWidget("sep", xmSeparatorWidgetClass, topics, args, n));
}


/* ---------------- top-level contents label ---------------- */

static Widget make_top_level_label(const char *label, Widget parent)
{
  int n;
  Arg args[20];
  n = 0;
  XtSetArg(args[n], XmNbackground, ss->light_blue); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
  XtSetArg(args[n], XmNheight, 32); n++;
  return(XtCreateManagedWidget(label, xmLabelWidgetClass, parent, args, n));
}


static Widget make_top_level_box(Widget topics)
{
  Widget frame;
  int n;
  Arg args[20];
  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  frame = XtCreateManagedWidget("pref-frame", xmFrameWidgetClass, topics, args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->white); n++;
  return(XtCreateManagedWidget("pref-box", xmFormWidgetClass, frame, args, n));
}

static Widget make_inner_label(const char *label, Widget parent, Widget top_widget)
{
  int n;
  Arg args[20];
  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, top_widget); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
  XtSetArg(args[n], XmNheight, 32); n++;
  return(XtCreateManagedWidget(label, xmLabelWidgetClass, parent, args, n));
}


/* ---------------- base buttons ---------------- */

static void wm_delete_callback(Widget w, XtPointer context, XtPointer info) 
{
  clear_prefs_dialog_error();
}


static void preferences_help_callback(Widget w, XtPointer context, XtPointer info) 
{
  snd_help("preferences",
	   "This dialog sets various global variables. 'Save' then writes the new values \
to ~/.snd_prefs_ruby|forth|s7 so that they take effect the next time you start Snd.  'Revert' resets each variable either to \
its value when the Preferences dialog was started, or to the last saved value.  'Clear' resets each variable to its default value (its \
value when Snd starts, before loading initialization files). 'Help' starts this dialog, and as long as it's active, it will post helpful \
information if the mouse lingers over some variable -- sort of a tooltip that stays out of your way. \
You can also request help on a given topic by clicking the variable name on the far right.",
	   WITH_WORD_WRAP);
}


static void preferences_quit_callback(Widget w, XtPointer context, XtPointer info) 
{
  clear_prefs_dialog_error();
  if (XmGetFocusWidget(preferences_dialog) == XmMessageBoxGetChild(preferences_dialog, XmDIALOG_CANCEL_BUTTON))
    XtUnmanageChild(preferences_dialog);
}


static void prefs_set_dialog_title(const char *filename)
{
  XmString title;
  char *str;
  if (filename)
    {
      if (prefs_saved_filename) free(prefs_saved_filename);
      prefs_saved_filename = mus_strdup(filename);
    }
  if (prefs_saved_filename)
    str = mus_format("Preferences%s (saved in %s)\n",
		     (prefs_unsaved) ? "*" : "",
		     prefs_saved_filename);
  else str = mus_format("Preferences%s",
			(prefs_unsaved) ? "*" : "");
  title = XmStringCreateLocalized(str);
  free(str);
  XtVaSetValues(preferences_dialog, 
		XmNdialogTitle, title, 
		NULL);
  XmStringFree(title);
}


static void preferences_revert_callback(Widget w, XtPointer context, XtPointer info) 
{
  preferences_revert_or_clear(true);
}


static void preferences_clear_callback(Widget w, XtPointer context, XtPointer info) 
{
  preferences_revert_or_clear(false);
}


#if HAVE_EXTENSION_LANGUAGE
static void preferences_save_callback(Widget w, XtPointer context, XtPointer info) 
{
  clear_prefs_dialog_error();
  redirect_snd_error_to(post_prefs_dialog_error, NULL);
  redirect_snd_warning_to(post_prefs_dialog_error, NULL);
  save_prefs();
  redirect_snd_error_to(NULL, NULL);
  redirect_snd_warning_to(NULL, NULL);
}
#endif



/* ---------------- errors ---------------- */

static void clear_prefs_error(Widget w, XtPointer context, XtPointer info) 
{
  prefs_info *prf = (prefs_info *)context;
  XtRemoveCallback(prf->text, XmNvalueChangedCallback, clear_prefs_error, context);
  set_label(prf->error, "");
}


static void post_prefs_error(const char *msg, prefs_info *prf)
{
  prf->got_error = true;
  set_label(prf->error, msg);
  XtAddCallback(prf->text, XmNvalueChangedCallback, clear_prefs_error, (XtPointer)prf);
}


static void va_post_prefs_error(const char *msg, prefs_info *data, ...)
{
  char *buf;
  va_list ap;
  va_start(ap, data);
  buf = vstr(msg, ap);
  va_end(ap);
  post_prefs_error(buf, data);
  free(buf);
}


/* ---------------- preferences dialog ---------------- */

widget_t make_preferences_dialog(void)
{
  Arg args[20];
  Widget scroller, topics, current_sep;
  char *str;
  prefs_info *prf;

  if (preferences_dialog) 
    {
      /* I don't think this should reflect current state except when it is created */
      if (!(XtIsManaged(preferences_dialog)))
	XtManageChild(preferences_dialog);
      else raise_dialog(preferences_dialog);
      return(preferences_dialog);
    }

  /* -------- base buttons -------- */
  {
    int n;
    XmString title, help, revert, clear, save, go_away;
    Widget clear_button, revert_button;
#if HAVE_EXTENSION_LANGUAGE
    Widget save_button;

    save = XmStringCreateLocalized((char *)"Save");
#endif

    title = XmStringCreateLocalized((char *)"Preferences");
    help = XmStringCreateLocalized((char *)I_HELP);
    revert = XmStringCreateLocalized((char *)"Revert");
    clear = XmStringCreateLocalized((char *)"Clear");
    go_away = XmStringCreateLocalized((char *)I_GO_AWAY);

    n = 0;
    XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
    XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
    XtSetArg(args[n], XmNnoResize, false); n++;
    XtSetArg(args[n], XmNtransient, false); n++;
    XtSetArg(args[n], XmNcancelLabelString, go_away); n++;
    XtSetArg(args[n], XmNhelpLabelString, help); n++;
    XtSetArg(args[n], XmNokLabelString, revert); n++;
    XtSetArg(args[n], XmNdialogTitle, title); n++;
    XtSetArg(args[n], XmNallowShellResize, true); n++;
    XtSetArg(args[n], XmNautoUnmanage, false); n++;
    {
      Dimension width, height;
      width = XDisplayWidth(main_display(ss), DefaultScreen(main_display(ss)));
      height = XDisplayHeight(main_display(ss), DefaultScreen(main_display(ss)));

      XtSetArg(args[n], XmNwidth, (STARTUP_WIDTH < width) ? (Dimension)STARTUP_WIDTH : ((Dimension)(width - 50))); n++;
      XtSetArg(args[n], XmNheight, (STARTUP_HEIGHT < height) ? (Dimension)STARTUP_HEIGHT : ((Dimension)(height - 50))); n++;
    }
    preferences_dialog = XmCreateTemplateDialog(main_pane(ss), (char *)"preferences", args, n);
    revert_button = XmMessageBoxGetChild(preferences_dialog, XmDIALOG_OK_BUTTON);

    n = 0;
    XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
    XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
    clear_button = XtCreateManagedWidget("Clear", xmPushButtonGadgetClass, preferences_dialog, args, n);

#if HAVE_EXTENSION_LANGUAGE
    n = 0;
    XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
    XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
    save_button = XtCreateManagedWidget("Save", xmPushButtonGadgetClass, preferences_dialog, args, n);
    XtAddCallback(save_button, XmNactivateCallback, preferences_save_callback, NULL);
#endif

    XtAddCallback(preferences_dialog, XmNcancelCallback, preferences_quit_callback, NULL);
    XtAddCallback(preferences_dialog, XmNhelpCallback, preferences_help_callback, NULL);
    /* XtAddCallback(preferences_dialog, XmNokCallback, preferences_revert_callback, NULL); */
    XtAddCallback(revert_button, XmNactivateCallback, preferences_revert_callback, NULL);
    XtAddCallback(clear_button, XmNactivateCallback, preferences_clear_callback, NULL);
    
    XmStringFree(title);
    XmStringFree(help);
#if HAVE_EXTENSION_LANGUAGE
    XmStringFree(save);
#endif
    XmStringFree(go_away);
    XmStringFree(revert);
    XmStringFree(clear);
    
    map_over_children(preferences_dialog, set_main_color_of_widget);
#if HAVE_EXTENSION_LANGUAGE
    XtVaSetValues(XmMessageBoxGetChild(preferences_dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor,   ss->selection_color, NULL);
    XtVaSetValues(XmMessageBoxGetChild(preferences_dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color,   NULL);
#endif
    XtVaSetValues(XmMessageBoxGetChild(preferences_dialog, XmDIALOG_OK_BUTTON),     XmNarmColor,   ss->selection_color, NULL);
    XtVaSetValues(XmMessageBoxGetChild(preferences_dialog, XmDIALOG_HELP_BUTTON),   XmNarmColor,   ss->selection_color, NULL);
    XtVaSetValues(XmMessageBoxGetChild(preferences_dialog, XmDIALOG_OK_BUTTON),     XmNbackground, ss->highlight_color,   NULL);
    XtVaSetValues(XmMessageBoxGetChild(preferences_dialog, XmDIALOG_HELP_BUTTON),   XmNbackground, ss->highlight_color,   NULL);
    
    n = 0;
    XtSetArg(args[n], XmNbackground, ss->white); n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
    XtSetArg(args[n], XmNbottomWidget, XmMessageBoxGetChild(preferences_dialog, XmDIALOG_SEPARATOR)); n++;
    XtSetArg(args[n], XmNscrollingPolicy, XmAUTOMATIC); n++;
    XtSetArg(args[n], XmNscrollBarDisplayPolicy, XmSTATIC); n++;
    scroller = XmCreateScrolledWindow(preferences_dialog, (char *)"pref-scroller", args, n);
    XtManageChild(scroller);
    
    n = attach_all_sides(args, 0);
    XtSetArg(args[n], XmNbackground, ss->white); n++;
    XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
    topics = XtCreateManagedWidget("pref-topics", xmRowColumnWidgetClass, scroller, args, n);
    XtVaSetValues(scroller,
		  XmNworkWindow, topics, 
		  NULL);
  }

  red = rgb_to_color(1.0, 0.0, 0.0);
  green = rgb_to_color(0.0, 1.0, 0.0);
  blue = rgb_to_color(0.0, 0.0, 1.0);


  /* ---------------- overall behavior ---------------- */

  {
    Widget dpy_box, dpy_label, file_label, cursor_label, key_label;
    char *str1, *str2;

    /* ---------------- overall behavior ----------------*/

    dpy_box = make_top_level_box(topics);
    dpy_label = make_top_level_label("overall behavior choices", dpy_box);

    current_sep = dpy_label;
    str1 = mus_format("%d", ss->init_window_width);
    str2 = mus_format("%d", ss->init_window_height);
    rts_init_window_width = ss->init_window_width;
    rts_init_window_height = ss->init_window_height;
    prf = prefs_row_with_two_texts("start up size", S_window_width, 
				   "width:", str1, "height:", str2, 6,
				   dpy_box, current_sep,
				   startup_size_text);
    remember_pref(prf, reflect_init_window_size, save_init_window_size, help_init_window_size, clear_init_window_size, revert_init_window_size); 
    free(str2);
    free(str1);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    prf = prefs_row_with_toggle("ask before overwriting anything", S_ask_before_overwrite,
				rts_ask_before_overwrite = ask_before_overwrite(ss), 
				dpy_box, current_sep,
				overwrite_toggle);
    remember_pref(prf, reflect_ask_before_overwrite, save_ask_before_overwrite, help_ask_before_overwrite, NULL, revert_ask_before_overwrite);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    prf = prefs_row_with_toggle("ask about unsaved edits before exiting", S_ask_about_unsaved_edits,
				rts_unsaved_edits = ask_about_unsaved_edits(ss), 
				dpy_box, current_sep,
				unsaved_edits_toggle);
    remember_pref(prf, reflect_unsaved_edits, save_unsaved_edits, help_unsaved_edits, clear_unsaved_edits, revert_unsaved_edits);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    prf = prefs_row_with_toggle("include thumbnail graph in upper right corner", S_with_inset_graph,
				rts_with_inset_graph = with_inset_graph(ss),
				dpy_box, current_sep,
				with_inset_graph_toggle);
    remember_pref(prf, reflect_with_inset_graph, save_with_inset_graph, help_inset_graph, 
		  clear_with_inset_graph, revert_with_inset_graph);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    prf = prefs_row_with_toggle("resize main window as sounds open and close", S_auto_resize,
				rts_auto_resize = auto_resize(ss), 
				dpy_box, current_sep, 
				resize_toggle);
    remember_pref(prf, reflect_auto_resize, save_auto_resize, help_auto_resize, NULL, revert_auto_resize);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    prf = prefs_row_with_toggle("pointer focus", S_with_pointer_focus,
				rts_with_pointer_focus = with_pointer_focus(ss),
				dpy_box, current_sep,
				with_pointer_focus_toggle);
    remember_pref(prf, reflect_with_pointer_focus, save_with_pointer_focus, help_pointer_focus, 
		  clear_with_pointer_focus, revert_with_pointer_focus);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    rts_sync_style = sync_style(ss);
    prf = prefs_row_with_two_toggles("operate on all channels together", S_sync,
				     "within each sound", rts_sync_style == SYNC_BY_SOUND,
				     "across all sounds", rts_sync_style == SYNC_ALL,
				     dpy_box, current_sep, 
				     sync1_choice, sync2_choice);
    remember_pref(prf, reflect_sync_style, save_sync_style, help_sync_style, clear_sync_style, revert_sync_style);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    rts_remember_sound_state = remember_sound_state(ss);
    prf = prefs_row_with_toggle("restore a sound's state if reopened later", S_remember_sound_state,
				rts_remember_sound_state,
				dpy_box, current_sep, 
				toggle_remember_sound_state);
    remember_pref(prf, reflect_remember_sound_state, save_remember_sound_state, help_remember_sound_state, 
		  clear_remember_sound_state, revert_remember_sound_state);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    prf = prefs_row_with_toggle("show the control panel upon opening a sound", S_show_controls,
				rts_show_controls = in_show_controls(ss), 
				dpy_box, current_sep, 
				controls_toggle);
    remember_pref(prf, reflect_show_controls, save_show_controls, help_show_controls, NULL, revert_show_controls);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    include_peak_env_directory = mus_strdup(peak_env_dir(ss));
    rts_peak_env_directory = mus_strdup(include_peak_env_directory);
    include_peak_envs = find_peak_envs();
    rts_peak_envs = include_peak_envs;
    prf = prefs_row_with_toggle_with_text("save peak envs to speed up initial display", S_peak_env_dir,
					  include_peak_envs,
					  "directory:", include_peak_env_directory, 25,
					  dpy_box, current_sep,
					  peak_envs_toggle, peak_envs_text);
    remember_pref(prf, reflect_peak_envs, save_peak_envs, help_peak_envs, clear_peak_envs, revert_peak_envs);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    str = mus_format("%d", rts_max_regions = max_regions(ss));
    prf = prefs_row_with_toggle_with_text("selection creates an associated region", S_selection_creates_region,
					  rts_selection_creates_region = selection_creates_region(ss),
					  "max regions:", str, 5,
					  dpy_box, current_sep,
					  selection_creates_region_toggle, max_regions_text);
    remember_pref(prf, reflect_selection_creates_region, save_selection_creates_region, help_selection_creates_region, NULL, revert_selection_creates_region);
    free(str);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    rts_with_toolbar = with_toolbar(ss);
    prf = prefs_row_with_toggle("include a toolbar", S_with_toolbar,
				rts_with_toolbar, 
				dpy_box, current_sep, 
				toggle_with_toolbar);
    remember_pref(prf, reflect_with_toolbar, save_with_toolbar, help_with_toolbar, clear_with_toolbar, revert_with_toolbar);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    rts_with_tooltips = with_tooltips(ss);
    prf = prefs_row_with_toggle("enable tooltips", S_with_tooltips,
				rts_with_tooltips, 
				dpy_box, current_sep, 
				toggle_with_tooltips);
    remember_pref(prf, reflect_with_tooltips, save_with_tooltips, help_with_tooltips, clear_with_tooltips, revert_with_tooltips);



    /* ---------------- file options ---------------- */

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    file_label = make_inner_label("  file options", dpy_box, current_sep);

    rts_load_path = find_sources();
    prf = prefs_row_with_text("directory containing Snd's " Xen_language " files", "load path", 
			      rts_load_path,
			      dpy_box, file_label,
			      load_path_text);
    remember_pref(prf, reflect_load_path, NULL, help_load_path, clear_load_path, revert_load_path);
    load_path_text_widget = prf->text;

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    prf = prefs_row_with_toggle("display only sound files in various file lists", S_just_sounds,
				rts_just_sounds = just_sounds(ss), 
				dpy_box, current_sep, 
				just_sounds_toggle);
    remember_pref(prf, prefs_reflect_just_sounds, save_just_sounds, help_just_sounds, NULL, revert_just_sounds);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    rts_temp_dir = mus_strdup(temp_dir(ss));
    prf = prefs_row_with_text("directory for temporary files", S_temp_dir, 
			      temp_dir(ss), 
			      dpy_box, current_sep,
			      temp_dir_text);
    remember_pref(prf, reflect_temp_dir, save_temp_dir, help_temp_dir, NULL, revert_temp_dir);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    rts_save_dir = mus_strdup(save_dir(ss));
    prf = prefs_row_with_text("directory for save-state files", S_save_dir, 
			      save_dir(ss), 
			      dpy_box, current_sep,
			      save_dir_text);
    remember_pref(prf, reflect_save_dir, save_save_dir, help_save_dir, NULL, revert_save_dir);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    rts_save_state_file = mus_strdup(save_state_file(ss));
    prf = prefs_row_with_text("default save-state filename", S_save_state_file, 
			      save_state_file(ss), 
			      dpy_box, current_sep,
			      save_state_file_text);
    remember_pref(prf, reflect_save_state_file, save_save_state_file, help_save_state_file, NULL, revert_save_state_file);

#if HAVE_LADSPA
    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    rts_ladspa_dir = mus_strdup(ladspa_dir(ss));
    prf = prefs_row_with_text("directory for ladspa plugins", S_ladspa_dir, 
			      ladspa_dir(ss), 
			      dpy_box, current_sep,
			      ladspa_dir_text);
    remember_pref(prf, reflect_ladspa_dir, save_ladspa_dir, help_ladspa_dir, NULL, revert_ladspa_dir);
#endif

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    rts_vf_directory = mus_strdup(view_files_find_any_directory());
    prf = prefs_row_with_text("directory for view-files dialog", S_add_directory_to_view_files_list,
			      rts_vf_directory,
			      dpy_box, current_sep,
			      view_files_directory_text);
    remember_pref(prf, reflect_view_files_directory, save_view_files_directory, help_view_files_directory, NULL, revert_view_files_directory);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    rts_html_program = mus_strdup(html_program(ss));
    prf = prefs_row_with_text("external program to read HTML files via snd-help", S_html_program,
			      html_program(ss),
			      dpy_box, current_sep,
			      html_program_text);
    remember_pref(prf, reflect_html_program, save_html_program, help_html_program, NULL, revert_html_program);
    current_sep = make_inter_variable_separator(dpy_box, prf->label);

    rts_default_output_chans = default_output_chans(ss);
    prf = prefs_row_with_radio_box("default new sound attributes: chans", S_default_output_chans,
				   output_chan_choices, NUM_OUTPUT_CHAN_CHOICES, -1,
				   dpy_box, current_sep,
				   default_output_chans_choice);
    remember_pref(prf, reflect_default_output_chans, save_default_output_chans, help_default_output_chans, NULL, revert_default_output_chans);
    reflect_default_output_chans(prf);

    rts_default_output_srate = default_output_srate(ss);
    prf = prefs_row_with_radio_box("srate", S_default_output_srate,
				   output_srate_choices, NUM_OUTPUT_SRATE_CHOICES, -1,
				   dpy_box, prf->label,
				   default_output_srate_choice);
    remember_pref(prf, reflect_default_output_srate, save_default_output_srate, help_default_output_srate, NULL, revert_default_output_srate);
    reflect_default_output_srate(prf);

    rts_default_output_header_type = default_output_header_type(ss);
    prf = prefs_row_with_radio_box("header type", S_default_output_header_type,
				   output_header_type_choices, NUM_OUTPUT_HEADER_TYPE_CHOICES, -1,
				   dpy_box, prf->label,
				   default_output_header_type_choice);
    output_header_type_prf = prf;
    remember_pref(prf, reflect_default_output_header_type, save_default_output_header_type, help_default_output_header_type, NULL, revert_default_output_header_type);

    rts_default_output_sample_type = default_output_sample_type(ss);
    prf = prefs_row_with_radio_box("sample type", S_default_output_sample_type,
				   output_sample_type_choices, NUM_OUTPUT_SAMPLE_TYPE_CHOICES, -1,
				   dpy_box, prf->label,
				   default_output_sample_type_choice);
    output_sample_type_prf = prf;
    remember_pref(prf, reflect_default_output_sample_type, save_default_output_sample_type, help_default_output_sample_type, NULL, revert_default_output_sample_type);
    reflect_default_output_header_type(output_header_type_prf);
    reflect_default_output_sample_type(output_sample_type_prf);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    {
      int i, srate = 0, chans = 0;
      mus_sample_t sample_type = MUS_UNKNOWN_SAMPLE;
      mus_header_raw_defaults(&srate, &chans, &sample_type);
      rts_raw_chans = chans;
      rts_raw_srate = srate;
      rts_raw_sample_type = sample_type;
      str = mus_format("%d", chans);
      str1 = mus_format("%d", srate);
      raw_sample_type_choices = (char **)calloc(MUS_NUM_SAMPLES - 1, sizeof(char *));
      for (i = 1; i < MUS_NUM_SAMPLES; i++)
	raw_sample_type_choices[i - 1] = raw_sample_type_to_string((mus_sample_t)i); /* skip MUS_UNKNOWN_SAMPLE = 0 */
      prf = prefs_row_with_text("default raw sound attributes: chans", S_mus_header_raw_defaults, str,
				dpy_box, current_sep,
				raw_chans_choice);
      remember_pref(prf, reflect_raw_chans, save_raw_chans, help_raw_chans, NULL, revert_raw_chans);

      prf = prefs_row_with_text("srate", S_mus_header_raw_defaults, str1,
				dpy_box, prf->label,
				raw_srate_choice);
      remember_pref(prf, reflect_raw_srate, save_raw_srate, help_raw_srate, NULL, revert_raw_srate);

      prf = prefs_row_with_list("sample type", S_mus_header_raw_defaults, raw_sample_type_choices[sample_type - 1],
				(const char **)raw_sample_type_choices, MUS_NUM_SAMPLES - 1,
				dpy_box, prf->label,
				raw_sample_type_from_text,
				NULL, NULL,
				raw_sample_type_from_menu);
      remember_pref(prf, reflect_raw_sample_type, save_raw_sample_type, help_raw_sample_type, NULL, revert_raw_sample_type);
      free(str);
      free(str1);
    }


    /* ---------------- additional keys ---------------- */

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    key_label = make_inner_label("  additional keys", dpy_box, current_sep);

    {
      key_info *ki;

      ki = find_prefs_key("play-from-cursor");
      prf = prefs_row_with_text_and_three_toggles("play all chans from cursor", S_play, 
						  "key:", 8, "ctrl:", "meta:",  "C-x:",
						  ki->key, ki->c, ki->m, ki->x,
						  dpy_box, key_label,
						  bind_play_from_cursor);
      remember_pref(prf, reflect_play_from_cursor, save_pfc, help_play_from_cursor, clear_play_from_cursor, NULL);
      free(ki);

      current_sep = make_inter_variable_separator(dpy_box, prf->label);
      ki = find_prefs_key("show-all");
      prf = prefs_row_with_text_and_three_toggles("show entire sound", S_x_bounds, 
						  "key:", 8, "ctrl:", "meta:",  "C-x:",
						  ki->key, ki->c, ki->m, ki->x,
						  dpy_box, current_sep,
						  bind_show_all);
      remember_pref(prf, reflect_show_all, save_show_all, help_show_all, clear_show_all, NULL);
      free(ki);

      current_sep = make_inter_variable_separator(dpy_box, prf->label);
      ki = find_prefs_key("select-all");
      prf = prefs_row_with_text_and_three_toggles("select entire sound", S_select_all, 
						  "key:", 8, "ctrl:", "meta:",  "C-x:",
						  ki->key, ki->c, ki->m, ki->x,
						  dpy_box, current_sep,
						  bind_select_all);
      remember_pref(prf, reflect_select_all, save_select_all, help_select_all, clear_select_all, NULL);
      free(ki);

      current_sep = make_inter_variable_separator(dpy_box, prf->label);
      ki = find_prefs_key("show-selection");
      prf = prefs_row_with_text_and_three_toggles("show current selection", "show-selection", 
						  "key:", 8, "ctrl:", "meta:",  "C-x:",
						  ki->key, ki->c, ki->m, ki->x,
						  dpy_box, current_sep,
						  bind_show_selection);
      remember_pref(prf, reflect_show_selection, save_show_selection, help_show_selection, clear_show_selection, NULL);
      free(ki);

      current_sep = make_inter_variable_separator(dpy_box, prf->label);
      ki = find_prefs_key("revert-sound");
      prf = prefs_row_with_text_and_three_toggles("undo all edits (revert)", S_revert_sound, 
						  "key:", 8, "ctrl:", "meta:",  "C-x:",
						  ki->key, ki->c, ki->m, ki->x,
						  dpy_box, current_sep,
						  bind_revert);
      remember_pref(prf, reflect_revert, save_revert, help_revert, clear_revert_sound, NULL);
      free(ki);

      current_sep = make_inter_variable_separator(dpy_box, prf->label);
      ki = find_prefs_key("exit");
      prf = prefs_row_with_text_and_three_toggles("exit from Snd", S_exit, 
						  "key:", 8, "ctrl:", "meta:",  "C-x:",
						  ki->key, ki->c, ki->m, ki->x,
						  dpy_box, current_sep,
						  bind_exit);
      remember_pref(prf, reflect_exit, save_exit, help_exit, clear_exit, NULL);
      free(ki);

      current_sep = make_inter_variable_separator(dpy_box, prf->label);
      ki = find_prefs_key("goto-maxamp");
      prf = prefs_row_with_text_and_three_toggles("move cursor to channel's maximum sample", S_maxamp_position, 
						  "key:", 8, "ctrl:", "meta:",  "C-x:",
						  ki->key, ki->c, ki->m, ki->x,
						  dpy_box, current_sep,
						  bind_goto_maxamp);
      remember_pref(prf, reflect_goto_maxamp, save_goto_maxamp, help_goto_maxamp, clear_goto_maxamp, NULL);
      free(ki);

    }


    /* ---------------- cursor options ---------------- */

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    cursor_label = make_inner_label("  cursor options", dpy_box, current_sep);

    prf = prefs_row_with_toggle("report cursor location as it moves", S_with_verbose_cursor,
				rts_with_verbose_cursor = with_verbose_cursor(ss), 
				dpy_box, cursor_label, 
				with_verbose_cursor_toggle);
    remember_pref(prf, reflect_with_verbose_cursor, save_with_verbose_cursor, help_with_verbose_cursor, NULL, revert_with_verbose_cursor);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    {
      char *str1;
      str = mus_format("%.2f", rts_cursor_update_interval = cursor_update_interval(ss));
      str1 = mus_format("%d", rts_cursor_location_offset = cursor_location_offset(ss));
      prf = prefs_row_with_toggle_with_two_texts("track current location while playing", S_with_tracking_cursor,
						 (rts_with_tracking_cursor = with_tracking_cursor(ss)), 
						 "update:", str,
						 "offset:", str1, 8, 
						 dpy_box, current_sep,
						 with_tracking_cursor_toggle,
						 cursor_location_text);
      remember_pref(prf, reflect_with_tracking_cursor, save_with_tracking_cursor, help_with_tracking_cursor, NULL, revert_with_tracking_cursor);
      free(str);
      free(str1);
    }

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    str = mus_format("%d", rts_cursor_size = cursor_size(ss));
    prf = prefs_row_with_number("size", S_cursor_size,
				str, 4, 
				dpy_box, current_sep,
				cursor_size_up, cursor_size_down, cursor_size_from_text);
    remember_pref(prf, reflect_cursor_size, save_cursor_size, help_cursor_size, NULL, revert_cursor_size);
    free(str);
    if (cursor_size(ss) <= 0) XtSetSensitive(prf->arrow_down, false);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    prf = prefs_row_with_radio_box("shape", S_cursor_style,
				   cursor_styles, NUM_CURSOR_STYLES, 
				   rts_cursor_style = cursor_style(ss),
				   dpy_box, current_sep, 
				   cursor_style_choice);
    remember_pref(prf, reflect_cursor_style, save_cursor_style, help_cursor_style, NULL, revert_cursor_style);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    prf = prefs_row_with_radio_box("tracking cursor shape", S_tracking_cursor_style,
				   cursor_styles, NUM_CURSOR_STYLES, 
				   rts_tracking_cursor_style = tracking_cursor_style(ss),
				   dpy_box, current_sep, 
				   tracking_cursor_style_choice);
    remember_pref(prf, reflect_tracking_cursor_style, save_tracking_cursor_style, help_tracking_cursor_style, NULL, revert_tracking_cursor_style);

    current_sep = make_inter_variable_separator(dpy_box, prf->label);
    saved_cursor_color = ss->cursor_color;
    prf = prefs_color_selector_row("color", S_cursor_color, ss->cursor_color,
				   dpy_box, current_sep,
				   cursor_color_func);
    remember_pref(prf, NULL, save_cursor_color, help_cursor_color, clear_cursor_color, revert_cursor_color);


    /* ---------------- (overall) colors ---------------- */

    current_sep = make_inter_variable_separator(dpy_box, prf->rscl);
    cursor_label = make_inner_label("  colors", dpy_box, current_sep);
    
    saved_basic_color = ss->basic_color;
    prf = prefs_color_selector_row("main background color", S_basic_color, ss->basic_color,
				   dpy_box, cursor_label,
				   basic_color_func);
    remember_pref(prf, NULL, save_basic_color, help_basic_color, clear_basic_color, revert_basic_color);

    current_sep = make_inter_variable_separator(dpy_box, prf->rscl);
    saved_highlight_color = ss->highlight_color;
    prf = prefs_color_selector_row("main highlight color", S_highlight_color, ss->highlight_color,
				   dpy_box, current_sep,
				   highlight_color_func);
    remember_pref(prf, NULL, save_highlight_color, help_highlight_color, clear_highlight_color, revert_highlight_color);

    current_sep = make_inter_variable_separator(dpy_box, prf->rscl);
    saved_position_color = ss->position_color;
    prf = prefs_color_selector_row("second highlight color", S_position_color, ss->position_color,
				   dpy_box, current_sep,
				   position_color_func);
    remember_pref(prf, NULL, save_position_color, help_position_color, clear_position_color, revert_position_color);

    current_sep = make_inter_variable_separator(dpy_box, prf->rscl);
    saved_zoom_color = ss->zoom_color;
    prf = prefs_color_selector_row("third highlight color", S_zoom_color, ss->zoom_color,
				   dpy_box, current_sep,
				   zoom_color_func);
    remember_pref(prf, NULL, save_zoom_color, help_zoom_color, clear_zoom_color, revert_zoom_color);
  }

  current_sep = make_inter_topic_separator(topics);

  /* -------- graphs -------- */
  {
    Widget grf_box, grf_label, colgrf_label;

    /* ---------------- graph options ---------------- */

    grf_box = make_top_level_box(topics);
    grf_label = make_top_level_label("graph options", grf_box);

    prf = prefs_row_with_radio_box("how to connect the dots", S_graph_style,
				   graph_styles, NUM_GRAPH_STYLES, 
				   rts_graph_style = graph_style(ss),
				   grf_box, grf_label,
				   graph_style_choice);
    remember_pref(prf, reflect_graph_style, save_graph_style, help_graph_style, NULL, revert_graph_style);

    current_sep = make_inter_variable_separator(grf_box, prf->label);
    str = mus_format("%d", rts_dot_size = dot_size(ss));
    prf = prefs_row_with_number("dot size", S_dot_size,
				str, 4, 
				grf_box, current_sep,
				dot_size_up, dot_size_down, dot_size_from_text);
    remember_pref(prf, reflect_dot_size, save_dot_size, help_dot_size, NULL, revert_dot_size);
    free(str);
    if (dot_size(ss) <= 0) XtSetSensitive(prf->arrow_down, false);

    current_sep = make_inter_variable_separator(grf_box, prf->label);
    rts_initial_beg = initial_beg(ss);
    rts_initial_dur = initial_dur(ss);
    str = mus_format("%.2f : %.2f", rts_initial_beg, rts_initial_dur);
    prf = prefs_row_with_text_with_toggle("initial graph x bounds", S_initial_graph_hook, 
					  (rts_full_duration = show_full_duration(ss)),
					  "show full duration", str, 16,
					  grf_box, current_sep,
					  initial_bounds_toggle,
					  initial_bounds_text);
    free(str);
    remember_pref(prf, reflect_initial_bounds, save_initial_bounds, help_initial_bounds, clear_initial_bounds, revert_initial_bounds);

    current_sep = make_inter_variable_separator(grf_box, prf->label);
    prf = prefs_row_with_radio_box("how to layout multichannel graphs", S_channel_style,
				   channel_styles, NUM_CHANNEL_STYLES, 
				   rts_channel_style = channel_style(ss),
				   grf_box, current_sep,
				   channel_style_choice);
    remember_pref(prf, reflect_channel_style, save_channel_style, help_channel_style, NULL, revert_channel_style);

    current_sep = make_inter_variable_separator(grf_box, prf->label);
    prf = prefs_row_with_toggle("layout wave and fft graphs horizontally", S_graphs_horizontal,
				rts_graphs_horizontal = graphs_horizontal(ss),
				grf_box, current_sep,
				graphs_horizontal_toggle);
    remember_pref(prf, reflect_graphs_horizontal, save_graphs_horizontal, help_graphs_horizontal, NULL, revert_graphs_horizontal);

    current_sep = make_inter_variable_separator(grf_box, prf->label);
   prf = prefs_row_with_toggle("include y=0 line in sound graphs", S_show_y_zero,
				rts_show_y_zero = show_y_zero(ss),
				grf_box, current_sep,
				y_zero_toggle);
    remember_pref(prf, reflect_show_y_zero, save_show_y_zero, help_show_y_zero, NULL, revert_show_y_zero);

    current_sep = make_inter_variable_separator(grf_box, prf->label);
    rts_show_grid = show_grid(ss);
    prf = prefs_row_with_toggle("include a grid in sound graphs", S_show_grid,
				rts_show_grid == WITH_GRID,
				grf_box, current_sep,
				grid_toggle);
    remember_pref(prf, reflect_show_grid, save_show_grid, help_show_grid, NULL, revert_show_grid);

    current_sep = make_inter_variable_separator(grf_box, prf->label);
    prf = prefs_row_with_scale("grid density", S_grid_density, 
			       2.0, rts_grid_density = grid_density(ss),
			       grf_box, current_sep,
			       grid_density_scale_callback, grid_density_text_callback);
    remember_pref(prf, reflect_grid_density, save_grid_density, help_grid_density, NULL, revert_grid_density);

    current_sep = make_inter_variable_separator(grf_box, prf->label);
    rts_show_axes = show_axes(ss);
    prf = prefs_row_with_list("what axes to display", S_show_axes, show_axes_choices[(int)rts_show_axes],
			      show_axes_choices, NUM_SHOW_AXES,
			      grf_box, current_sep,
			      show_axes_from_text,
			      NULL, NULL,
			      show_axes_from_menu);
    remember_pref(prf, reflect_show_axes, save_show_axes, help_show_axes, clear_show_axes, revert_show_axes);

    current_sep = make_inter_variable_separator(grf_box, prf->label);
    rts_x_axis_style = x_axis_style(ss);
    prf = prefs_row_with_list("time division", S_x_axis_style, x_axis_styles[(int)rts_x_axis_style],
			      x_axis_styles, NUM_X_AXIS_STYLES,
			      grf_box, current_sep,
			      x_axis_style_from_text,
			      NULL, NULL,
			      x_axis_style_from_menu);
    remember_pref(prf, reflect_x_axis_style, save_x_axis_style, help_x_axis_style, clear_x_axis_style, revert_x_axis_style);

    current_sep = make_inter_variable_separator(grf_box, prf->label);
    prf = prefs_row_with_toggle("include smpte info", "show-smpte-label",
				rts_with_smpte_label = with_smpte_label(ss),
				grf_box, current_sep,
				smpte_toggle);
    remember_pref(prf, reflect_smpte, save_smpte, help_smpte, clear_smpte, revert_smpte);


    /* ---------------- (graph) colors ---------------- */

    current_sep = make_inter_variable_separator(grf_box, prf->label); 
    colgrf_label = make_inner_label("  colors", grf_box, current_sep);

    saved_data_color = ss->data_color;    
    prf = prefs_color_selector_row("unselected data (waveform) color", S_data_color, ss->data_color,
				   grf_box, colgrf_label,
				   data_color_func);
    remember_pref(prf, NULL, save_data_color, help_data_color, clear_data_color, revert_data_color);

    current_sep = make_inter_variable_separator(grf_box, prf->rscl);
    saved_graph_color = ss->graph_color;
    prf = prefs_color_selector_row("unselected graph (background) color", S_graph_color, ss->graph_color,
				   grf_box, current_sep,
				   graph_color_func);
    remember_pref(prf, NULL, save_graph_color, help_graph_color, clear_graph_color, revert_graph_color);

    current_sep = make_inter_variable_separator(grf_box, prf->rscl);
    saved_selected_data_color = ss->selected_data_color;
    prf = prefs_color_selector_row("selected channel data (waveform) color", S_selected_data_color, ss->selected_data_color,
				   grf_box, current_sep,
				   selected_data_color_func);
    remember_pref(prf, NULL, save_selected_data_color, help_selected_data_color, clear_selected_data_color, revert_selected_data_color);

    current_sep = make_inter_variable_separator(grf_box, prf->rscl);
    saved_selected_graph_color = ss->selected_graph_color;
    prf = prefs_color_selector_row("selected channel graph (background) color", S_selected_graph_color, ss->selected_graph_color,
				   grf_box, current_sep,
				   selected_graph_color_func);
    remember_pref(prf, NULL, save_selected_graph_color, help_selected_graph_color, clear_selected_graph_color, revert_selected_graph_color);

    current_sep = make_inter_variable_separator(grf_box, prf->rscl);
    saved_selection_color = ss->selection_color;
    prf = prefs_color_selector_row("selection color", S_selection_color, ss->selection_color,
				   grf_box, current_sep,
				   selection_color_func);
    remember_pref(prf, NULL, save_selection_color, help_selection_color, clear_selection_color, revert_selection_color);

    /* ---------------- (graph) fonts ---------------- */

    current_sep = make_inter_variable_separator(grf_box, prf->rscl);
    colgrf_label = make_inner_label("  fonts", grf_box, current_sep);

    rts_axis_label_font = mus_strdup(axis_label_font(ss));
    prf = prefs_row_with_text("axis label font", S_axis_label_font, 
			      axis_label_font(ss), 
			      grf_box, colgrf_label,
			      axis_label_font_text);
    remember_pref(prf, reflect_axis_label_font, save_axis_label_font, help_axis_label_font, clear_axis_label_font, revert_axis_label_font);

    current_sep = make_inter_variable_separator(grf_box, prf->label);     
    rts_axis_numbers_font = mus_strdup(axis_numbers_font(ss));
    prf = prefs_row_with_text("axis number font", S_axis_numbers_font, 
			      axis_numbers_font(ss), 
			      grf_box, current_sep,
			      axis_numbers_font_text);
    remember_pref(prf, reflect_axis_numbers_font, save_axis_numbers_font, help_axis_numbers_font, clear_axis_numbers_font, revert_axis_numbers_font);

    current_sep = make_inter_variable_separator(grf_box, prf->label);     
    rts_peaks_font = mus_strdup(peaks_font(ss));
    prf = prefs_row_with_text("fft peaks font", S_peaks_font, 
			      peaks_font(ss), 
			      grf_box, current_sep,
			      peaks_font_text);
    remember_pref(prf, reflect_peaks_font, save_peaks_font, help_peaks_font, clear_peaks_font, revert_peaks_font);

    current_sep = make_inter_variable_separator(grf_box, prf->label);     
    rts_bold_peaks_font = mus_strdup(bold_peaks_font(ss));
    prf = prefs_row_with_text("fft peaks bold font (for main peaks)", S_bold_peaks_font, 
			      bold_peaks_font(ss), 
			      grf_box, current_sep,
			      bold_peaks_font_text);
    remember_pref(prf, reflect_bold_peaks_font, save_bold_peaks_font, help_bold_peaks_font, clear_bold_peaks_font, revert_bold_peaks_font);

    current_sep = make_inter_variable_separator(grf_box, prf->label);     
    rts_tiny_font = mus_strdup(tiny_font(ss));
    prf = prefs_row_with_text("tiny font (for various annotations)", S_peaks_font, 
			      tiny_font(ss), 
			      grf_box, current_sep,
			      tiny_font_text);
    remember_pref(prf, reflect_tiny_font, save_tiny_font, help_tiny_font, clear_tiny_font, revert_tiny_font);
  }

  current_sep = make_inter_topic_separator(topics);

  /* -------- transform -------- */
  {
    Widget fft_box, fft_label;

    /* ---------------- transform options ---------------- */

    fft_box = make_top_level_box(topics);
    fft_label = make_top_level_label("transform options", fft_box);

    rts_fft_size = transform_size(ss);
    str = mus_format("%" print_mus_long, rts_fft_size);
    prf = prefs_row_with_number("size", S_transform_size,
				str, 12, 
				fft_box, fft_label, 
				fft_size_up, fft_size_down, fft_size_from_text);
    remember_pref(prf, reflect_fft_size, save_fft_size, help_fft_size, NULL, revert_fft_size);
    free(str);
    if (transform_size(ss) <= 2) XtSetSensitive(prf->arrow_down, false);

    current_sep = make_inter_variable_separator(fft_box, prf->label);
    prf = prefs_row_with_radio_box("transform graph choice", S_transform_graph_type,
				   transform_graph_types, NUM_TRANSFORM_GRAPH_TYPES, 
				   rts_transform_graph_type = transform_graph_type(ss),
				   fft_box, current_sep,
				   transform_graph_type_choice);
    remember_pref(prf, reflect_transform_graph_type, save_transform_graph_type, help_transform_graph_type, NULL, revert_transform_graph_type);

    current_sep = make_inter_variable_separator(fft_box, prf->label);
    rts_transform_type = transform_type(ss);
    prf = prefs_row_with_list("transform", S_transform_type, transform_types[rts_transform_type],
			      transform_types, NUM_BUILTIN_TRANSFORM_TYPES,
			      fft_box, current_sep,
			      transform_type_from_text,
			      transform_type_completer, NULL,
			      transform_type_from_menu);
    remember_pref(prf, reflect_transform_type, save_transform_type, help_transform_type, clear_transform_type, revert_transform_type);

    current_sep = make_inter_variable_separator(fft_box, prf->label);
    rts_fft_window = fft_window(ss);
    prf = prefs_row_with_list("data window", S_fft_window, mus_fft_window_name(rts_fft_window),
			      mus_fft_window_names(), MUS_NUM_FFT_WINDOWS,
			      fft_box, current_sep,
			      fft_window_from_text,
			      fft_window_completer, NULL,
			      fft_window_from_menu);
    remember_pref(prf, reflect_fft_window, save_fft_window, help_fft_window, clear_fft_window, revert_fft_window);

    current_sep = make_inter_variable_separator(fft_box, prf->label);
    prf = prefs_row_with_scale("data window family parameter", S_fft_window_beta, 
			       1.0, rts_fft_window_beta = fft_window_beta(ss),
			       fft_box, current_sep,
			       fft_window_beta_scale_callback, fft_window_beta_text_callback);
    remember_pref(prf, reflect_fft_window_beta, save_fft_window_beta, help_fft_window_beta, NULL, revert_fft_window_beta);

    current_sep = make_inter_variable_separator(fft_box, prf->label);
    str = mus_format("%d", rts_max_transform_peaks = max_transform_peaks(ss));
    prf = prefs_row_with_toggle_with_text("show fft peak data", S_show_transform_peaks,
					  rts_show_transform_peaks = show_transform_peaks(ss),
					  "max peaks:", str, 5,
					  fft_box, current_sep,
					  transform_peaks_toggle, max_peaks_text);
    remember_pref(prf, reflect_transform_peaks, save_transform_peaks, help_transform_peaks, NULL, revert_transform_peaks);
    free(str);

    current_sep = make_inter_variable_separator(fft_box, prf->label);
    {
      const char **cmaps;
      int i, len;
      len = num_colormaps();
      cmaps = (const char **)calloc(len, sizeof(const char *));
      for (i = 0; i < len; i++)
	cmaps[i] = (const char *)colormap_name(i);
      rts_colormap = color_map(ss);
      prf = prefs_row_with_list("sonogram colormap", S_colormap, cmaps[rts_colormap],
				cmaps, len,
				fft_box, current_sep,
				colormap_from_text,
				colormap_completer, NULL,
				colormap_from_menu);
      remember_pref(prf, reflect_colormap, save_colormap, help_colormap, clear_colormap, revert_colormap);
      free(cmaps);
    }

    current_sep = make_inter_variable_separator(fft_box, prf->label);
    prf = prefs_row_with_toggle("y axis as log magnitude (dB)", S_fft_log_magnitude,
				rts_fft_log_magnitude = fft_log_magnitude(ss),
				fft_box, current_sep,
				log_magnitude_toggle);
    remember_pref(prf, reflect_fft_log_magnitude, save_fft_log_magnitude, help_fft_log_magnitude, NULL, revert_fft_log_magnitude);

    current_sep = make_inter_variable_separator(fft_box, prf->label);
    str = mus_format("%.1f", rts_min_dB = min_dB(ss));
    prf = prefs_row_with_text("minimum y-axis dB value", S_min_dB, str,
			      fft_box, current_sep,
			      min_dB_text);
    remember_pref(prf, reflect_min_dB, save_min_dB, help_min_dB, NULL, revert_min_dB);
    free(str);

    current_sep = make_inter_variable_separator(fft_box, prf->label);
    prf = prefs_row_with_toggle("x axis as log freq", S_fft_log_frequency,
				rts_fft_log_frequency = fft_log_frequency(ss),
				fft_box, current_sep,
				log_frequency_toggle);
    remember_pref(prf, reflect_fft_log_frequency, save_fft_log_frequency, help_fft_log_frequency, NULL, revert_fft_log_frequency);

    current_sep = make_inter_variable_separator(fft_box, prf->label);
    prf = prefs_row_with_radio_box("normalization", S_transform_normalization,
				   transform_normalizations, NUM_TRANSFORM_NORMALIZATIONS, 
				   rts_transform_normalization = transform_normalization(ss),
				   fft_box, current_sep,
				   transform_normalization_choice);
    remember_pref(prf, reflect_transform_normalization, save_transform_normalization, help_transform_normalization, NULL, revert_transform_normalization);
  }

  current_sep = make_inter_topic_separator(topics);

  /* -------- marks, mixes, and regions -------- */
  {
    Widget mmr_box, mmr_label;
    char *str1, *str2;

    /* ---------------- marks and mixes ---------------- */

    mmr_box = make_top_level_box(topics);
    mmr_label = make_top_level_label("marks and mixes", mmr_box);

    saved_mark_color = ss->mark_color;
    prf = prefs_color_selector_row("mark and mix tag color", S_mark_color, ss->mark_color,
				   mmr_box, mmr_label,
				   mark_color_func);
    remember_pref(prf, NULL, save_mark_color, help_mark_color, clear_mark_color, revert_mark_color);

    current_sep = make_inter_variable_separator(mmr_box, prf->rscl);

    str1 = mus_format("%d", rts_mark_tag_width = mark_tag_width(ss));
    str2 = mus_format("%d", rts_mark_tag_height = mark_tag_height(ss));
    prf = prefs_row_with_two_texts("mark tag size", S_mark_tag_width, 
				   "width:", str1, "height:", str2, 4,
				   mmr_box, current_sep,
				   mark_tag_size_text);
    remember_pref(prf, reflect_mark_tag_size, save_mark_tag_size, help_mark_tag_size, NULL, revert_mark_tag_size);
    free(str2);
    free(str1);

    current_sep = make_inter_variable_separator(mmr_box, prf->label);
    str1 = mus_format("%d", rts_mix_tag_width = mix_tag_width(ss));
    str2 = mus_format("%d", rts_mix_tag_height = mix_tag_height(ss));
    prf = prefs_row_with_two_texts("mix tag size", S_mix_tag_width, 
				   "width:", str1, "height:", str2, 4,
				   mmr_box, current_sep,
				   mix_tag_size_text);
    remember_pref(prf, reflect_mix_tag_size, save_mix_tag_size, help_mix_tag_size, NULL, revert_mix_tag_size);
    free(str2);
    free(str1);

    current_sep = make_inter_variable_separator(mmr_box, prf->label);
    saved_mix_color = ss->mix_color;
    prf = prefs_color_selector_row("mix waveform color", S_mix_color, ss->mix_color,
				   mmr_box, current_sep,
				   mix_color_func);
    remember_pref(prf, NULL, save_mix_color, help_mix_color, clear_mix_color, revert_mix_color);

    current_sep = make_inter_variable_separator(mmr_box, prf->rscl);
    str = mus_format("%d", rts_mix_waveform_height = mix_waveform_height(ss));
    prf = prefs_row_with_toggle_with_text("show mix waveforms (attached to the mix tag)", S_show_mix_waveforms,
					  rts_show_mix_waveforms = show_mix_waveforms(ss),
					  "max waveform height:", str, 5,
					  mmr_box, current_sep,
					  show_mix_waveforms_toggle, mix_waveform_height_text);
    remember_pref(prf, reflect_mix_waveforms, save_mix_waveforms, help_mix_waveforms, NULL, revert_mix_waveforms);
    free(str);
  }
  
  current_sep = make_inter_topic_separator(topics);

  /* -------- clm -------- */
  {
    Widget clm_box, clm_label;

    /* ---------------- clm options ---------------- */

    clm_box = make_top_level_box(topics);
    clm_label = make_top_level_label("clm", clm_box);

    rts_speed_control_style = speed_control_style(ss);
    str = mus_format("%d", rts_speed_control_tones = speed_control_tones(ss));
    prf = prefs_row_with_radio_box_and_number("speed control choice", S_speed_control_style,
					      speed_control_styles, NUM_SPEED_CONTROL_STYLES, (int)speed_control_style(ss),
					      str, 6,
					      clm_box, clm_label,
					      speed_control_choice, speed_control_up, speed_control_down, speed_control_text);
    XtSetSensitive(prf->arrow_down, (speed_control_tones(ss) > MIN_SPEED_CONTROL_SEMITONES));
    remember_pref(prf, reflect_speed_control, save_speed_control, help_speed_control, NULL, revert_speed_control);
    free(str);

    current_sep = make_inter_variable_separator(clm_box, prf->label);
    str = mus_format("%d", rts_sinc_width = sinc_width(ss));
    prf = prefs_row_with_text("sinc interpolation width in srate converter", S_sinc_width, str,
			      clm_box, current_sep,
			      sinc_width_text);
    remember_pref(prf, reflect_sinc_width, save_sinc_width, help_sinc_width, NULL, revert_sinc_width);
    free(str);
  }

  current_sep = make_inter_topic_separator(topics);

  /* -------- programming -------- */
  {
    Widget prg_box, prg_label;

    /* ---------------- listener options ---------------- */

    prg_box = make_top_level_box(topics);
    prg_label = make_top_level_label("listener options", prg_box);

    prf = prefs_row_with_toggle("show listener at start up", S_show_listener,
				rts_show_listener = listener_is_visible(),
				prg_box, prg_label,
				show_listener_toggle);
    remember_pref(prf, reflect_show_listener, save_show_listener, help_show_listener, clear_show_listener, revert_show_listener);

    current_sep = make_inter_variable_separator(prg_box, prf->label);
    rts_listener_prompt = mus_strdup(listener_prompt(ss));
    prf = prefs_row_with_text("prompt", S_listener_prompt, 
			      listener_prompt(ss), 
			      prg_box, current_sep,
			      listener_prompt_text);
    remember_pref(prf, reflect_listener_prompt, save_listener_prompt, help_listener_prompt, NULL, revert_listener_prompt);

    current_sep = make_inter_variable_separator(prg_box, prf->label);
    str = mus_format("%d", rts_print_length = print_length(ss));
    prf = prefs_row_with_text("number of vector elements to display", S_print_length, str,
			      prg_box, current_sep,
			      print_length_text);
    remember_pref(prf, reflect_print_length, save_print_length, help_print_length, NULL, revert_print_length);
    free(str);

    current_sep = make_inter_variable_separator(prg_box, prf->label);
    rts_listener_font = mus_strdup(listener_font(ss));
    prf = prefs_row_with_text("font", S_listener_font, 
			      listener_font(ss), 
			      prg_box, current_sep,
			      listener_font_text);
    remember_pref(prf, reflect_listener_font, save_listener_font, help_listener_font, clear_listener_font, revert_listener_font);

    current_sep = make_inter_variable_separator(prg_box, prf->label);
    saved_listener_color = ss->listener_color;
    prf = prefs_color_selector_row("background color", S_listener_color, ss->listener_color,
				   prg_box, current_sep,
				   listener_color_func);
    remember_pref(prf, NULL, save_listener_color, help_listener_color, clear_listener_color, revert_listener_color);

    current_sep = make_inter_variable_separator(prg_box, prf->rscl);
    saved_listener_text_color = ss->listener_text_color;
    prf = prefs_color_selector_row("text color", S_listener_text_color, ss->listener_text_color,
				   prg_box, current_sep,
				   listener_text_color_func);
    remember_pref(prf, NULL, save_listener_text_color, help_listener_text_color, clear_listener_text_color, revert_listener_text_color);
  }

  current_sep = make_inter_topic_separator(topics);

  /* -------- audio -------- */
  {
    Widget aud_box, aud_label;

    /* ---------------- audio options ---------------- */

    aud_box = make_top_level_box(topics);
    aud_label = make_top_level_label("audio options", aud_box);

    str = mus_format("%d", rts_dac_size = dac_size(ss));
    prf = prefs_row_with_text("dac buffer size", S_dac_size, 
			      str,
			      aud_box, aud_label,
			      dac_size_text);
    remember_pref(prf, reflect_dac_size, save_dac_size, help_dac_size, NULL, revert_dac_size);
    free(str);

    current_sep = make_inter_variable_separator(aud_box, prf->label);
    prf = prefs_row_with_toggle("fold in otherwise unplayable channels", S_dac_combines_channels,
				rts_dac_combines_channels = dac_combines_channels(ss),
				aud_box, current_sep,
				dac_combines_channels_toggle);
    remember_pref(prf, reflect_dac_combines_channels, save_dac_combines_channels, help_dac_combines_channels, NULL, revert_dac_combines_channels);
  }

  {
    Atom wm_delete_window;
    wm_delete_window = XmInternAtom(main_display(ss), (char *)"WM_DELETE_WINDOW", false);
    XmAddWMProtocolCallback(XtParent(preferences_dialog), wm_delete_window, wm_delete_callback, NULL);
  }

  XtManageChild(preferences_dialog);
  set_dialog_widget(PREFERENCES_DIALOG, preferences_dialog);

  return(preferences_dialog);
}


#include "snd-menu.h"
#include <X11/cursorfont.h>

/* X side of file print */

static Widget print_dialog = NULL;
static Widget print_name = NULL;
static Widget print_eps_or_lpr = NULL;
static char print_string[PRINT_BUFFER_SIZE];
static Widget error_info_box, error_info_frame, error_info;

static void print_help_callback(Widget w, XtPointer context, XtPointer info)
{
  print_dialog_help();
}


static void clear_print_error(void);

static void print_cancel_callback(Widget w, XtPointer context, XtPointer info)
{
  if (XmGetFocusWidget(print_dialog) == XmMessageBoxGetChild(print_dialog, XmDIALOG_CANCEL_BUTTON))
    {
      ss->print_choice = PRINT_SND;
      clear_print_error();
      XtUnmanageChild(print_dialog);
    }
  /* else it's the <cr> from the text widget probably */
}


static int lpr(char *name)
{
  /* make some desultory effort to print the file */
  snprintf(print_string, PRINT_BUFFER_SIZE, "lpr %s", name);
  return(system(print_string));
}


static void watch_print(Widget w, XtPointer context, XtPointer info)
{
  clear_print_error();
}


static Widget rc;
static bool print_watching = false, print_error = false;

static void clear_print_error(void)
{
  XtUnmanageChild(rc);
  XtUnmanageChild(error_info_box);
  XtVaSetValues(print_eps_or_lpr, XmNbottomAttachment, XmATTACH_FORM, NULL);
  XtManageChild(rc);
  print_error = false;
  if (print_watching)
    {
      print_watching = false;
      XtRemoveCallback(print_name, XmNvalueChangedCallback, watch_print, NULL);
      XtRemoveCallback(print_eps_or_lpr, XmNvalueChangedCallback, watch_print, NULL);
    }
}


static void report_in_error_info(const char *msg, void *ignore)
{
  XmString s1;
  if ((!msg) || (!(*msg))) return;
  print_error = true;
  s1 = XmStringCreateLocalized((char *)msg);
  XtVaSetValues(error_info, XmNlabelString, s1, NULL);
  if (!(XtIsManaged(error_info_box)))
    {
      Dimension text_wid = 0, dialog_wid = 0;
      XmFontList fonts;

      XtVaGetValues(error_info, XmNfontList, &fonts, NULL);
      XtVaGetValues(print_dialog, XmNwidth, &dialog_wid, NULL);
      text_wid = XmStringWidth(fonts, s1);
      XtUnmanageChild(rc);

      XtVaSetValues(print_eps_or_lpr, XmNbottomAttachment, XmATTACH_NONE, NULL);
      if (text_wid > dialog_wid)
	{
	  XtUnmanageChild(print_dialog);
	  XtVaSetValues(print_dialog, XmNwidth, text_wid + 40, NULL);
	  XtManageChild(print_dialog);
	}
      XtManageChild(error_info_box);
      XtManageChild(rc);
      print_watching = true;
      XtAddCallback(print_name, XmNvalueChangedCallback, watch_print, NULL);
      XtAddCallback(print_eps_or_lpr, XmNvalueChangedCallback, watch_print, NULL);
    }
  XmStringFree(s1);
}


static printing_t printing = NOT_PRINTING;

static void print_ok_callback(Widget w, XtPointer context, XtPointer info)
{
  bool quit = false;
  XmString plab, slab;
  snd_info *nsp = NULL;

  if (printing) 
    ss->stopped_explicitly = true;
  else
    {
      bool print_it;

      clear_print_error();
      if (ss->print_choice == PRINT_SND)
	{
	  plab = XmStringCreateLocalized((char *)I_STOP);
	  nsp = any_selected_sound();
	  snprintf(print_string, PRINT_BUFFER_SIZE, "printing %s", nsp->short_filename);
	  slab = XmStringCreateLocalized(print_string);
	  XtVaSetValues(print_dialog, 
			XmNokLabelString, plab, 
			XmNmessageString, slab, 
			NULL);
	  XmStringFree(plab);
	  XmStringFree(slab);
	}

      printing = PRINTING;
      print_it = (bool)XmToggleButtonGetState(print_eps_or_lpr);
      quit = (ss->print_choice == PRINT_ENV);

      if (print_it)
	{
	  char *name;
	  name = snd_tempnam();

	  redirect_snd_error_to(report_in_error_info, NULL);
	  switch (ss->print_choice)
	    {
	    case PRINT_SND: 
	      snd_print(name);
	      break;

	    case PRINT_ENV: 
	      enved_print(name); 
	      break;
	    }
	  redirect_snd_error_to(NULL, NULL);
	  if (!print_error)
	    {
	      int err;
	      err = lpr(name); /* lpr apparently insists on printing to stderr? */
	      if (err != 0)
		report_in_error_info("can't print!", NULL);
	      snd_remove(name, IGNORE_CACHE);
	    }
	  free(name);
	}
      else 
	{
	  char *str = NULL;
	  redirect_snd_error_to(report_in_error_info, NULL);
	  str = XmTextGetString(print_name);
	  switch (ss->print_choice)
	    {
	    case PRINT_SND: 
	      if (snd_print(str))
		status_report(nsp, "printed current view to %s", str);
	      break;

	    case PRINT_ENV: 
	      enved_print(str); 
	      break;
	    }
	  redirect_snd_error_to(NULL, NULL);
	  if (str) XtFree(str);
	}
    }

  printing = NOT_PRINTING;
  if (ss->print_choice == PRINT_SND)
    {
      plab = XmStringCreateLocalized((char *)"Print");
      snprintf(print_string, PRINT_BUFFER_SIZE, "print %s", nsp->short_filename);
      slab = XmStringCreateLocalized(print_string);
      XtVaSetValues(print_dialog, 
		    XmNokLabelString, plab, 
		    XmNmessageString, slab, 
		    NULL);
      XmStringFree(plab);
      XmStringFree(slab);
    }
  ss->print_choice = PRINT_SND;
  if (quit) 
    XtUnmanageChild(print_dialog);
}


static void start_print_dialog(XmString xmstr4, bool managed)
{
  if (!print_dialog)
    {
      Widget dl;
      XmString xmstr1, xmstr2, xmstr3, titlestr;
      Arg args[20];
      int n;

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      xmstr1 = XmStringCreateLocalized((char *)"Print");  /* "ok" here is confusing -- might mean, ok I'm done */
      xmstr2 = XmStringCreateLocalized((char *)I_HELP);
      xmstr3 = XmStringCreateLocalized((char *)I_GO_AWAY);
      titlestr = XmStringCreateLocalized((char *)"Print");

      XtSetArg(args[n], XmNmessageString, xmstr4); n++;
      XtSetArg(args[n], XmNokLabelString, xmstr1); n++;
      XtSetArg(args[n], XmNhelpLabelString, xmstr2); n++;
      XtSetArg(args[n], XmNcancelLabelString, xmstr3); n++;
      XtSetArg(args[n], XmNautoUnmanage, false); n++;
      XtSetArg(args[n], XmNdialogTitle, titlestr); n++;
      XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
      XtSetArg(args[n], XmNallowResize, true); n++;
      XtSetArg(args[n], XmNnoResize, false); n++;
      XtSetArg(args[n], XmNtransient, false); n++; /* this gives us the resize handles */
      print_dialog = XmCreateMessageDialog(main_pane(ss), (char *)"eps file:", args, n);

      XtVaSetValues(XmMessageBoxGetChild(print_dialog, XmDIALOG_MESSAGE_LABEL), XmNbackground, ss->basic_color, NULL);

      XmStringFree(xmstr1);
      XmStringFree(xmstr2);
      XmStringFree(xmstr3);
      XmStringFree(titlestr);

      XtUnmanageChild(XmMessageBoxGetChild(print_dialog, XmDIALOG_SYMBOL_LABEL));

      XtAddCallback(print_dialog, XmNhelpCallback, print_help_callback, NULL);
      XtAddCallback(print_dialog, XmNcancelCallback, print_cancel_callback, NULL);
      XtAddCallback(print_dialog, XmNokCallback, print_ok_callback, NULL);

      n = 0;
      rc = XtCreateManagedWidget("form", xmFormWidgetClass, print_dialog, args, n);

      n = 0;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      dl = XtCreateManagedWidget("eps file:", xmLabelWidgetClass, rc, args, n);

      n = 0;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, dl); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNvalue, eps_file(ss)); n++;
      print_name = make_textfield_widget("text", rc, args, n, ACTIVATABLE, NO_COMPLETER);

      n = 0;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, print_name); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      print_eps_or_lpr = make_togglebutton_widget("direct to printer", rc, args, n);

      /* error display */

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, print_eps_or_lpr); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNallowResize, true); n++; 
      XtSetArg(args[n], XmNmargin, 0); n++;
      error_info_box = XtCreateWidget("error-box", xmRowColumnWidgetClass, rc, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNmarginHeight, 4); n++;
      error_info_frame = XtCreateManagedWidget("error-frame", xmFrameWidgetClass, error_info_box, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      error_info = XtCreateManagedWidget("error-info", xmLabelWidgetClass, error_info_frame, args, n);

      XtVaSetValues(print_eps_or_lpr, XmNbottomAttachment, XmATTACH_FORM, NULL);

      if (managed) XtManageChild(print_dialog);

      map_over_children(print_dialog, set_main_color_of_widget);
      XtVaSetValues(XmMessageBoxGetChild(print_dialog, XmDIALOG_OK_BUTTON),     XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(print_dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(print_dialog, XmDIALOG_HELP_BUTTON),   XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(print_dialog, XmDIALOG_OK_BUTTON),     XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(print_dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(XmMessageBoxGetChild(print_dialog, XmDIALOG_HELP_BUTTON),   XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(print_eps_or_lpr, XmNselectColor, ss->selection_color, NULL);

      set_dialog_widget(PRINT_DIALOG, print_dialog);
    }
  else
    {
      XtVaSetValues(print_dialog, XmNmessageString, xmstr4, NULL);
      if (managed)
	{
	  if (!XtIsManaged(print_dialog))
	    XtManageChild(print_dialog);
	  raise_dialog(print_dialog); /* a no-op unless already managed */
	}
    }
}


widget_t make_file_print_dialog(bool managed, bool direct_to_printer)
{
  XmString xmstr4;
  xmstr4 = XmStringCreateLocalized((char *)"print");
  start_print_dialog(xmstr4, managed);
  XmStringFree(xmstr4);
  XmToggleButtonSetState(print_eps_or_lpr, direct_to_printer, false);
  return(print_dialog);
}


static void file_print_callback(Widget w, XtPointer context, XtPointer info)
{
  XmString xmstr4;
  if (ss->print_choice == PRINT_SND)
    {
      snd_info *nsp;
      nsp = any_selected_sound();
      if (!nsp) return;
      snprintf(print_string, PRINT_BUFFER_SIZE, "print %s", nsp->short_filename);
      xmstr4 = XmStringCreateLocalized(print_string);
    }
  else xmstr4 = XmStringCreateLocalized((char *)"print env");
  start_print_dialog(xmstr4, true);
  XmStringFree(xmstr4);
}


void save_print_dialog_state(FILE *fd)
{
  if ((print_dialog) && (XtIsManaged(print_dialog)))
    {
#if HAVE_SCHEME
      fprintf(fd, "(%s #t %s)\n", S_print_dialog, ((bool)(XmToggleButtonGetState(print_eps_or_lpr))) ? "#t" : "#f");
#endif
#if HAVE_RUBY
      fprintf(fd, "%s(true, %s)\n", to_proc_name(S_print_dialog), ((bool)(XmToggleButtonGetState(print_eps_or_lpr))) ? "true" : "false");
#endif
#if HAVE_FORTH
      fprintf(fd, "#t %s %s drop\n", ((bool)(XmToggleButtonGetState(print_eps_or_lpr))) ? "#t" : "#f", S_print_dialog);
#endif
    }
}



void set_menu_label(Widget w, const char *label) {if (w) set_button_label(w, label);}


/* -------------------------------- FILE MENU -------------------------------- */

static Widget *recent_file_items = NULL;
static int recent_file_items_size = 0;

static void open_recent_file_callback(Widget w, XtPointer context, XtPointer info)
{
  char *filename;
  snd_info *sp;
  filename = get_label(w);
  ss->open_requestor = FROM_OPEN_RECENT_MENU;
  ss->open_requestor_data = NULL;
  sp = snd_open_file(filename, FILE_READ_WRITE);
  if (sp) select_channel(sp, 0);
}


static void file_open_recent_callback(Widget w, XtPointer info, XtPointer context) 
{
  int size;
  size = recent_files_size();
  if (size > 0)
    {
      int i;
      char **recent_file_names;

      if (size > recent_file_items_size)
	{
	  if (recent_file_items_size == 0)
	    recent_file_items = (Widget *)calloc(size, sizeof(Widget));
	  else
	    {
	      recent_file_items = (Widget *)realloc(recent_file_items, size * sizeof(Widget));
	      for (i = recent_file_items_size; i < size; i++)
		recent_file_items[i] = NULL;
	    }
	  recent_file_items_size = size;
	}

      recent_file_names = recent_files();

      for (i = 0; i < size; i++)
	{
	  if (!recent_file_items[i])
	    {
	      int n = 0;
	      Arg args[6];

	      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;

	      recent_file_items[i] = XtCreateManagedWidget(recent_file_names[i], xmPushButtonWidgetClass, file_open_recent_menu, args, n);
	      XtAddCallback(recent_file_items[i], XmNactivateCallback, open_recent_file_callback, NULL); 
	    }
	  else
	    {
	      set_label(recent_file_items[i], recent_file_names[i]);
	      XtManageChild(recent_file_items[i]);
	    }
	}

      for (i = size; i < recent_file_items_size; i++) /* maybe previous file was deleted */
	if ((recent_file_items[i]) &&
	    (XtIsManaged(recent_file_items[i])))
	  XtUnmanageChild(recent_file_items[i]);
    }
}


static void make_open_recent_menu(void)
{
  int n = 0;
  Arg args[6];
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNpositionIndex, 1); n++;  /* just after "Open" menu */
  
  file_open_recent_menu = XmCreatePulldownMenu(file_menu, (char *)"open-recent", args, n);
	  
  XtSetArg(args[n], XmNsubMenuId, file_open_recent_menu); n++;

  file_open_recent_cascade_menu = XtCreateManagedWidget("Open recent", xmCascadeButtonWidgetClass, file_menu, args, n);
  XtAddCallback(file_open_recent_cascade_menu, XmNcascadingCallback, file_open_recent_callback, NULL);
}


static void file_menu_update_1(Widget w, XtPointer info, XtPointer context) 
{
  if (recent_files_size() > 0)
    {
      if (!file_open_recent_menu)
	make_open_recent_menu();
      else set_sensitive(file_open_recent_cascade_menu, true);
    }
  else
    {
      if (file_open_recent_menu)
	set_sensitive(file_open_recent_cascade_menu, false);
    }
    
  file_menu_update();
}


static void file_open_callback(Widget w, XtPointer info, XtPointer context) {make_open_file_dialog(FILE_READ_WRITE, true);}
static void file_view_callback(Widget w, XtPointer info, XtPointer context) {make_open_file_dialog(FILE_READ_ONLY, true);}
static void file_new_callback(Widget w, XtPointer info, XtPointer context) {make_new_file_dialog(true);}
static void file_close_callback(Widget w, XtPointer info, XtPointer context) {if (any_selected_sound()) snd_close_file(any_selected_sound());}
static void file_close_all_callback(Widget w, XtPointer info, XtPointer context) {for_each_sound(snd_close_file);}
static void file_save_callback(Widget w, XtPointer info, XtPointer context) {if (any_selected_sound()) save_edits_from_kbd(any_selected_sound());}
static void file_update_callback(Widget w, XtPointer info, XtPointer context) {update_file_from_menu();}
static void file_save_as_callback(Widget w, XtPointer info, XtPointer context) {make_sound_save_as_dialog(true);}
static void file_revert_callback(Widget w, XtPointer info, XtPointer context) {revert_file_from_menu();}
static void file_exit_callback(Widget w, XtPointer info, XtPointer context) {if (snd_exit_cleanly(EXIT_NOT_FORCED)) snd_exit(1);}
static void file_mix_callback_1(Widget w, XtPointer info, XtPointer context) {make_mix_file_dialog(true);}
static void file_insert_callback_1(Widget w, XtPointer info, XtPointer context) {make_insert_file_dialog(true);}
static void file_print_callback_1(Widget w, XtPointer info, XtPointer context) {file_print_callback(w, info, context);}


/* -------------------------------- EDIT MENU -------------------------------- */

static void edit_mix_callback(Widget w, XtPointer info, XtPointer context) {add_selection_or_region(0, selected_channel());}
static void edit_envelope_callback(Widget w, XtPointer info, XtPointer context) {create_envelope_editor();}
static void edit_cut_callback(Widget w, XtPointer info, XtPointer context) {delete_selection(UPDATE_DISPLAY);}
static void edit_paste_callback(Widget w, XtPointer info, XtPointer context) {insert_selection_from_menu();}
static void edit_save_as_callback(Widget w, XtPointer info, XtPointer context) {make_selection_save_as_dialog(true);}
static void edit_select_all_callback(Widget w, XtPointer info, XtPointer context) {select_all(current_channel());}
static void edit_unselect_callback(Widget w, XtPointer info, XtPointer context) {deactivate_selection();}
static void edit_undo_callback(Widget w, XtPointer info, XtPointer context) {undo_edit_with_sync(current_channel(), 1);}
static void edit_redo_callback(Widget w, XtPointer info, XtPointer context) {redo_edit_with_sync(current_channel(), 1);}

static void edit_menu_update_1(Widget w, XtPointer info, XtPointer context) {edit_menu_update();}


#if WITH_AUDIO
static void edit_play_callback(Widget w, XtPointer info, XtPointer context) 
{
  if (ss->selection_play_stop)
    {
      stop_playing_all_sounds(PLAY_BUTTON_UNSET);
      reflect_play_selection_stop(); /* if there was an error, stop_playing might not remember to clear this */
    }
  else
    {
      set_menu_label(edit_play_menu, I_STOP);
      ss->selection_play_stop = true;
      play_selection(IN_BACKGROUND);
    }
}


void reflect_play_selection_stop(void)
{
  set_menu_label(edit_play_menu, "Play Selection");
  ss->selection_play_stop = false;
}

#else

void reflect_play_selection_stop(void)
{
}

#endif


static void edit_header_callback_1(Widget w, XtPointer info, XtPointer context)
{
  snd_info *sp;
  sp = any_selected_sound();
  if (sp) edit_header(sp);
}


#if HAVE_EXTENSION_LANGUAGE
static void edit_find_callback_1(Widget w, XtPointer info, XtPointer context) 
{
  edit_find_callback(w, info, context);
}
#endif



/* -------------------------------- VIEW MENU -------------------------------- */

static Widget *view_files_items = NULL;
static Widget view_files_cascade_menu = NULL;
static int view_files_items_size = 0;


static void view_files_item_callback(Widget w, XtPointer context, XtPointer info)
{
  view_files_start_dialog_with_title(get_label(w));
}


static void view_files_callback(Widget w, XtPointer info, XtPointer context) 
{
  int size;

  size = view_files_dialog_list_length();
  if (size == 0)
    make_view_files_dialog(true, true); /* managed and empty (brand-new) */
  else
    {
      if (size == 1)
	make_view_files_dialog(true, false); /* raise current */
      else
	{
	  int i;
	  char **view_files_names;
	  
	  if ((XmIsPushButton(view_files_menu)) && /* autotest check */
	      (!view_files_cascade_menu))
	    return;
	  
	  view_files_names = view_files_dialog_titles();
	  if (size > view_files_items_size)
	    {
	      if (view_files_items_size == 0)
		view_files_items = (Widget *)calloc(size, sizeof(Widget));
	      else
		{
		  view_files_items = (Widget *)realloc(view_files_items, size * sizeof(Widget));
		  for (i = view_files_items_size; i < size; i++)
		    view_files_items[i] = NULL;
		}
	      view_files_items_size = size;
	    }
	  
	  for (i = 0; i < size; i++)
	    {
	      if (!view_files_items[i])
		{
		  int n = 0;
		  Arg args[6];
		  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
		  
		  view_files_items[i] = XtCreateManagedWidget(view_files_names[i], xmPushButtonWidgetClass, view_files_menu, args, n);
		  XtAddCallback(view_files_items[i], XmNactivateCallback, view_files_item_callback, NULL); 
		}
	      else
		{
		  set_label(view_files_items[i], view_files_names[i]);
		  XtManageChild(view_files_items[i]);
		}
	      free(view_files_names[i]);
	    }
	  free(view_files_names);
	}
    }
}


static void make_view_files_list_menu(void)
{
  int n = 0, pos = 2;
  Arg args[6];

  if ((view_files_menu) &&
      (XmIsPushButton(view_files_menu)))
    {
      XtVaGetValues(view_files_menu, XmNpositionIndex, &pos, NULL);
      XtUnmanageChild(view_files_menu);
    }
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNpositionIndex, pos); n++;
  
  view_files_menu = XmCreatePulldownMenu(view_menu, (char *)"view-files", args, n);
  XtSetArg(args[n], XmNsubMenuId, view_files_menu); n++;

  view_files_cascade_menu = XtCreateManagedWidget("Files", xmCascadeButtonWidgetClass, view_menu, args, n);
  XtAddCallback(view_files_cascade_menu, XmNcascadingCallback, view_files_callback, NULL);
}


static void view_menu_update_1(Widget w, XtPointer info, XtPointer context) 
{
  if ((view_files_dialog_list_length() > 1) &&
      (!view_files_cascade_menu))
    make_view_files_list_menu();
    
  view_menu_update();
}



static void view_separate_callback(Widget w, XtPointer info, XtPointer context) {set_channel_style(CHANNELS_SEPARATE);}
static void view_combined_callback(Widget w, XtPointer info, XtPointer context) {set_channel_style(CHANNELS_COMBINED);}
static void view_superimposed_callback(Widget w, XtPointer info, XtPointer context) {set_channel_style(CHANNELS_SUPERIMPOSED);}
static void view_dots_callback(Widget w, XtPointer info, XtPointer context) {set_graph_style(GRAPH_DOTS);}
static void view_lines_callback(Widget w, XtPointer info, XtPointer context) {set_graph_style(GRAPH_LINES);}
static void view_filled_callback(Widget w, XtPointer info, XtPointer context) {set_graph_style(GRAPH_FILLED);}
static void view_dots_and_lines_callback(Widget w, XtPointer info, XtPointer context) {set_graph_style(GRAPH_DOTS_AND_LINES);}
static void view_lollipops_callback(Widget w, XtPointer info, XtPointer context) {set_graph_style(GRAPH_LOLLIPOPS);}
#if HAVE_EXTENSION_LANGUAGE
static void view_listener_callback(Widget w, XtPointer info, XtPointer context) {handle_listener(!(listener_is_visible()));}
#endif
static void view_mix_dialog_callback(Widget w, XtPointer info, XtPointer context) {make_mix_dialog();}
static void view_zero_callback(Widget w, XtPointer info, XtPointer context){set_show_y_zero((!(show_y_zero(ss))));}
static void view_cursor_callback(Widget w, XtPointer info, XtPointer context){set_with_verbose_cursor((!(with_verbose_cursor(ss))));}

#if HAVE_EXTENSION_LANGUAGE
static void view_inset_callback(Widget w, XtPointer info, XtPointer context)
{
  set_with_inset_graph((!(with_inset_graph(ss))));
  for_each_chan(update_graph);
}
#endif

static void view_controls_callback(Widget w, XtPointer info, XtPointer context) {set_show_controls(!in_show_controls(ss));}
static void view_region_callback_1(Widget w, XtPointer info, XtPointer context) {view_region_callback(w, info, context);}
static void view_color_orientation_callback_1(Widget w, XtPointer info, XtPointer context) {view_color_orientation_callback(w, info, context);}

static void view_x_axis_seconds_callback(Widget w, XtPointer info, XtPointer context) {set_x_axis_style(X_AXIS_IN_SECONDS);}
static void view_x_axis_clock_callback(Widget w, XtPointer info, XtPointer context) {set_x_axis_style(X_AXIS_AS_CLOCK);}
static void view_x_axis_beats_callback(Widget w, XtPointer info, XtPointer context) {set_x_axis_style(X_AXIS_IN_BEATS);}
static void view_x_axis_measures_callback(Widget w, XtPointer info, XtPointer context) {set_x_axis_style(X_AXIS_IN_MEASURES);}
static void view_x_axis_samples_callback(Widget w, XtPointer info, XtPointer context) {set_x_axis_style(X_AXIS_IN_SAMPLES);}
static void view_x_axis_percentage_callback(Widget w, XtPointer info, XtPointer context) {set_x_axis_style(X_AXIS_AS_PERCENTAGE);}

static void view_no_axes_callback(Widget w, XtPointer info, XtPointer context) {set_show_axes(SHOW_NO_AXES);}
static void view_all_axes_callback(Widget w, XtPointer info, XtPointer context) {set_show_axes(SHOW_ALL_AXES);}
static void view_just_x_axis_callback(Widget w, XtPointer info, XtPointer context) {set_show_axes(SHOW_X_AXIS);}
static void view_all_axes_unlabelled_callback(Widget w, XtPointer info, XtPointer context) {set_show_axes(SHOW_ALL_AXES_UNLABELLED);}
static void view_just_x_axis_unlabelled_callback(Widget w, XtPointer info, XtPointer context) {set_show_axes(SHOW_X_AXIS_UNLABELLED);}
static void view_bare_x_axis_callback(Widget w, XtPointer info, XtPointer context) {set_show_axes(SHOW_BARE_X_AXIS);}

static void view_focus_right_callback(Widget w, XtPointer info, XtPointer context) {set_zoom_focus_style(ZOOM_FOCUS_RIGHT);}
static void view_focus_left_callback(Widget w, XtPointer info, XtPointer context) {set_zoom_focus_style(ZOOM_FOCUS_LEFT);}
static void view_focus_middle_callback(Widget w, XtPointer info, XtPointer context) {set_zoom_focus_style(ZOOM_FOCUS_MIDDLE);}
static void view_focus_active_callback(Widget w, XtPointer info, XtPointer context) {set_zoom_focus_style(ZOOM_FOCUS_ACTIVE);}

static void view_grid_callback(Widget w, XtPointer info, XtPointer context)
{
  if (show_grid(ss) == NO_GRID)
    set_show_grid(WITH_GRID);
  else set_show_grid(NO_GRID);
}



/* -------------------------------- OPTIONS MENU -------------------------------- */

static void options_transform_callback(Widget w, XtPointer info, XtPointer context) {make_transform_dialog(true);}
static void options_controls_callback(Widget w, XtPointer info, XtPointer context) {make_controls_dialog();}
#if HAVE_EXTENSION_LANGUAGE
static void options_save_state_callback(Widget w, XtPointer info, XtPointer context) {save_state_from_menu();}
#endif
static void options_preferences_callback(Widget w, XtPointer info, XtPointer context) {make_preferences_dialog();}



/* -------------------------------- HELP MENU -------------------------------- */

static void help_about_snd_callback(Widget w, XtPointer info, XtPointer context) {about_snd_help();}
static void help_fft_callback(Widget w, XtPointer info, XtPointer context) {fft_help();}
#if HAVE_EXTENSION_LANGUAGE
static void help_find_callback(Widget w, XtPointer info, XtPointer context) {find_help();}
static void help_init_file_callback(Widget w, XtPointer info, XtPointer context) {init_file_help();}
#endif
static void help_undo_callback(Widget w, XtPointer info, XtPointer context) {undo_help();}
static void help_sync_callback(Widget w, XtPointer info, XtPointer context) {sync_help();}
static void help_debug_callback(Widget w, XtPointer info, XtPointer context) {debug_help();}
static void help_controls_callback(Widget w, XtPointer info, XtPointer context) {controls_help();}
static void help_env_callback(Widget w, XtPointer info, XtPointer context) {env_help();}
static void help_marks_callback(Widget w, XtPointer info, XtPointer context) {marks_help();}
static void help_mix_callback(Widget w, XtPointer info, XtPointer context) {mix_help();}
static void help_sound_files_callback(Widget w, XtPointer info, XtPointer context) {sound_files_help();}
static void help_keys_callback(Widget w, XtPointer info, XtPointer context) {key_help();}
static void help_play_callback(Widget w, XtPointer info, XtPointer context) {play_help();}
static void help_filter_callback(Widget w, XtPointer info, XtPointer context) {filter_help();}
static void help_save_callback(Widget w, XtPointer info, XtPointer context) {save_help();}
static void help_reverb_callback(Widget w, XtPointer info, XtPointer context) {reverb_help();}
static void help_resample_callback(Widget w, XtPointer info, XtPointer context) {resample_help();}
static void help_insert_callback(Widget w, XtPointer info, XtPointer context) {insert_help();}
static void help_delete_callback(Widget w, XtPointer info, XtPointer context) {delete_help();}
static void help_region_callback(Widget w, XtPointer info, XtPointer context) {region_help();}
static void help_selection_callback(Widget w, XtPointer info, XtPointer context) {selection_help();}
static void help_colors_callback(Widget w, XtPointer info, XtPointer context) {colors_help();}

void check_menu_labels(int key, int state, bool extended)
{
  /* user has redefined key, so erase old key binding info from the menu label */
  if (extended)
    {
      if (state == ControlMask)
	{
	  if (key == snd_K_f) set_label(file_open_menu, "Open"); else
	  if (key == snd_K_s) set_label(file_save_menu, "Save"); else
	  if (key == snd_K_q) set_label(file_mix_menu, "Mix"); else
	  if (key == snd_K_i) set_label(file_insert_menu, "Insert"); else
	  if (key == snd_K_u) set_label(edit_undo_menu, "Undo"); else
	  if (key == snd_K_r) set_label(edit_redo_menu, "Redo");
	}
      else
	{
	  if (key == snd_K_k) set_label(file_close_menu, "Close"); else
	  if (key == snd_K_i) set_label(edit_paste_menu, "Insert Selection"); else	  
	  if (key == snd_K_q) set_label(edit_mix_menu, "Mix Selection"); else
#if WITH_AUDIO	  
	  if (key == snd_K_p) set_label(edit_play_menu, "Play Selection"); else	  
#endif
	  if (key == snd_K_w) set_label(edit_save_as_menu, "Save Selection");
	}
    }
#if HAVE_EXTENSION_LANGUAGE
  else 
    {
      if ((key == snd_K_s) && (state == ControlMask))
	set_label(edit_find_menu, I_FIND);
    }
#endif
}


/* -------------------------------- MAIN MENU -------------------------------- */

static void menu_drag_watcher(Widget w, const char *str, Position x, Position y, drag_style_t dtype, void *data)
{
  char *new_title;
  switch (dtype)
    {
    case DRAG_ENTER:
      new_title = mus_format("%s: drop to open file", ss->startup_title);
      XtVaSetValues(main_shell(ss), XmNtitle, (char *)new_title, NULL);
      XmChangeColor(w, ss->selection_color);
      free(new_title);
      break;

    case DRAG_LEAVE:
      reflect_file_change_in_title();
      XmChangeColor(w, ss->highlight_color);
      break;

    default:
      break;
    }
}


static void menu_drop_watcher(Widget w, const char *str, Position x, Position y, void *data)
{
  snd_info *sp = NULL;
  ss->open_requestor = FROM_DRAG_AND_DROP;
  sp = snd_open_file(str, FILE_READ_WRITE);
  if (sp) select_channel(sp, 0);
}


void add_menu_drop(void)
{
  add_drag_and_drop(main_menu, menu_drop_watcher, menu_drag_watcher, NULL);
}


Widget add_menu(void)
{
  static Arg main_args[12];
  static Arg in_args[12];
  static Arg high_args[12];
  Arg sep_args[12];
  int in_n = 0, n, high_n = 0, main_n = 0, start_high_n, k, j;

  ss->mw = (Widget *)calloc(NUM_MENU_WIDGETS, sizeof(Widget));

  XtSetArg(main_args[main_n], XmNbackground, ss->basic_color); main_n++;
  XtSetArg(high_args[high_n], XmNbackground, ss->highlight_color); high_n++;
  XtSetArg(in_args[in_n], XmNbackground, ss->basic_color); in_n++;

  start_high_n = high_n;
  XtSetArg(in_args[in_n], XmNsensitive, false); in_n++;
  
  n = high_n;
  XtSetArg(high_args[n], XmNtopAttachment, XmATTACH_FORM); n++;
  XtSetArg(high_args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
  XtSetArg(high_args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(high_args[n], XmNrightAttachment, XmATTACH_FORM); n++;

  main_menu = XmCreateMenuBar(main_pane(ss), (char *)"menuBar", high_args, n);


  /* FILE MENU */
  XtSetArg(main_args[main_n], XmNuserData, (XtPointer)0);
  file_menu = XmCreatePulldownMenu(main_menu, (char *)"File", main_args, main_n + 1);
  
  high_n = start_high_n;
  XtSetArg(high_args[high_n], XmNsubMenuId, file_menu); high_n++;
  XtSetArg(high_args[high_n], XmNmnemonic, 'F'); high_n++;
  XtSetArg(high_args[high_n], XmNuserData, (XtPointer)0); high_n++;
  file_cascade_menu = XtCreateManagedWidget("File", xmCascadeButtonWidgetClass, main_menu, high_args, high_n);

  file_open_menu = XtCreateManagedWidget("Open", xmPushButtonWidgetClass, file_menu, main_args, main_n);
  XtAddCallback(file_open_menu, XmNactivateCallback, file_open_callback, NULL);
  XtVaSetValues(file_open_menu, XmNmnemonic, 'O', NULL);

  file_close_menu = XtCreateManagedWidget("Close", xmPushButtonWidgetClass, file_menu, in_args, in_n);
  XtAddCallback(file_close_menu, XmNactivateCallback, file_close_callback, NULL);
  XtVaSetValues(file_close_menu, XmNmnemonic, 'C', NULL);

  file_close_all_menu = XtCreateWidget("Close all", xmPushButtonWidgetClass, file_menu, main_args, main_n);
  XtAddCallback(file_close_all_menu, XmNactivateCallback, file_close_all_callback, NULL);
  
  file_save_menu = XtCreateManagedWidget("Save", xmPushButtonWidgetClass, file_menu, in_args, in_n);
  XtAddCallback(file_save_menu, XmNactivateCallback, file_save_callback, NULL);
  XtVaSetValues(file_save_menu, XmNmnemonic, 'S', NULL);
  
  file_save_as_menu = XtCreateManagedWidget("Save as", xmPushButtonWidgetClass, file_menu, in_args, in_n);
  XtAddCallback(file_save_as_menu, XmNactivateCallback, file_save_as_callback, NULL);
  XtVaSetValues(file_save_as_menu, XmNmnemonic, 'a', NULL);
  
  file_revert_menu = XtCreateManagedWidget("Revert", xmPushButtonWidgetClass, file_menu, in_args, in_n);
  XtAddCallback(file_revert_menu, XmNactivateCallback, file_revert_callback, NULL);
  XtVaSetValues(file_revert_menu, XmNmnemonic, 'R', NULL);
  
  file_mix_menu = XtCreateManagedWidget("Mix", xmPushButtonWidgetClass, file_menu, in_args, in_n);
  XtAddCallback(file_mix_menu, XmNactivateCallback, file_mix_callback_1, NULL);
  XtVaSetValues(file_mix_menu, XmNmnemonic, 'M', NULL);

  file_insert_menu = XtCreateManagedWidget("Insert", xmPushButtonWidgetClass, file_menu, in_args, in_n);
  XtAddCallback(file_insert_menu, XmNactivateCallback, file_insert_callback_1, NULL);
  XtVaSetValues(file_insert_menu, XmNmnemonic, 'I', NULL);

  file_update_menu = XtCreateManagedWidget("Update", xmPushButtonWidgetClass, file_menu, in_args, in_n);
  XtAddCallback(file_update_menu, XmNactivateCallback, file_update_callback, NULL);
  XtVaSetValues(file_update_menu, XmNmnemonic, 'U', NULL);

  file_new_menu = XtCreateManagedWidget("New", xmPushButtonWidgetClass, file_menu, main_args, main_n);
  XtAddCallback(file_new_menu, XmNactivateCallback, file_new_callback, NULL);
  XtVaSetValues(file_new_menu, XmNmnemonic, 'N', NULL);

  file_view_menu = XtCreateManagedWidget("View", xmPushButtonWidgetClass, file_menu, main_args, main_n);
  XtAddCallback(file_view_menu, XmNactivateCallback, file_view_callback, NULL);
  XtVaSetValues(file_view_menu, XmNmnemonic, 'V', NULL);

  file_print_menu = XtCreateManagedWidget("Print", xmPushButtonWidgetClass, file_menu, in_args, in_n);
  XtAddCallback(file_print_menu, XmNactivateCallback, file_print_callback_1, NULL);
  XtVaSetValues(file_print_menu, XmNmnemonic, 'P', NULL);

  j = 0;
  XtSetArg(sep_args[j], XmNbackground, ss->basic_color); j++;
  XtSetArg(sep_args[j], XmNseparatorType, XmSHADOW_ETCHED_IN); j++;
  file_sep_menu = XtCreateManagedWidget("", xmSeparatorWidgetClass, file_menu, sep_args, j);

  file_exit_menu = XtCreateManagedWidget("Exit", xmPushButtonWidgetClass, file_menu, main_args, main_n);
  XtAddCallback(file_exit_menu, XmNactivateCallback, file_exit_callback, NULL);
  XtVaSetValues(file_exit_menu, XmNmnemonic, 'E', NULL);


  /* EDIT MENU */
  XtSetArg(main_args[main_n], XmNuserData, (XtPointer)1);
  edit_menu = XmCreatePulldownMenu(main_menu, (char *)"Edit", main_args, main_n + 1);

  high_n = start_high_n;
  XtSetArg(high_args[high_n], XmNsubMenuId, edit_menu); high_n++;
  XtSetArg(high_args[high_n], XmNmnemonic, 'E'); high_n++;
  XtSetArg(high_args[high_n], XmNuserData, (XtPointer)1); high_n++;
  edit_cascade_menu = XtCreateManagedWidget("Edit", xmCascadeButtonWidgetClass, main_menu, high_args, high_n);
  
  edit_undo_menu = XtCreateManagedWidget("Undo", xmPushButtonWidgetClass, edit_menu, in_args, in_n);
  XtAddCallback(edit_undo_menu, XmNactivateCallback, edit_undo_callback, NULL);
  XtVaSetValues(edit_undo_menu, XmNmnemonic, 'U', NULL);

  edit_redo_menu = XtCreateManagedWidget("Redo", xmPushButtonWidgetClass, edit_menu, in_args, in_n);
  XtAddCallback(edit_redo_menu, XmNactivateCallback, edit_redo_callback, NULL);
  XtVaSetValues(edit_redo_menu, XmNmnemonic, 'R', NULL);

#if HAVE_EXTENSION_LANGUAGE
  edit_find_menu = XtCreateManagedWidget(I_FIND, xmPushButtonWidgetClass, edit_menu, in_args, in_n);
  XtAddCallback(edit_find_menu, XmNactivateCallback, edit_find_callback_1, NULL);
  XtVaSetValues(edit_find_menu, XmNmnemonic, 'F', NULL);
#endif

  edit_select_sep_menu = XtCreateManagedWidget("", xmSeparatorWidgetClass, edit_menu, sep_args, j);

  edit_cut_menu = XtCreateManagedWidget("Delete selection", xmPushButtonWidgetClass, edit_menu, in_args, in_n);
  XtAddCallback(edit_cut_menu, XmNactivateCallback, edit_cut_callback, NULL);
  XtVaSetValues(edit_cut_menu, XmNmnemonic, 'C', NULL);

  edit_paste_menu = XtCreateManagedWidget("Insert selection", xmPushButtonWidgetClass, edit_menu, in_args, in_n);
  XtAddCallback(edit_paste_menu, XmNactivateCallback, edit_paste_callback, NULL);
  XtVaSetValues(edit_paste_menu, XmNmnemonic, 'P', NULL);

  edit_mix_menu = XtCreateManagedWidget("Mix selection", xmPushButtonWidgetClass, edit_menu, in_args, in_n);
  XtAddCallback(edit_mix_menu, XmNactivateCallback, edit_mix_callback, NULL);
  XtVaSetValues(edit_mix_menu, XmNmnemonic, 'M', NULL);

#if WITH_AUDIO
  edit_play_menu = XtCreateManagedWidget("Play selection", xmPushButtonWidgetClass, edit_menu, in_args, in_n);
  XtAddCallback(edit_play_menu, XmNactivateCallback, edit_play_callback, NULL);
  XtVaSetValues(edit_play_menu, XmNmnemonic, 'P', NULL);
#endif

  edit_save_as_menu = XtCreateManagedWidget("Save selection", xmPushButtonWidgetClass, edit_menu, in_args, in_n);
  XtAddCallback(edit_save_as_menu, XmNactivateCallback, edit_save_as_callback, NULL);
  XtVaSetValues(edit_save_as_menu, XmNmnemonic, 'S', NULL);

  edit_select_all_menu = XtCreateManagedWidget("Select all", xmPushButtonWidgetClass, edit_menu, in_args, in_n);
  XtAddCallback(edit_select_all_menu, XmNactivateCallback, edit_select_all_callback, NULL);

  edit_unselect_menu = XtCreateManagedWidget("Unselect all", xmPushButtonWidgetClass, edit_menu, in_args, in_n);
  XtAddCallback(edit_unselect_menu, XmNactivateCallback, edit_unselect_callback, NULL);

  edit_edit_sep_menu = XtCreateManagedWidget("", xmSeparatorWidgetClass, edit_menu, sep_args, j);

  edit_env_menu = XtCreateManagedWidget("Edit envelope", xmPushButtonWidgetClass, edit_menu, main_args, main_n);
  XtAddCallback(edit_env_menu, XmNactivateCallback, edit_envelope_callback, NULL);
  XtVaSetValues(edit_env_menu, XmNmnemonic, 'E', NULL);

  edit_header_menu = XtCreateManagedWidget("Edit header", xmPushButtonWidgetClass, edit_menu, in_args, in_n);
  XtAddCallback(edit_header_menu, XmNactivateCallback, edit_header_callback_1, NULL);
  XtVaSetValues(edit_header_menu, XmNmnemonic, 'H', NULL);


  /* VIEW MENU */
  XtSetArg(main_args[main_n], XmNuserData, (XtPointer)2);
  view_menu = XmCreatePulldownMenu(main_menu, (char *)"View", main_args, main_n + 1);

  high_n = start_high_n;
  XtSetArg(high_args[high_n], XmNsubMenuId, view_menu); high_n++;
  XtSetArg(high_args[high_n], XmNmnemonic, 'V'); high_n++;
  XtSetArg(high_args[high_n], XmNuserData, (XtPointer)2); high_n++;
  view_cascade_menu = XtCreateManagedWidget("View", xmCascadeButtonWidgetClass, main_menu, high_args, high_n);

#if HAVE_EXTENSION_LANGUAGE
  view_listener_menu = XtCreateManagedWidget("Open listener", xmPushButtonWidgetClass, view_menu, main_args, main_n);
  XtAddCallback(view_listener_menu, XmNactivateCallback, view_listener_callback, NULL);
  XtVaSetValues(view_listener_menu, XmNmnemonic, 'L', NULL);
#endif

  view_files_menu = XtCreateManagedWidget("Files", xmPushButtonWidgetClass, view_menu, main_args, main_n);
  XtAddCallback(view_files_menu, XmNactivateCallback, view_files_callback, NULL);
  XtVaSetValues(view_files_menu, XmNmnemonic, 'F', NULL);

  view_mix_dialog_menu = XtCreateManagedWidget("Mixes", xmPushButtonWidgetClass, view_menu, main_args, main_n);
  XtAddCallback(view_mix_dialog_menu, XmNactivateCallback, view_mix_dialog_callback, NULL);

  view_region_menu = XtCreateManagedWidget("Regions", xmPushButtonWidgetClass, view_menu, in_args, in_n);
  XtAddCallback(view_region_menu, XmNactivateCallback, view_region_callback_1, NULL);
  XtVaSetValues(view_region_menu, XmNmnemonic, 'R', NULL);

  view_color_orientation_menu = XtCreateManagedWidget("Color/Orientation", xmPushButtonWidgetClass, view_menu, main_args, main_n);
  XtAddCallback(view_color_orientation_menu, XmNactivateCallback, view_color_orientation_callback_1, NULL);

  view_controls_menu = XtCreateManagedWidget("Show controls", xmPushButtonWidgetClass, view_menu, main_args, main_n);
  XtAddCallback(view_controls_menu, XmNactivateCallback, view_controls_callback, NULL);
  XtVaSetValues(view_controls_menu, XmNmnemonic, 'S', NULL);

  view_sep2_menu = XtCreateManagedWidget("", xmSeparatorWidgetClass, view_menu, sep_args, j);

  view_graph_style_menu = XmCreatePulldownMenu(view_menu, (char *)"graph-style", main_args, main_n);

  k = main_n;
  XtSetArg(main_args[k], XmNsubMenuId, view_graph_style_menu); k++;
  view_graph_style_cascade_menu = XtCreateManagedWidget(I_LINES_OR_DOTS, xmCascadeButtonWidgetClass, view_menu, main_args, k);

  view_lines_menu = XtCreateManagedWidget("lines", xmPushButtonWidgetClass, view_graph_style_menu, main_args, main_n);
  XtAddCallback(view_lines_menu, XmNactivateCallback, view_lines_callback, NULL); 
  if (graph_style(ss) == GRAPH_LINES) set_sensitive(view_lines_menu, false);

  view_dots_menu = XtCreateManagedWidget("dots", xmPushButtonWidgetClass, view_graph_style_menu, main_args, main_n);
  XtAddCallback(view_dots_menu, XmNactivateCallback, view_dots_callback, NULL);  
  if (graph_style(ss) == GRAPH_DOTS) set_sensitive(view_dots_menu, false);

  view_filled_menu = XtCreateManagedWidget("filled", xmPushButtonWidgetClass, view_graph_style_menu, main_args, main_n);
  XtAddCallback(view_filled_menu, XmNactivateCallback, view_filled_callback, NULL);  
  if (graph_style(ss) == GRAPH_FILLED) set_sensitive(view_filled_menu, false);

  view_dots_and_lines_menu = XtCreateManagedWidget("dots and lines", xmPushButtonWidgetClass, view_graph_style_menu, main_args, main_n);
  XtAddCallback(view_dots_and_lines_menu, XmNactivateCallback, view_dots_and_lines_callback, NULL);  
  if (graph_style(ss) == GRAPH_DOTS_AND_LINES) set_sensitive(view_dots_and_lines_menu, false);

  view_lollipops_menu = XtCreateManagedWidget("lollipops", xmPushButtonWidgetClass, view_graph_style_menu, main_args, main_n);
  XtAddCallback(view_lollipops_menu, XmNactivateCallback, view_lollipops_callback, NULL);  
  if (graph_style(ss) == GRAPH_LOLLIPOPS) set_sensitive(view_lollipops_menu, false);

  view_cursor_menu = XtCreateManagedWidget("Verbose cursor", xmPushButtonWidgetClass, view_menu, main_args, main_n);
  XtAddCallback(view_cursor_menu, XmNactivateCallback, view_cursor_callback, NULL);

#if HAVE_EXTENSION_LANGUAGE
  view_inset_menu = XtCreateManagedWidget("With inset graph", xmPushButtonWidgetClass, view_menu, main_args, main_n);
  XtAddCallback(view_inset_menu, XmNactivateCallback, view_inset_callback, NULL);
#endif

  view_combine_menu = XmCreatePulldownMenu(view_menu, (char *)"combine", main_args, main_n);

  k = main_n;
  XtSetArg(main_args[k], XmNsubMenuId, view_combine_menu); k++;
  view_combine_cascade_menu = XtCreateManagedWidget(I_CHANNEL_LAYOUT, xmCascadeButtonWidgetClass, view_menu, main_args, k);

  view_combine_separate_menu = XtCreateManagedWidget("separate", xmPushButtonWidgetClass, view_combine_menu, main_args, main_n);
  XtAddCallback(view_combine_separate_menu, XmNactivateCallback, view_separate_callback, NULL); 
  if (channel_style(ss) == CHANNELS_SEPARATE) set_sensitive(view_combine_separate_menu, false);

  view_combine_combined_menu = XtCreateManagedWidget("combined", xmPushButtonWidgetClass, view_combine_menu, main_args, main_n);
  XtAddCallback(view_combine_combined_menu, XmNactivateCallback, view_combined_callback, NULL);  
  if (channel_style(ss) == CHANNELS_COMBINED) set_sensitive(view_combine_combined_menu, false);

  view_combine_superimposed_menu = XtCreateManagedWidget("superimposed", xmPushButtonWidgetClass, view_combine_menu, main_args, main_n);
  XtAddCallback(view_combine_superimposed_menu, XmNactivateCallback, view_superimposed_callback, NULL);  
  if (channel_style(ss) == CHANNELS_SUPERIMPOSED) set_sensitive(view_combine_superimposed_menu, false);

  view_zero_menu = XtCreateManagedWidget("Show y = 0", xmPushButtonWidgetClass, view_menu, main_args, main_n);
  XtAddCallback(view_zero_menu, XmNactivateCallback, view_zero_callback, NULL);
  XtVaSetValues(view_zero_menu, XmNmnemonic, 'y', NULL);

  view_x_axis_menu = XmCreatePulldownMenu(view_menu, (char *)"xaxis", main_args, main_n);

  k = main_n;
  XtSetArg(main_args[k], XmNsubMenuId, view_x_axis_menu); k++;
  view_x_axis_cascade_menu = XtCreateManagedWidget("X axis units", xmCascadeButtonWidgetClass, view_menu, main_args, k);

  view_x_axis_seconds_menu = XtCreateManagedWidget("seconds", xmPushButtonWidgetClass, view_x_axis_menu, main_args, main_n);
  XtAddCallback(view_x_axis_seconds_menu, XmNactivateCallback, view_x_axis_seconds_callback, NULL);  
  set_sensitive(view_x_axis_seconds_menu, false);

  view_x_axis_samples_menu = XtCreateManagedWidget("samples", xmPushButtonWidgetClass, view_x_axis_menu, main_args, main_n);
  XtAddCallback(view_x_axis_samples_menu, XmNactivateCallback, view_x_axis_samples_callback, NULL);  

  view_x_axis_clock_menu = XtCreateManagedWidget("clock", xmPushButtonWidgetClass, view_x_axis_menu, main_args, main_n);
  XtAddCallback(view_x_axis_clock_menu, XmNactivateCallback, view_x_axis_clock_callback, NULL);  

  view_x_axis_percentage_menu = XtCreateManagedWidget("percentage", xmPushButtonWidgetClass, view_x_axis_menu, main_args, main_n);
  XtAddCallback(view_x_axis_percentage_menu, XmNactivateCallback, view_x_axis_percentage_callback, NULL);  

  view_x_axis_beats_menu = XtCreateManagedWidget("beats", xmPushButtonWidgetClass, view_x_axis_menu, main_args, main_n);
  XtAddCallback(view_x_axis_beats_menu, XmNactivateCallback, view_x_axis_beats_callback, NULL);  

  view_x_axis_measures_menu = XtCreateManagedWidget("measures", xmPushButtonWidgetClass, view_x_axis_menu, main_args, main_n);
  XtAddCallback(view_x_axis_measures_menu, XmNactivateCallback, view_x_axis_measures_callback, NULL);  


  view_axes_menu = XmCreatePulldownMenu(view_menu, (char *)"axes", main_args, main_n);

  k = main_n;
  XtSetArg(main_args[k], XmNsubMenuId, view_axes_menu); k++;
  view_axes_cascade_menu = XtCreateManagedWidget(I_AXIS_LAYOUT, xmCascadeButtonWidgetClass, view_menu, main_args, k);

  view_no_axes_menu = XtCreateManagedWidget("no axes", xmPushButtonWidgetClass, view_axes_menu, main_args, main_n);
  XtAddCallback(view_no_axes_menu, XmNactivateCallback, view_no_axes_callback, NULL);  
  if (show_axes(ss) == SHOW_NO_AXES) set_sensitive(view_no_axes_menu, false); /* false because it is already chosen */

  view_all_axes_menu = XtCreateManagedWidget("both axes", xmPushButtonWidgetClass, view_axes_menu, main_args, main_n);
  XtAddCallback(view_all_axes_menu, XmNactivateCallback, view_all_axes_callback, NULL);  
  if (show_axes(ss) == SHOW_ALL_AXES) set_sensitive(view_all_axes_menu, false);

  view_just_x_axis_menu = XtCreateManagedWidget("just x axis", xmPushButtonWidgetClass, view_axes_menu, main_args, main_n);
  XtAddCallback(view_just_x_axis_menu, XmNactivateCallback, view_just_x_axis_callback, NULL);  
  if (show_axes(ss) == SHOW_X_AXIS) set_sensitive(view_just_x_axis_menu, false);

  view_all_axes_unlabelled_menu = XtCreateManagedWidget("both axes, no labels", xmPushButtonWidgetClass, view_axes_menu, main_args, main_n);
  XtAddCallback(view_all_axes_unlabelled_menu, XmNactivateCallback, view_all_axes_unlabelled_callback, NULL);  
  if (show_axes(ss) == SHOW_ALL_AXES_UNLABELLED) set_sensitive(view_all_axes_unlabelled_menu, false);

  view_just_x_axis_unlabelled_menu = XtCreateManagedWidget("just x axis, no label", xmPushButtonWidgetClass, view_axes_menu, main_args, main_n);
  XtAddCallback(view_just_x_axis_unlabelled_menu, XmNactivateCallback, view_just_x_axis_unlabelled_callback, NULL);  
  if (show_axes(ss) == SHOW_X_AXIS_UNLABELLED) set_sensitive(view_just_x_axis_unlabelled_menu, false);

  view_bare_x_axis_menu = XtCreateManagedWidget("bare x axis", xmPushButtonWidgetClass, view_axes_menu, main_args, main_n);
  XtAddCallback(view_bare_x_axis_menu, XmNactivateCallback, view_bare_x_axis_callback, NULL);  
  if (show_axes(ss) == SHOW_BARE_X_AXIS) set_sensitive(view_bare_x_axis_menu, false);

  view_focus_style_menu = XmCreatePulldownMenu(view_menu, (char *)"focusstyle", main_args, main_n);

  k = main_n;
  XtSetArg(main_args[k], XmNsubMenuId, view_focus_style_menu); k++;
  view_focus_cascade_menu = XtCreateManagedWidget(I_ZOOM_CENTERS_ON, xmCascadeButtonWidgetClass, view_menu, main_args, k);

  view_focus_left_menu = XtCreateManagedWidget("window left edge", xmPushButtonWidgetClass, view_focus_style_menu, main_args, main_n);
  XtAddCallback(view_focus_left_menu, XmNactivateCallback, view_focus_left_callback, NULL);  

  view_focus_right_menu = XtCreateManagedWidget("window right edge", xmPushButtonWidgetClass, view_focus_style_menu, main_args, main_n);
  XtAddCallback(view_focus_right_menu, XmNactivateCallback, view_focus_right_callback, NULL);  

  view_focus_middle_menu = XtCreateManagedWidget("window midpoint", xmPushButtonWidgetClass, view_focus_style_menu, main_args, main_n);
  XtAddCallback(view_focus_middle_menu, XmNactivateCallback, view_focus_middle_callback, NULL);  

  view_focus_active_menu = XtCreateManagedWidget("cursor or selection", xmPushButtonWidgetClass, view_focus_style_menu, main_args, main_n);
  XtAddCallback(view_focus_active_menu, XmNactivateCallback, view_focus_active_callback, NULL);  


  view_grid_menu = XtCreateManagedWidget("With grid", xmPushButtonWidgetClass, view_menu, main_args, main_n);
  XtAddCallback(view_grid_menu, XmNactivateCallback, view_grid_callback, NULL);


  /* OPTIONS MENU */
  XtSetArg(main_args[main_n], XmNuserData, (XtPointer)3);
  options_menu = XmCreatePulldownMenu(main_menu, (char *)"Option", main_args, main_n + 1);

  high_n = start_high_n;
  XtSetArg(high_args[high_n], XmNsubMenuId, options_menu); high_n++;
  XtSetArg(high_args[high_n], XmNmnemonic, 'O'); high_n++;
  XtSetArg(high_args[high_n], XmNuserData, (XtPointer)3); high_n++;
  options_cascade_menu = XtCreateManagedWidget("Options", xmCascadeButtonWidgetClass, main_menu, high_args, high_n);

  options_transform_menu = XtCreateManagedWidget("Transform options", xmPushButtonWidgetClass, options_menu, main_args, main_n);
  XtAddCallback(options_transform_menu, XmNactivateCallback, options_transform_callback, NULL);
  XtVaSetValues(options_transform_menu, XmNmnemonic, 't', NULL);

  options_controls_menu = XtCreateManagedWidget("Control panel options", xmPushButtonWidgetClass, options_menu, main_args, main_n);
  XtAddCallback(options_controls_menu, XmNactivateCallback, options_controls_callback, NULL);
  XtVaSetValues(options_controls_menu, XmNmnemonic, 'c', NULL);


#if HAVE_EXTENSION_LANGUAGE
  options_save_state_menu = XtCreateManagedWidget("Save session", xmPushButtonWidgetClass, options_menu, main_args, main_n);
  XtAddCallback(options_save_state_menu, XmNactivateCallback, options_save_state_callback, NULL);
#endif

  options_sep_menu = XtCreateManagedWidget("", xmSeparatorWidgetClass, options_menu, sep_args, j);

  options_preferences_menu = XtCreateManagedWidget("Preferences", xmPushButtonWidgetClass, options_menu, main_args, main_n);
  XtAddCallback(options_preferences_menu, XmNactivateCallback, options_preferences_callback, NULL);


  /* HELP MENU */
  XtSetArg(main_args[main_n], XmNuserData, (XtPointer)4);
  help_menu = XmCreatePulldownMenu(main_menu, (char *)I_HELP, main_args, main_n + 1);

  high_n = start_high_n;
  XtSetArg(high_args[high_n], XmNsubMenuId, help_menu); high_n++;
  XtSetArg(high_args[high_n], XmNmnemonic, 'H'); high_n++;
  XtSetArg(high_args[high_n], XmNuserData, (XtPointer)4); high_n++;
  help_cascade_menu = XtCreateManagedWidget(I_HELP, xmCascadeButtonWidgetClass, main_menu, high_args, high_n);

  help_about_snd_menu = XtCreateManagedWidget("About Snd", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_about_snd_menu, XmNactivateCallback, help_about_snd_callback, NULL);
  XtVaSetValues(help_about_snd_menu, XmNmnemonic, 'O', NULL);

#if HAVE_EXTENSION_LANGUAGE
  help_init_file_menu = XtCreateManagedWidget("Customization", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_init_file_menu, XmNactivateCallback, help_init_file_callback, NULL);
#endif

  help_controls_menu = XtCreateManagedWidget("Control panel", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_controls_menu, XmNactivateCallback, help_controls_callback, NULL);

  help_keys_menu = XtCreateManagedWidget("Key bindings", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_keys_menu, XmNactivateCallback, help_keys_callback, NULL);

  help_play_menu = XtCreateManagedWidget("Play", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_play_menu, XmNactivateCallback, help_play_callback, NULL);

  help_save_menu = XtCreateManagedWidget("Save", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_save_menu, XmNactivateCallback, help_save_callback, NULL);

  help_mix_menu = XtCreateManagedWidget("Mix", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_mix_menu, XmNactivateCallback, help_mix_callback, NULL);

  help_resample_menu = XtCreateManagedWidget("Resample", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_resample_menu, XmNactivateCallback, help_resample_callback, NULL);

  help_fft_menu = XtCreateManagedWidget("FFT", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_fft_menu, XmNactivateCallback, help_fft_callback, NULL);

  help_filter_menu = XtCreateManagedWidget("Filter", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_filter_menu, XmNactivateCallback, help_filter_callback, NULL);

  help_reverb_menu = XtCreateManagedWidget("Reverb", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_reverb_menu, XmNactivateCallback, help_reverb_callback, NULL);

  help_env_menu = XtCreateManagedWidget("Envelope", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_env_menu, XmNactivateCallback, help_env_callback, NULL);

  help_marks_menu = XtCreateManagedWidget("Mark", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_marks_menu, XmNactivateCallback, help_marks_callback, NULL);

  help_insert_menu = XtCreateManagedWidget("Insert", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_insert_menu, XmNactivateCallback, help_insert_callback, NULL);

  help_delete_menu = XtCreateManagedWidget("Delete", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_delete_menu, XmNactivateCallback, help_delete_callback, NULL);

  help_undo_menu = XtCreateManagedWidget("Undo and redo", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_undo_menu, XmNactivateCallback, help_undo_callback, NULL);

#if HAVE_EXTENSION_LANGUAGE
  help_find_menu = XtCreateManagedWidget("Search", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_find_menu, XmNactivateCallback, help_find_callback, NULL);
#endif

  help_sync_menu = XtCreateManagedWidget("Sync and unite", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_sync_menu, XmNactivateCallback, help_sync_callback, NULL);

  help_sound_files_menu = XtCreateManagedWidget("Headers and data", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_sound_files_menu, XmNactivateCallback, help_sound_files_callback, NULL);

  help_debug_menu = XtCreateManagedWidget("Debugging", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_debug_menu, XmNactivateCallback, help_debug_callback, NULL);

  help_region_menu = XtCreateManagedWidget("Regions", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_region_menu, XmNactivateCallback, help_region_callback, NULL);

  help_selection_menu = XtCreateManagedWidget("Selections", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_selection_menu, XmNactivateCallback, help_selection_callback, NULL);

  help_colors_menu = XtCreateManagedWidget("Colors", xmPushButtonWidgetClass, help_menu, main_args, main_n);
  XtAddCallback(help_colors_menu, XmNactivateCallback, help_colors_callback, NULL);

  XtVaSetValues(main_menu, XmNmenuHelpWidget, help_cascade_menu, NULL);

  XtAddCallback(file_cascade_menu, XmNcascadingCallback, file_menu_update_1, NULL);
  XtAddCallback(edit_cascade_menu, XmNcascadingCallback, edit_menu_update_1, NULL);
  XtAddCallback(view_cascade_menu, XmNcascadingCallback, view_menu_update_1, NULL);

  XtManageChild(main_menu);
  return(main_menu);
}


/* -------------------------------- POPUP MENU -------------------------------- */

static Widget basic_popup_menu = NULL, selection_popup_menu = NULL, fft_popup_menu = NULL;

static Widget add_menu_item(Widget menu, const char *label, void (*callback)(Widget w, XtPointer info, XtPointer context))
{
  Arg args[20];
  int n;
  Widget w;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  w = XtCreateManagedWidget(label, xmPushButtonWidgetClass, menu, args, n);
  XtAddCallback(w, XmNactivateCallback, callback, NULL);
  return(w);
}

static void popup_info_callback(Widget w, XtPointer info, XtPointer context) 
{
  snd_info *sp;
  sp = any_selected_sound();
  if (sp) display_info(sp);
}

static void popup_normalize_callback(Widget w, XtPointer info, XtPointer context) 
{
  mus_float_t scl[1];
  scl[0] = 1.0;
  scale_to(any_selected_sound(), current_channel(), scl, 1, OVER_SOUND);
}


static void popup_reverse_callback(Widget w, XtPointer info, XtPointer context) 
{
  reverse_sound(current_channel(), OVER_SOUND, C_int_to_Xen_integer(AT_CURRENT_EDIT_POSITION), 0);
}


static void stop_everything_callback(Widget w, XtPointer info, XtPointer context) 
{
  control_g(any_selected_sound());
}


static void play_channel_callback(Widget w, XtPointer info, XtPointer context) 
{
  chan_info *cp;
  cp = current_channel();
  play_channel(cp, 0, current_samples(cp));
}


static Widget popup_play;

void post_basic_popup_menu(void *e)
{
  XButtonPressedEvent *event = (XButtonPressedEvent *)e;
  snd_info *sp;
  if (!basic_popup_menu)
    {
      Arg args[20];
      int n;

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNpopupEnabled, false); n++;      /* this was XmPOPUP_AUTOMATIC_RECURSIVE */
      basic_popup_menu = XmCreatePopupMenu(main_pane(ss), (char *)"basic-popup-menu", args, n);

      add_menu_item(basic_popup_menu, "Info",           popup_info_callback);
      add_menu_item(basic_popup_menu, "Select all",     edit_select_all_callback);
      popup_play = add_menu_item(basic_popup_menu, "Play channel", play_channel_callback);
      add_menu_item(basic_popup_menu, "Stop!",          stop_everything_callback);
      add_menu_item(basic_popup_menu, "-> 1.0",         popup_normalize_callback);
      add_menu_item(basic_popup_menu, "Reverse",        popup_reverse_callback);
    }

  XmMenuPosition(basic_popup_menu, event);

  sp = any_selected_sound();
  if ((!sp) || (sp->nchans == 1))
    XtUnmanageChild(popup_play);
  else XtManageChild(popup_play);

  XtManageChild(basic_popup_menu);
}


/* -------- selection popup -------- */

static void popup_show_selection_callback(Widget w, XtPointer info, XtPointer context) 
{
  show_selection();
}

static void popup_zero_selection_callback(Widget w, XtPointer info, XtPointer context) 
{
  mus_float_t scl[1];
  scl[0] = 0.0;
  scale_by(NULL, scl, 1, OVER_SELECTION);
}

static void popup_normalize_selection_callback(Widget w, XtPointer info, XtPointer context) 
{
  mus_float_t scl[1];
  scl[0] = 1.0;
  scale_to(NULL, NULL, scl, 1, OVER_SELECTION);
}

static void popup_error_handler(const char *msg, void *data)
{
  redirect_snd_error_to(NULL, NULL);
  redirect_snd_warning_to(NULL, NULL);
  status_report(any_selected_sound(), "%s: %s", (char *)data, msg);
}

static void popup_cut_to_new_callback_1(bool cut) 
{
  char *temp_file;
  io_error_t io_err;

  temp_file = snd_tempnam();
  io_err = save_selection(temp_file, selection_srate(), default_output_sample_type(ss), default_output_header_type(ss), NULL, SAVE_ALL_CHANS);
  if (io_err == IO_NO_ERROR)
    {
      if (cut) delete_selection(UPDATE_DISPLAY);

      ss->open_requestor = FROM_POPUP_CUT_TO_NEW;
      redirect_snd_error_to(popup_error_handler, (void *)"popup cut->new");
      snd_open_file(temp_file, FILE_READ_WRITE);
      redirect_snd_error_to(NULL, NULL);

      free(temp_file);
    }
}

static void popup_cut_to_new_callback(Widget w, XtPointer info, XtPointer context) {popup_cut_to_new_callback_1(true);}
static void popup_copy_to_new_callback(Widget w, XtPointer info, XtPointer context) {popup_cut_to_new_callback_1(false);}

static void crop(chan_info *cp)
{
  if (selection_is_active_in_channel(cp))
    {
      mus_long_t beg, end, framples;
      framples = current_samples(cp);
      beg = selection_beg(cp);
      end = selection_end(cp);
      if (beg > 0)
	delete_samples(0, beg, cp, cp->edit_ctr);
      if (end < (framples - 1))
	delete_samples(end + 1, framples - end, cp, cp->edit_ctr);
    }
}

static void popup_crop_callback(Widget w, XtPointer info, XtPointer context)
{
  for_each_chan(crop);
}


static void popup_cut_and_smooth_callback(Widget w, XtPointer info, XtPointer context)
{
  for_each_chan(cut_and_smooth);
}

static void mark_selection(chan_info *cp)
{
  if (selection_is_active_in_channel(cp))
    {
      add_mark(selection_beg(cp), NULL, cp);
      add_mark(selection_end(cp), NULL, cp);
    }
}

static void popup_mark_selection_callback(Widget w, XtPointer info, XtPointer context)
{
  for_each_chan(mark_selection);
}

static void popup_reverse_selection_callback(Widget w, XtPointer info, XtPointer context)
{
  reverse_sound(current_channel(), OVER_SELECTION, C_int_to_Xen_integer(AT_CURRENT_EDIT_POSITION), 0);
}

static mus_float_t selection_max = 0.0;

static void selection_info(chan_info *cp)
{
  if ((selection_is_active_in_channel(cp)) &&
      (selection_maxamp(cp) > selection_max))
    selection_max = selection_maxamp(cp);
}

static void popup_selection_info_callback(Widget w, XtPointer info, XtPointer context)
{
  selection_max = 0.0;
  for_each_chan(selection_info);
  status_report(any_selected_sound(), "selection max: %f", selection_max);
}

#if WITH_AUDIO
static void popup_loop_play_callback(Widget w, XtPointer info, XtPointer context) 
{
  if (ss->selection_play_stop)
    {
      stop_playing_all_sounds(PLAY_BUTTON_UNSET);
      reflect_play_selection_stop();
    }
  else
    {
      set_menu_label(edit_play_menu, I_STOP);
      ss->selection_play_stop = true;
      loop_play_selection();
    }
}
#endif


void post_selection_popup_menu(void *e) 
{
  XButtonPressedEvent *event = (XButtonPressedEvent *)e;
  if (!selection_popup_menu)
    {
      Arg args[20];
      int n;

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNpopupEnabled, false); n++;      /* this was XmPOPUP_AUTOMATIC_RECURSIVE */
      selection_popup_menu = XmCreatePopupMenu(main_pane(ss), (char *)"selection-popup-menu", args, n);

      add_menu_item(selection_popup_menu, "Fill window",    popup_show_selection_callback);
      add_menu_item(selection_popup_menu, "Cut",            edit_cut_callback);
      add_menu_item(selection_popup_menu, "Cut and smooth", popup_cut_and_smooth_callback);
      add_menu_item(selection_popup_menu, "Cut -> new",     popup_cut_to_new_callback);
      add_menu_item(selection_popup_menu, "Save as",        edit_save_as_callback);
#if WITH_AUDIO
      add_menu_item(selection_popup_menu, "Play",           edit_play_callback);
      add_menu_item(selection_popup_menu, "Play looping",   popup_loop_play_callback);
#endif
      add_menu_item(selection_popup_menu, "Crop",           popup_crop_callback);
      add_menu_item(selection_popup_menu, "Unselect all",   edit_unselect_callback);
      add_menu_item(selection_popup_menu, "-> 0.0",         popup_zero_selection_callback);
      add_menu_item(selection_popup_menu, "-> 1.0",         popup_normalize_selection_callback);
      add_menu_item(selection_popup_menu, "Copy -> new",    popup_copy_to_new_callback);
      add_menu_item(selection_popup_menu, "Paste",          edit_paste_callback);
      add_menu_item(selection_popup_menu, "Mix",            edit_mix_callback);
      add_menu_item(selection_popup_menu, "Mark",           popup_mark_selection_callback);
      add_menu_item(selection_popup_menu, "Reverse",        popup_reverse_selection_callback);
      add_menu_item(selection_popup_menu, "Info",           popup_selection_info_callback);
    }

  XmMenuPosition(selection_popup_menu, event);
  XtManageChild(selection_popup_menu);
}


/* -------- fft popup -------- */

static void popup_peaks_callback(Widget w, XtPointer info, XtPointer context) 
{
  FILE *peaks_fd;
  peaks_fd = FOPEN("fft.txt", "w");
  if (peaks_fd)
    {
      write_transform_peaks(peaks_fd, current_channel()); /* follows sync */
      fclose(peaks_fd);
    }
}

static void fft_size_16_callback(Widget w, XtPointer info, XtPointer context) {set_transform_size(16);}
static void fft_size_64_callback(Widget w, XtPointer info, XtPointer context) {set_transform_size(64);}
static void fft_size_256_callback(Widget w, XtPointer info, XtPointer context) {set_transform_size(256);}
static void fft_size_1024_callback(Widget w, XtPointer info, XtPointer context) {set_transform_size(1024);}
static void fft_size_4096_callback(Widget w, XtPointer info, XtPointer context) {set_transform_size(4096);}
static void fft_size_16384_callback(Widget w, XtPointer info, XtPointer context) {set_transform_size(16384);}
static void fft_size_65536_callback(Widget w, XtPointer info, XtPointer context) {set_transform_size(65536);}
static void fft_size_262144_callback(Widget w, XtPointer info, XtPointer context) {set_transform_size(262144);}
static void fft_size_1048576_callback(Widget w, XtPointer info, XtPointer context) {set_transform_size(1048576);}


static void fft_window_rectangular_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_RECTANGULAR_WINDOW);}
static void fft_window_hann_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_HANN_WINDOW);}
static void fft_window_welch_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_WELCH_WINDOW);}
static void fft_window_parzen_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_PARZEN_WINDOW);}
static void fft_window_bartlett_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_BARTLETT_WINDOW);}
static void fft_window_blackman2_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_BLACKMAN2_WINDOW);}
static void fft_window_blackman3_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_BLACKMAN3_WINDOW);}
static void fft_window_blackman4_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_BLACKMAN4_WINDOW);}
static void fft_window_hamming_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_HAMMING_WINDOW);}
static void fft_window_exponential_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_EXPONENTIAL_WINDOW);}
static void fft_window_riemann_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_RIEMANN_WINDOW);}
static void fft_window_kaiser_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_KAISER_WINDOW);}
static void fft_window_cauchy_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_CAUCHY_WINDOW);}
static void fft_window_poisson_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_POISSON_WINDOW);}
static void fft_window_gaussian_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_GAUSSIAN_WINDOW);}
static void fft_window_tukey_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_TUKEY_WINDOW);}
static void fft_window_dolph_chebyshev_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_DOLPH_CHEBYSHEV_WINDOW);}
static void fft_window_blackman6_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_BLACKMAN6_WINDOW);}
static void fft_window_blackman8_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_BLACKMAN8_WINDOW);}
static void fft_window_blackman10_callback(Widget w, XtPointer info, XtPointer context) {set_fft_window(MUS_BLACKMAN10_WINDOW);}

static void fft_type_fourier_callback(Widget w, XtPointer info, XtPointer context) {set_transform_type(FOURIER);}
static void fft_type_wavelet_callback(Widget w, XtPointer info, XtPointer context) {set_transform_type(WAVELET);}
static void fft_type_autocorrelation_callback(Widget w, XtPointer info, XtPointer context) {set_transform_type(AUTOCORRELATION);}
static void fft_type_cepstrum_callback(Widget w, XtPointer info, XtPointer context) {set_transform_type(CEPSTRUM);}


static void fft_graph_once_callback(Widget w, XtPointer info, XtPointer context) {set_transform_graph_type(GRAPH_ONCE);}
static void fft_graph_sonogram_callback(Widget w, XtPointer info, XtPointer context) {set_transform_graph_type(GRAPH_AS_SONOGRAM);}
static void fft_graph_spectrogram_callback(Widget w, XtPointer info, XtPointer context) {set_transform_graph_type(GRAPH_AS_SPECTROGRAM);}


static void fft_gray_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(GRAY_COLORMAP);}
static void fft_hot_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(HOT_COLORMAP);}
static void fft_cool_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(COOL_COLORMAP);}
static void fft_bone_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(BONE_COLORMAP);}
static void fft_copper_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(COPPER_COLORMAP);}
static void fft_pink_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(PINK_COLORMAP);}
static void fft_jet_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(JET_COLORMAP);}
static void fft_prism_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(PRISM_COLORMAP);}
static void fft_autumn_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(AUTUMN_COLORMAP);}
static void fft_winter_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(WINTER_COLORMAP);}
static void fft_spring_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(SPRING_COLORMAP);}
static void fft_summer_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(SUMMER_COLORMAP);}
static void fft_rainbow_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(RAINBOW_COLORMAP);}
static void fft_flag_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(FLAG_COLORMAP);}
static void fft_phases_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(PHASES_COLORMAP);}
static void fft_black_and_white_callback(Widget w, XtPointer info, XtPointer context) {set_color_map(BLACK_AND_WHITE_COLORMAP);}

void post_fft_popup_menu(void *e)
{
  XButtonPressedEvent *event = (XButtonPressedEvent *)e;
  if (!fft_popup_menu)
    {
      Widget outer_menu;
      Arg args[20];
      int n;

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNpopupEnabled, false); n++;      /* this was XmPOPUP_AUTOMATIC_RECURSIVE */
      fft_popup_menu = XmCreatePopupMenu(main_pane(ss), (char *)"fft-popup-menu", args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      outer_menu = XmCreatePulldownMenu(fft_popup_menu, (char *)"Size", args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNsubMenuId, outer_menu); n++;
      XtCreateManagedWidget("Size", xmCascadeButtonWidgetClass, fft_popup_menu, args, n);

      add_menu_item(outer_menu, "16",      fft_size_16_callback);
      add_menu_item(outer_menu, "64",      fft_size_64_callback);
      add_menu_item(outer_menu, "256",     fft_size_256_callback);
      add_menu_item(outer_menu, "1024",    fft_size_1024_callback);
      add_menu_item(outer_menu, "4096",    fft_size_4096_callback);
      add_menu_item(outer_menu, "16384",   fft_size_16384_callback);
      add_menu_item(outer_menu, "65536",   fft_size_65536_callback);
      add_menu_item(outer_menu, "262144",  fft_size_262144_callback);
      add_menu_item(outer_menu, "1048576", fft_size_1048576_callback);


      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      outer_menu = XmCreatePulldownMenu(fft_popup_menu, (char *)"Window", args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNsubMenuId, outer_menu); n++;
      XtCreateManagedWidget("Window", xmCascadeButtonWidgetClass, fft_popup_menu, args, n);

      add_menu_item(outer_menu, "rectangular",     fft_window_rectangular_callback);
      add_menu_item(outer_menu, "hann",            fft_window_hann_callback);
      add_menu_item(outer_menu, "welch",           fft_window_welch_callback);
      add_menu_item(outer_menu, "parzen",          fft_window_parzen_callback);
      add_menu_item(outer_menu, "bartlett",        fft_window_bartlett_callback);
      add_menu_item(outer_menu, "hamming",         fft_window_hamming_callback);
      add_menu_item(outer_menu, "blackman2",       fft_window_blackman2_callback);
      add_menu_item(outer_menu, "blackman3",       fft_window_blackman3_callback);
      add_menu_item(outer_menu, "blackman4",       fft_window_blackman4_callback);
      add_menu_item(outer_menu, "exponential",     fft_window_exponential_callback);
      add_menu_item(outer_menu, "riemann",         fft_window_riemann_callback);
      add_menu_item(outer_menu, "kaiser",          fft_window_kaiser_callback);
      add_menu_item(outer_menu, "cauchy",          fft_window_cauchy_callback);
      add_menu_item(outer_menu, "poisson",         fft_window_poisson_callback);
      add_menu_item(outer_menu, "gaussian",        fft_window_gaussian_callback);
      add_menu_item(outer_menu, "tukey",           fft_window_tukey_callback);
      add_menu_item(outer_menu, "dolph-chebyshev", fft_window_dolph_chebyshev_callback);
      add_menu_item(outer_menu, "blackman6",       fft_window_blackman6_callback);
      add_menu_item(outer_menu, "blackman8",       fft_window_blackman8_callback);
      add_menu_item(outer_menu, "blackman10" ,     fft_window_blackman10_callback);


      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      outer_menu = XmCreatePulldownMenu(fft_popup_menu, (char *)"Graph type", args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNsubMenuId, outer_menu); n++;
      XtCreateManagedWidget("Graph type", xmCascadeButtonWidgetClass, fft_popup_menu, args, n);

      add_menu_item(outer_menu, "one fft",     fft_graph_once_callback);
      add_menu_item(outer_menu, "sonogram",    fft_graph_sonogram_callback);
      add_menu_item(outer_menu, "spectrogram", fft_graph_spectrogram_callback);


      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      outer_menu = XmCreatePulldownMenu(fft_popup_menu, (char *)"Transform type", args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNsubMenuId, outer_menu); n++;
      XtCreateManagedWidget("Transform type", xmCascadeButtonWidgetClass, fft_popup_menu, args, n);

      add_menu_item(outer_menu, "fourier",         fft_type_fourier_callback);
      add_menu_item(outer_menu, "wavelet",         fft_type_wavelet_callback);
      add_menu_item(outer_menu, "autocorrelation", fft_type_autocorrelation_callback);
      add_menu_item(outer_menu, "cepstrum",        fft_type_cepstrum_callback);


      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      outer_menu = XmCreatePulldownMenu(fft_popup_menu, (char *)"Colormap", args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNsubMenuId, outer_menu); n++;
      XtCreateManagedWidget("Colormap", xmCascadeButtonWidgetClass, fft_popup_menu, args, n);

      add_menu_item(outer_menu, "gray",    fft_gray_callback);
      add_menu_item(outer_menu, "autumn",  fft_autumn_callback);
      add_menu_item(outer_menu, "spring",  fft_spring_callback);
      add_menu_item(outer_menu, "winter",  fft_winter_callback);
      add_menu_item(outer_menu, "summer",  fft_summer_callback);
      add_menu_item(outer_menu, "cool",    fft_cool_callback);
      add_menu_item(outer_menu, "copper",  fft_copper_callback);
      add_menu_item(outer_menu, "flag",    fft_flag_callback);
      add_menu_item(outer_menu, "prism",   fft_prism_callback);
      add_menu_item(outer_menu, "bone",    fft_bone_callback);
      add_menu_item(outer_menu, "hot",     fft_hot_callback);
      add_menu_item(outer_menu, "jet",     fft_jet_callback);
      add_menu_item(outer_menu, "pink",    fft_pink_callback);
      add_menu_item(outer_menu, "rainbow", fft_rainbow_callback);
      add_menu_item(outer_menu, "phases",  fft_phases_callback);
      add_menu_item(outer_menu, "black and white", fft_black_and_white_callback);


      add_menu_item(fft_popup_menu, "Peaks->fft.txt", popup_peaks_callback);
    }

  XmMenuPosition(fft_popup_menu, event);
  XtManageChild(fft_popup_menu);
}



void post_lisp_popup_menu(void *e) {}




/* ---------------- tooltips ---------------- */

static Widget tooltip_shell = NULL;
#if (!HAVE_GL)
  static Widget tooltip_label = NULL;
#endif
static timeout_result_t tool_proc = 0, quit_proc = 0;
static Time tool_last_time = 0;
static Position tool_x, tool_y;
static Widget tool_w;

#if (!HAVE_GL)
static void leave_tooltip(XtPointer tooltip, XtIntervalId *id)
{
  XtUnmanageChild(tooltip_shell);
  quit_proc = 0;
}
#endif

static void handle_tooltip(XtPointer tooltip, XtIntervalId *id)
{
#if (!HAVE_GL)
  /* if GL, we get a segfault here -- I don't know why */

  char *tip = (char *)tooltip;
  Position rx, ry;
  XmString str;
  int lines = 0;

  if (!tooltip_shell)
    {
      tooltip_shell = XtVaCreatePopupShell(tip, overrideShellWidgetClass, main_shell(ss), 
					   XmNallowShellResize, true, 
					   NULL);
      tooltip_label = XtVaCreateManagedWidget("tooltip", xmLabelWidgetClass, tooltip_shell,
					      XmNrecomputeSize, true,
					      XmNbackground, ss->lighter_blue,
					      NULL);
    }
  str = multi_line_label(tip, &lines);
  XtVaSetValues(tooltip_label, XmNlabelString, str, NULL);
  XmStringFree(str);

  XtTranslateCoords(tool_w, tool_x, tool_y, &rx, &ry);
  XtVaSetValues(tooltip_shell, XmNx, rx, XmNy, ry, NULL);
  XtManageChild(tooltip_shell);
  quit_proc = XtAppAddTimeOut(main_app(ss), (unsigned long)10000, (XtTimerCallbackProc)leave_tooltip, NULL);
#endif
}


static void tool_starter(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  XEnterWindowEvent *ev = (XEnterWindowEvent *)event;
  char *tip = (char *)context;
  if ((with_tooltips(ss)) &&
      ((ev->time - tool_last_time) > 2))
    {
      tool_x = ev->x;
      tool_y = ev->y;
      tool_w = w;
      tool_last_time = ev->time;
      tool_proc = XtAppAddTimeOut(main_app(ss), (unsigned long)300, (XtTimerCallbackProc)handle_tooltip, (XtPointer)tip);
    }
}


static void tool_stopper(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  XLeaveWindowEvent *ev = (XLeaveWindowEvent *)event;
  tool_last_time = ev->time;
  if (tool_proc != 0)
    {
      XtRemoveTimeOut(tool_proc);
      tool_proc = 0;
    }
  if (quit_proc != 0)
    {
      XtRemoveTimeOut(quit_proc);
      quit_proc = 0;
    }
  if ((tooltip_shell) && (XtIsManaged(tooltip_shell)))
    XtUnmanageChild(tooltip_shell);
}


void add_tooltip(Widget w, const char *tip)
{
  XtAddEventHandler(w, EnterWindowMask, false, tool_starter, (XtPointer)tip);
  XtAddEventHandler(w, LeaveWindowMask, false, tool_stopper, NULL);
}



/* ---------------- toolbar ---------------- */

static void add_to_toolbar(Widget bar, Pixmap icon, const char *tip, void (*callback)(Widget w, XtPointer info, XtPointer context))
{
  Widget w;
  w = XtVaCreateManagedWidget("icon", xmPushButtonWidgetClass, bar,
			      XmNlabelPixmap, icon,
			      XmNlabelType, XmPIXMAP,
			      XmNwidth, 24,
			      XmNheight, 24,
			      XmNshadowThickness, 0,
			      XmNhighlightThickness, 0,
			      /* XmNmarginHeight, 0, */
			      XmNbackground, ss->basic_color,
			      NULL);
  XtAddCallback(w, XmNactivateCallback, callback, NULL);
  add_tooltip(w, tip);
}


static void add_separator_to_toolbar(Widget bar)
{
  XtVaCreateManagedWidget("icon", xmPushButtonWidgetClass, bar,
			  XmNlabelPixmap, toolbar_icon(SND_XPM_SEPARATOR),
			  XmNlabelType, XmPIXMAP,
			  XmNwidth, 8,
			  XmNheight, 24,
			  XmNshadowThickness, 0,
			  XmNhighlightThickness, 0,
			  XmNbackground, ss->basic_color,
			  NULL);
}


#if WITH_AUDIO
static void play_from_start_callback(Widget w, XtPointer info, XtPointer context) 
{
  snd_info *sp;
  sp = any_selected_sound();
  if (sp)
    play_sound(sp, 0, NO_END_SPECIFIED);
}


static void play_from_cursor_callback(Widget w, XtPointer info, XtPointer context) 
{
  snd_info *sp;
  sp = any_selected_sound();
  if (sp)
    {
      chan_info *cp;
      cp = any_selected_channel(sp);
      if (cp)
	play_sound(sp, cursor_sample(cp), NO_END_SPECIFIED);
    }
}


static void stop_playing_callback(Widget w, XtPointer info, XtPointer context) 
{
  stop_playing_all_sounds(PLAY_C_G);
  reflect_play_selection_stop(); /* this sets ss->selection_play_stop = false; */
}
#endif


static void full_dur_callback(Widget w, XtPointer info, XtPointer context) 
{
  snd_info *sp;
  sp = any_selected_sound();
  if (sp)
    {
      uint32_t i;
      for (i = 0; i < sp->nchans; i++)
	set_x_axis_x0x1(sp->chans[i], 0.0, sp->chans[i]->axis->xmax);
    }
}


static void zoom_out_callback(Widget w, XtPointer info, XtPointer context) 
{
  snd_info *sp;
  sp = any_selected_sound();
  if (sp)
    {
      uint32_t i;
      for (i = 0; i < sp->nchans; i++)
	zx_incremented(sp->chans[i], 2.0);
    }
}


static void zoom_in_callback(Widget w, XtPointer info, XtPointer context) 
{
  snd_info *sp;
  sp = any_selected_sound();
  if (sp)
    {
      uint32_t i;
      for (i = 0; i < sp->nchans; i++)
	zx_incremented(sp->chans[i], 0.5);
    }
}    


static void goto_start_callback(Widget w, XtPointer info, XtPointer context) 
{
  snd_info *sp;
  sp = any_selected_sound();
  if (sp)
    {
      uint32_t i;
      for (i = 0; i < sp->nchans; i++)
	set_x_axis_x0x1(sp->chans[i], 0.0, sp->chans[i]->axis->x1 - sp->chans[i]->axis->x0);
    }
}

static void go_back_callback(Widget w, XtPointer info, XtPointer context) 
{
  snd_info *sp;
  sp = any_selected_sound();
  if (sp)
    {
      uint32_t i;
      for (i = 0; i < sp->nchans; i++)
	sx_incremented(sp->chans[i], -1.0);
    }
}


static void go_forward_callback(Widget w, XtPointer info, XtPointer context) 
{
  snd_info *sp;
  sp = any_selected_sound();
  if (sp)
    {
      uint32_t i;
      for (i = 0; i < sp->nchans; i++)
	sx_incremented(sp->chans[i], 1.0);
    }
}

static void goto_end_callback(Widget w, XtPointer info, XtPointer context) 
{
  snd_info *sp;
  sp = any_selected_sound();
  if (sp)
    {
      uint32_t i;
      for (i = 0; i < sp->nchans; i++)
	set_x_axis_x0x1(sp->chans[i], sp->chans[i]->axis->xmax - sp->chans[i]->axis->x1 + sp->chans[i]->axis->x0, sp->chans[i]->axis->xmax);
    }
}



static Widget toolbar = NULL;

void show_toolbar(void)
{
  if (!toolbar)
    {
      #define ICON_HEIGHT 28
      Arg args[32];
      int n;

      XtVaSetValues(main_shell(ss), XmNallowShellResize, false, NULL);

      n = attach_all_sides(args, 0);
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNheight, ICON_HEIGHT); n++;
      XtSetArg(args[n], XmNpaneMaximum, ICON_HEIGHT); n++; /* Xm/Paned initializes each pane max to 1000 apparently! */
      XtSetArg(args[n], XmNpositionIndex, 0); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNmarginHeight, 0); n++;

      if ((sound_style(ss) == SOUNDS_IN_NOTEBOOK) || 
	  (sound_style(ss) == SOUNDS_HORIZONTAL))
	toolbar = XtCreateManagedWidget("toolbar", xmRowColumnWidgetClass, sound_pane_box(ss), args, n);
      else toolbar = XtCreateManagedWidget("toolbar", xmRowColumnWidgetClass, sound_pane(ss), args, n);
      ss->toolbar = toolbar;

      if (auto_resize(ss))
	XtVaSetValues(main_shell(ss), XmNallowShellResize, true, NULL);

      make_toolbar_icons(main_shell(ss));

      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_NEW),           "new sound",                  file_new_callback);
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_OPEN),          "open sound",                 file_open_callback);
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_SAVE),          "save current sound, overwriting it", file_save_callback);
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_SAVE_AS),       "save current sound in a new file", file_save_as_callback);
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_REVERT),        "revert to saved",            file_revert_callback); 
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_UNDO),          "undo edit",                  edit_undo_callback);
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_REDO),          "redo last (undone) edit",    edit_redo_callback);
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_CLOSE),         "close selected sound",       file_close_callback);
      add_separator_to_toolbar(toolbar); 

#if WITH_AUDIO
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_PLAY),          "play from the start",        play_from_start_callback);      
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_CURSOR_PLAY),   "play from the cursor",       play_from_cursor_callback);      
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_STOP_PLAY),     "stop playing",               stop_playing_callback);      
      add_separator_to_toolbar(toolbar);
#endif
 
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_UP),            "show full sound",            full_dur_callback);      
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_ZOOM_OUT),      "zoom out",                   zoom_out_callback);      
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_ZOOM_IN),       "zoom in",                    zoom_in_callback);      
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_BACK_ARROW),    "go to start of sound",       goto_start_callback);      
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_BACK),          "go back a window",           go_back_callback);      
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_NEXT),          "go forward a window",        go_forward_callback);      
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_FORWARD_ARROW), "go to end of sound",         goto_end_callback);      
      add_separator_to_toolbar(toolbar);

      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_CUT),           "delete selection",           edit_cut_callback);      
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_PASTE),         "insert selection at cursor", edit_paste_callback);      
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_PREFERENCES),   "open preferences dialog",    options_preferences_callback);
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_STOP),          "stop the current operation", stop_everything_callback);
      add_to_toolbar(toolbar, toolbar_icon(SND_XPM_EXIT),          "exit Snd",                   file_exit_callback);

    }
  else
    {
      XtVaSetValues(main_shell(ss), XmNallowShellResize, false, NULL);
      XtManageChild(toolbar);
      if (auto_resize(ss))
	XtVaSetValues(main_shell(ss), XmNallowShellResize, true, NULL);
    }
}


void hide_toolbar(void)
{
  if (toolbar)
    {
      XtVaSetValues(main_shell(ss), XmNallowShellResize, false, NULL);
      XtUnmanageChild(toolbar);
      if (auto_resize(ss))
	XtVaSetValues(main_shell(ss), XmNallowShellResize, true, NULL);
    }
}




/* ---------------- ext lang tie-ins ---------------- */

#define INVALID_MENU -1
#define call_index(Data) (Data >> 16)
#define pack_menu_data(Slot, Menu) ((Slot << 16) | (Menu))

static void menu_callback(Widget w, XtPointer info, XtPointer context) 
{
  intptr_t callb;
  XtVaGetValues(w, XmNuserData, &callb, NULL);
  g_menu_callback(call_index(callb)); /* menu option activate callback */
}


static void GHC_callback(Widget w, XtPointer info, XtPointer context) 
{
  intptr_t slot;
  XtVaGetValues(w, XmNuserData, &slot, NULL);
  g_menu_callback(call_index(slot)); /* main menu cascading callback */
}


static Widget *main_menus = NULL;
static int main_menus_size = 0;
/* fancy code here looping through the main menus children hangs or segfaults in 86_64 unoptimized cases! */

Widget menu_widget(int which_menu)
{
  switch (which_menu)
    {
    case 0: return(file_menu);    break;
    case 1: return(edit_menu);    break;
    case 2: return(view_menu);    break;
    case 3: return(options_menu); break;
    case 4: return(help_menu);    break;
      
    default:
      if (which_menu < main_menus_size)
	return(main_menus[which_menu]);
      break;
    }
  return(NULL);
}


static bool or_over_children(Widget w, bool (*func)(Widget uw, const char *ustr), const char *str)
{
  if (w)
    {
      if ((*func)(w, str)) return(true);
      if (XtIsComposite(w))
	{
	  uint32_t i;
	  CompositeWidget cw = (CompositeWidget)w;
	  for (i = 0; i < cw->composite.num_children; i++)
	    if (or_over_children(cw->composite.children[i], func, str))
	      return(true);
	}
    }
  return(false);
}


static bool clobber_menu(Widget w, const char *name)
{
  char *wname;
  wname = XtName(w);
  if ((wname) && 
      (mus_strcmp(name, wname)) &&
      (XtIsManaged(w)))
    {
      intptr_t slot;
      XtVaGetValues(w, XmNuserData, &slot, NULL);
      unprotect_callback(call_index(slot));
      XtUnmanageChild(w);
    }
  return(true); /* in any case, don't try to recurse into the children */
}


int g_remove_from_menu(int which_menu, const char *label)
{
  Widget top_menu;
  top_menu = menu_widget(which_menu);
  if (top_menu)
    {
      or_over_children(top_menu, clobber_menu, label);
      return(0);
    }
  return(INVALID_MENU);
}


static void set_widget_name(Widget w, const char *new_name)
{
  /* based on XtName in Xt/Intrinsic.c, Xt/Create.c, and Xt/ResourceI.h */
  w->core.xrm_name = XrmStringToName(new_name);
}


static int new_menu = 5;

int g_add_to_main_menu(const char *label, int slot)
{
  static Arg args[12];
  Widget m, cas;
  int n;

  if (auto_resize(ss)) XtVaSetValues(main_shell(ss), XmNallowShellResize, false, NULL);
  new_menu++;

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNuserData, pack_menu_data(slot, new_menu)); n++;
  m = XmCreatePulldownMenu(main_menu, (char *)label, args, n);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
  XtSetArg(args[n], XmNsubMenuId, m); n++;
  XtSetArg(args[n], XmNuserData, pack_menu_data(slot, new_menu)); n++;
  cas = XtCreateManagedWidget(label, xmCascadeButtonWidgetClass, main_menu, args, n);
  if (slot >= 0) XtAddCallback(cas, XmNcascadingCallback, GHC_callback, NULL);

  if (auto_resize(ss)) XtVaSetValues(main_shell(ss), XmNallowShellResize, true, NULL);
  
  if (main_menus_size == 0)
    {
      main_menus_size = 8;
      main_menus = (Widget *)calloc(main_menus_size, sizeof(Widget));
    }
  else
    {
      if (new_menu >= main_menus_size)
	{
	  main_menus_size = new_menu + 8;
	  main_menus = (Widget *)realloc(main_menus, main_menus_size * sizeof(Widget));
	}
    }
  main_menus[new_menu] = m;
  return(new_menu);
}


Widget g_add_to_menu(int which_menu, const char *label, int callb, int position)
{
  Widget m, menw;
  Arg args[12];
  int n = 0;
  menw = menu_widget(which_menu);
  if (!menw) return(NULL);
  if (label)
    {
      uint32_t i;
      /* look for currently unused widget first */
      /*   but close-all and open-recent should be left alone! */
      CompositeWidget cw = (CompositeWidget)menw;
      for (i = 0; i < cw->composite.num_children; i++)
	{
	  m = cw->composite.children[i];
	  if ((m) && 
	      (!(XtIsManaged(m))) &&
	      (m != file_close_all_menu) &&
	      (m != file_open_recent_menu) &&
	      (m != file_open_recent_cascade_menu))
	    {
	      if (!(mus_strcmp(XtName(m), label)))
		{
		  set_widget_name(m, label);
		  set_button_label(m, label);
		}
	      if (position >= 0) XtVaSetValues(m, XmNpositionIndex, position, NULL);
	      XtVaSetValues(m, XmNuserData, pack_menu_data(callb, which_menu), NULL);
	      XtManageChild(m);
	      return(m);
	    }
	}
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      if (position >= 0) {XtSetArg(args[n], XmNpositionIndex, position); n++;}
      XtSetArg(args[n], XmNuserData, pack_menu_data(callb, which_menu)); n++;
      m = XtCreateManagedWidget(label, xmPushButtonWidgetClass, menw, args, n);
      XtAddCallback(m, XmNactivateCallback, menu_callback, NULL);
    }
  else
    {
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      if (position >= 0) {XtSetArg(args[n], XmNpositionIndex, position); n++;}
      m = XtCreateManagedWidget("sep", xmSeparatorWidgetClass, menw, args, n);
    }
  return(m);
}


static Xen g_menu_widgets(void)
{
  #define H_menu_widgets "(" S_menu_widgets "): a list of top level menu widgets: ((0)main (1)file (2)edit (3)view (4)options (5)help)"
  return(Xen_cons(Xen_wrap_widget(main_menu),
	  Xen_cons(Xen_wrap_widget(file_cascade_menu),
           Xen_cons(Xen_wrap_widget(edit_cascade_menu),
            Xen_cons(Xen_wrap_widget(view_cascade_menu),
             Xen_cons(Xen_wrap_widget(options_cascade_menu),
              Xen_cons(Xen_wrap_widget(help_cascade_menu),
	       Xen_empty_list)))))));
}

/* Motif bug: the button backgrounds remain in the original highlight color? but the widget (if it is one) is not the child of any obvious widget
 */


/* motif case needs prompt length fixups, listener if no panes, minibuffer as label not text entry
 */

/* ---------------- listener text history ---------------- */

static char **listener_strings = NULL;
static int listener_strings_size = 0, listener_strings_pos = 0;
static bool listener_first_time = true;

static void remember_listener_string(const char *str)
{
  int i, top;
  if (!str) return;

  if (listener_strings_size == 0)
    {
      listener_strings_size = 8;
      listener_strings = (char **)calloc(listener_strings_size, sizeof(char *));
    }
  
  listener_strings_pos = 0;
  listener_first_time = true;

  /* if str matches current history top entry, ignore it (as in tcsh) */
  if ((listener_strings[0]) &&
      (mus_strcmp(str, listener_strings[0])))
    return;

  top = listener_strings_size - 1;
  if (listener_strings[top]) free(listener_strings[top]);
  for (i = top; i > 0; i--) listener_strings[i] = listener_strings[i - 1];

  listener_strings[0] = mus_strdup(str);
}


static void restore_listener_string(bool back)
{
  if (listener_strings)
    {
      char *str;
      if (!(listener_first_time))
	{
	  if (back)
	    listener_strings_pos++;
	  else listener_strings_pos--;
	}
      listener_first_time = false;
      if (listener_strings_pos < 0) listener_strings_pos = 0;
      if (listener_strings_pos > (listener_strings_size - 1)) listener_strings_pos = listener_strings_size - 1;
      str = listener_strings[listener_strings_pos];
      if (str)
	append_listener_text(-1, str); 
    }
}

static bool is_prompt(const char *str, int beg)
{
  int i, j;
  /* fprintf(stderr, "check %s %s for %d at %d\n", str, ss->Listener_Prompt, ss->listener_prompt_length, beg); */

  for (i = beg, j = ss->listener_prompt_length - 1; (i >= 0) && (j >= 0); i--, j--)
    if (str[i] != ss->Listener_Prompt[j])
      {
	/* fprintf(stderr, "%c != %c at %d\n", str[i], ss->Listener_Prompt[j], j); */
      return(false);
      }
  if (j != -1) 
    {
      /* fprintf(stderr, "j: %d\n", j); */
    return(false);
    }
  if ((i == -1) || (str[i] == '\n'))
    {
      /* fprintf(stderr, "found prompt!\n"); */
    return(true);
    }
  /* fprintf(stderr, "i: %d, str[i]: %c\n", i, str[i]); */
  return(false);
}


static void listener_help_at_cursor(char *buf, int name_curpos, int len, int prompt_pos)
{
  int i, name_start, name_end;

  if (isspace(buf[name_curpos]))
    {
      for (i = name_curpos - 1; i >= 0; i--)
	if ((!isspace(buf[i])) &&
	    (buf[i] != '(') &&
	    (buf[i] != ')'))
	  break;
      if (i > 0)
	name_curpos = i;
    }

  for (i = name_curpos; i >= 0; i--)
    if ((isspace(buf[i])) ||
	(buf[i] == '(') ||
	(buf[i] == ')'))
      break;
  name_start = i + 1;

  for (i = name_curpos + 1; i < len; i++)
    if ((isspace(buf[i])) ||
	(buf[i] == '(') ||
	(buf[i] == ')'))
      break;
  name_end = i - 1;

  if (name_end > name_start)
    {
      char *new_text;

      buf[name_end + 1] = '\0';
      new_text = direct_completions((char *)(buf + name_start));

      if (new_text)
	{
	  int matches;
	  matches = get_possible_completions_size();

	  if (matches == 1)
	    {
	      Xen help;
	      help = g_snd_help(C_string_to_Xen_string(new_text), 0);
	      if (Xen_is_string(help))
		snd_help((char *)(buf + name_start), Xen_string_to_C_string(help), WITH_WORD_WRAP);
	    }
	  else
	    {
	      if (matches > 1)
		{
		  char **buffer;
		  char *help_str;
		  int match_len = 0;

		  buffer = get_possible_completions();
		  for (i = 0; i < matches; i++)
		    match_len += mus_strlen(buffer[i]);
		  
		  help_str = (char *)calloc(match_len + matches * 8, sizeof(char));
		  for (i = 0; i < matches; i++)
		    {
		      strcat(help_str, buffer[i]);
		      strcat(help_str, "\n");
		    }
		  snd_help((char *)(buf + name_start), help_str, WITHOUT_WORD_WRAP);
		  free(help_str);
		}
	    }
	  free(new_text);
	}
    }
}


static bool within_prompt(const char *str, int beg, int end)
{
  /* search backwards up to prompt length for cr (or 0), check for prompt */
  int i, lim;
  if ((beg + 1 == end) && (str[beg] == '\n')) return(false); /* end-of-page cr within double quotes probably */
  lim = beg - ss->listener_prompt_length;
  if (lim < 0) return(true);
  for (i = beg; i >= lim; i--)
    if ((str[i] == '\n') || (i == 0))
      {
	int j, k;
	for (j = 0, k = i + 1; (j < ss->listener_prompt_length) && (k < end); j++, k++)
	  if (str[k] != ss->Listener_Prompt[j])
	    return(false);
	return(true);
      }
  return(false);
}


static char *trim(char *orig)
{
  int i, j, start = -1, end = -1, len;
  char *str;

  len = mus_strlen(orig);
  if (len == 0) return(NULL);

  for (start = 0; start < len; start++)
    if ((!isspace(orig[start])) &&
	(orig[start] != '(') &&
	(orig[start] != ')'))
      break;
  if (start >= len) return(NULL);
  for (end = len - 1; end > start; end--)
    if ((!isspace(orig[end])) &&
	(orig[end] != '(') &&
	(orig[end] != ')'))
      break;
  if (start == end) return(NULL);
  
  len = end - start + 1;
  str = (char *)calloc(len + 1, sizeof(char));
  for (i = start, j = 0; i <= end; i++, j++)
    str[j] = orig[i];
  return(str);
}


static int find_matching_paren(const char *str, int parens, int pos, int *highlight_pos)
{
  int i, j;
  bool quoting = false;
  int up_comment = -1;
  for (i = pos - 1; i > 0;)
    {
      if (is_prompt(str, i))
	break;
      if (str[i] == '\"')
	{
	  /* there could be any number of slashified slashes before this quote. */
	  int k, slashes = 0;
	  for (k = i - 1; k >= 0; k--)
	    {
	      if (str[k] == '\\')
		slashes++;
	      else break;
	    }
	  if ((slashes & 1) == 0)
	    quoting = !quoting;
	}
      if (!quoting)
	{
	  if ((i <= 1) || (str[i - 1] != '\\') || (str[i - 2] != '#'))
	    {
	      if (str[i] == ')') parens++; else
	      if (str[i] == '(') parens--; else
	      if (str[i] == '\n')
		{
		  /* check for comment on next line up */
		  bool up_quoting = false;
		  int quote_comment = -1;
		  for (j = i - 1; j > 0; j--)
		    {
		      if (str[j] == '\n') break;
		      if ((str[j] == '\"') && (str[j - 1] != '\\'))
			up_quoting = !up_quoting;
		      if ((str[j] == ';') &&
			  ((j <= 1) || (str[j - 1] != '\\') || (str[j - 2] != '#')))
			{
			  if (!up_quoting)
			    up_comment = j;
			  else quote_comment = j;
			}
		    }
		  if (up_comment < i)
		    {
		      if (up_comment > 0)
			i = up_comment;
		      else
			{
			  if ((up_quoting) && (quote_comment > 0))
			    i = quote_comment;
			}
		    }
		}
	    }
	}
      if (parens == 0)
	{
	  (*highlight_pos) = i;
	  break;
	}
      i--;
    }
  return(parens);
}

#if (!HAVE_RUBY) && (!HAVE_FORTH)
static bool highlight_unbalanced_paren(void);

static int check_balance(const char *expr, int start, int end, bool in_listener) 
{
  int i;
  bool not_whitespace = false;
  int paren_count = 0;
  bool prev_separator = true;
  bool quote_wait = false;

  i = start;
  while (i < end) 
    {
      switch (expr[i]) 
	{
	case ';' :
	  /* skip till newline. */
	  do {
	    i++;
	  } while ((expr[i] != '\n') && (i < end));
	  break;

	case ' ':
	case '\n':
	case '\t':
	case '\r':
	  if ((not_whitespace) && (paren_count == 0) && (!quote_wait))
	    return(i);
	  else 
	    {
	      prev_separator = true;
	      i++;
	    }
	  break;

	case '\"' :
	  if ((not_whitespace) && (paren_count == 0) && (!quote_wait))
	    return(i);
	  else 
	    {
	      /* skip past ", ignoring \", some cases:
	       *  "\"\"" '("\"\"") "\\" "#\\(" "'(\"#\\\")"
	       */
	      while (i < end)
		{
		  i++;
		  if (expr[i] == '\\') 
		    i++;
		  else
		    {
		      if (expr[i] == '\"')
			break;
		    }
		}
	      i++;
	      if (paren_count == 0) 
		{
		  if (i < end) 
		    return(i);
		  else return(0);
		} 
	      else 
		{
		  prev_separator = true;
		  not_whitespace = true;
		  quote_wait = false;
		}
	    }
	  break;

	case '#':
	  if ((i < end - 1) &&
	      (expr[i + 1] == '|'))
	    {
	      /* (+ #| a comment |# 2 1) */
	      i++;
	      do {
		i++;
	      } while (((expr[i] != '|') || (expr[i + 1] != '#')) && (i < end));
	      i++;
	      break;
	    }
	  else
	    {
	      /* (set! *#readers* (cons (cons #\c (lambda (str) (apply make-rectangular (read)))) *#readers*))
	       */
	      if ((not_whitespace) && (paren_count == 0) && (!quote_wait))
		return(i);
	      else 
		{
		  bool found_it = false;
		  if (prev_separator)
		    {
		      int k, incr = 0;
		      for (k = i + 1; k < end; k++)
			{
			  if (expr[k] == '(')
			    {
			      /* should we look at the readers here? I want to support #c(1 2) for example */
			      not_whitespace = false;
			      prev_separator = false;
			      incr = k - i;
			      break;
			    }
			  else
			    {
			      if ((!isdigit(expr[k])) && /* #2d(...)? */
				  (!isalpha(expr[k])) && /* #c(1 2)? */
				  (expr[k] != 'D') && 
				  (expr[k] != 'd') &&
				  (expr[k] != '=') &&   /* what is this for? */
				  (expr[k] != '#'))     /* perhaps #1d(#(1 2) 3) ? */
				break;
			    }
			}
		      if (incr > 0)
			{
			  i += incr;
			  found_it = true;
			}
		    }
		  if (!found_it)
		    {
		      if ((i + 2 < end) && (expr[i + 1] == '\\') && 
			  ((expr[i + 2] == ')') || (expr[i + 2] == ';') || (expr[i + 2] == '\"') || (expr[i + 2] == '(')))
			i += 3;
		      else
			{
			  prev_separator = false;
			  quote_wait = false;
			  not_whitespace = true;
			  i++;
			}
		    }
		}
	    }
	  break;

	case '(' :
	  if ((not_whitespace) && (paren_count == 0) && (!quote_wait))
	    return(i - 1); /* 'a(...) -- ignore the (...) */
	  else 
	    {
	      i++;
	      paren_count++;
	      not_whitespace = true;
	      prev_separator = true;
	      quote_wait = false;
	    }
	  break;

	case ')' :
	  paren_count--;
	  if ((not_whitespace) && (paren_count == 0))
	    return(i + 1);
	  else 
	    {
	      i++;
	      not_whitespace = true;
	      prev_separator = true;
	      quote_wait = false;
	    }
	  break;

	case '\'' :
	case '`' :                  /* `(1 2) */
	  if (prev_separator) 
	    quote_wait = true;
	  not_whitespace = true;
	  i++;
	  break;

	case ',':                   /* `,(+ 1 2) */
	case '@':                   /* `,@(list 1 2) */
	  prev_separator = false;
	  not_whitespace = true;
	  i++;
	  break;

	default:
	  prev_separator = false;
	  quote_wait = false;
	  not_whitespace = true;
	  i++;
	  break;
	}
    }

  if ((in_listener) && (!(highlight_unbalanced_paren()))) 
    return(-1);
  return(0);
}
#endif

static char listener_prompt_buffer[LABEL_BUFFER_SIZE];

static char *listener_prompt_with_cr(void)
{
  snprintf(listener_prompt_buffer, LABEL_BUFFER_SIZE, "\n%s", listener_prompt(ss));
  return(listener_prompt_buffer);
}

#define GUI_TEXT_END(w) XmTextGetLastPosition(w)
#define GUI_TEXT_POSITION_TYPE XmTextPosition
#define GUI_TEXT(w) XmTextGetString(w)
#define GUI_TEXT_INSERTION_POSITION(w) XmTextGetInsertionPosition(w)
#define GUI_TEXT_SET_INSERTION_POSITION(w, pos) XmTextSetInsertionPosition(w, pos)
#define GUI_LISTENER_TEXT_INSERT(w, pos, text) XmTextInsert(w, pos, text)
#define GUI_FREE(w) XtFree(w)
#define GUI_SET_CURSOR(w, cursor) XUndefineCursor(XtDisplay(w), XtWindow(w)); XDefineCursor(XtDisplay(w), XtWindow(w), cursor)
#define GUI_UNSET_CURSOR(w, cursor) XUndefineCursor(XtDisplay(w), XtWindow(w)); XDefineCursor(XtDisplay(w), XtWindow(w), None)
#define GUI_UPDATE(w) XmUpdateDisplay(w)
#define GUI_TEXT_GOTO(w, pos) XmTextShowPosition(w, pos)


static int current_listener_position = -1, listener_positions_size = 0;
static int *listener_positions = NULL;


static void add_listener_position(int pos)
{
  if (listener_positions_size == 0)
    {
      listener_positions_size = 32;
      listener_positions = (int *)calloc(listener_positions_size, sizeof(int));
      current_listener_position = 0;
    }
  else
    {
      int i;
      if (pos > listener_positions[current_listener_position])
	{
	  current_listener_position++;
	  if (current_listener_position >= listener_positions_size)
	    {
	      listener_positions_size += 32;
	      listener_positions = (int *)realloc(listener_positions, listener_positions_size * sizeof(int));
	      for (i = current_listener_position + 1; i < listener_positions_size; i++) listener_positions[i] = 0;
	    }
	}
      else
	{
	  for (i = current_listener_position - 1; i >= 0; i--)
	    if (listener_positions[i] < pos)
	      break;
	  current_listener_position = i + 1;
	}
    }
  listener_positions[current_listener_position] = pos;
}


static void backup_listener_to_previous_expression(void)
{
  if (current_listener_position > 0)
    {
      current_listener_position--;
      listener_delete_text(listener_positions[current_listener_position]);
    }
}

#if HAVE_SCHEME
static s7_pointer top_level_let = NULL;
static s7_pointer g_top_level_let(s7_scheme *sc, s7_pointer args)
{
  return(top_level_let);
}

static s7_pointer g_set_top_level_let(s7_scheme *sc, s7_pointer args)
{
  top_level_let = s7_car(args);
  return(top_level_let);
}
#endif

static void listener_return(widget_t w, int last_prompt)
{
#if (!USE_NO_GUI)
  /* try to find complete form either enclosing current cursor, or just before it */
  GUI_TEXT_POSITION_TYPE cmd_eot = 0;
  char *str = NULL, *full_str = NULL;
  int i, j;
  Xen form = Xen_undefined;
  GUI_TEXT_POSITION_TYPE last_position = 0, current_position = 0;

#if (!HAVE_RUBY && !HAVE_FORTH)
  GUI_TEXT_POSITION_TYPE end_of_text = 0, start_of_text = 0;
  int parens;
#if USE_MOTIF
  GUI_TEXT_POSITION_TYPE new_eot = 0;
#endif
#endif

  full_str = GUI_TEXT(w);
  current_position = GUI_TEXT_INSERTION_POSITION(w);
#if (!HAVE_RUBY && !HAVE_FORTH)
  start_of_text = current_position;
  end_of_text = current_position;
#endif
  last_position = GUI_TEXT_END(w);
  add_listener_position(last_position);

#if (!HAVE_SCHEME)
  if (have_read_hook())
    {
      Xen result;
      int len;
      len = last_position - last_prompt;
      if (len > 0)
	{
	  str = (char *)calloc(len + 1, sizeof(char)); 
	  for (i = last_prompt, j = 0; i < last_position; i++, j++) str[j] = full_str[i]; 
	  result = run_read_hook(str);
	  free(str);
	  if (Xen_is_true(result)) 
	    {
	      if (full_str) GUI_FREE(full_str);
	      return;
	    }
	}
    }
#endif

  /* prompt = listener_prompt(ss); */

  /* first look for a form just before the current mouse location,
   *   independent of everything (i.e. user may have made changes
   *   in a form included in a comment, then typed return, expecting
   *   us to use the new version, but the check_balance procedure
   *   tries to ignore comments).
   */

  str = NULL;

#if HAVE_RUBY || HAVE_FORTH
  {
    int k, len, start, full_len;
    for (i = current_position - 1; i >= 0; i--)
      if (is_prompt(full_str, i))
	{
	  full_len = strlen(full_str);
	  for (k = current_position - 1; k < full_len; k++)
	    if (full_str[k] == '\n')
	      break;
	  start = i + 1;
	  len = (k - start + 1);
	  str = (char *)calloc(len, sizeof(char));
	  for (k = 0; k < len - 1; k++)
	    str[k] = full_str[k + start];
          break; 
	}
  }
#else
  if (last_position > end_of_text)
    {
      end_of_text = last_position; /* added 12-Nov-07 for first form */
      for (i = current_position; i < last_position; i++)
	if (is_prompt(full_str, i + 1))
	  {
	    /* fprintf(stderr, "set end to %d (%d)\n", i - ss->listener_prompt_length + 1, i); */
	    end_of_text = i - ss->listener_prompt_length + 1;
	    break;
	  }
    }
  if (start_of_text > 0)
    {
      for (i = end_of_text; i >= 0; i--)
	if (is_prompt(full_str, i))
	  {
	    /* fprintf(stderr, "set start to %d\n", i + 1); */
	    start_of_text = i + 1;
	    break;
	  }
    }

  /* fprintf(stderr, "found %d %d\n", start_of_text, end_of_text); */
  
  if (end_of_text > start_of_text)
    {
      int slen;
      parens = 0;
      slen = end_of_text - start_of_text + 2;
      str = (char *)calloc(slen, sizeof(char));
      for (i = start_of_text, j = 0; i <= end_of_text; j++, i++) 
	{
	  str[j] = full_str[i]; 
	  if (str[j] == '(') 
	    parens++;
	}
      str[end_of_text - start_of_text + 1] = 0;
      end_of_text = mus_strlen(str);
      
      if (parens)
	{
	  end_of_text = check_balance(str, 0, (int)end_of_text, true); /* last-arg->we are in the listener */
	  if ((end_of_text > 0) && 
	      (end_of_text < slen))
	    {
	      if (end_of_text < (slen - 1))
		str[end_of_text + 1] = 0;
	      else str[end_of_text] = 0;
	      if (str[end_of_text] == '\n') str[end_of_text] = 0;
	    }
	  else
	    {
	      free(str);
	      str = NULL;
	      if (end_of_text < 0)
		listener_append_and_prompt(NULL);
	      else 
		{
#if USE_MOTIF
		  new_eot = GUI_TEXT_END(w);
		  GUI_LISTENER_TEXT_INSERT(w, new_eot, (char *)"\n");
#else
		  GUI_LISTENER_TEXT_INSERT(w, 0, (char *)"\n");
#endif
		}
	      if (full_str) GUI_FREE(full_str);
	      return;
	    }
	}
      else
	{
	  /* no parens -- pick up closest entity */
	  int loc, k, len;
	  char *tmp;
	  loc = current_position - start_of_text - 1;
	  for (i = loc; i >= 0; i--)
	    if ((str[i] == '\n') || (i == 0))
	      {
		len = mus_strlen(str);
		tmp = (char *)calloc(len + 1, sizeof(char));
		if (i != 0) i++;
		for (k = 0; i < len; i++, k++) 
		  if ((i > loc) &&
		      ((str[i] == '\n') || 
		       (str[i] == ' ')))
		    break;
		  else tmp[k] = str[i];
		free(str);
		str = tmp;
		break;
	      }
	}
    }
#endif

  if (full_str) GUI_FREE(full_str);
  {
    bool need_eval = false;
    int i, len;
    /* fprintf(stderr, "return: %s\n", str); */
    
    len = mus_strlen(str);
    for (i = 0; i < len; i++)
      if ((str[i] != ' ') &&
	  (str[i] != '\n') &&
	  (str[i] != '\r') &&
	  (str[i] != '\t'))
	{
	  need_eval = true;
	  break;
	}
    if (!need_eval)
      append_listener_text(-1, "\n");
    else
      {
	if (str)
	  {
	    char *errmsg = NULL;
	    
	    if (current_position < (last_position - 2))
	      GUI_LISTENER_TEXT_INSERT(w, GUI_TEXT_END(w), str);
	    
	    GUI_SET_CURSOR(w, ss->wait_cursor);
	    GUI_UPDATE(w); /* not sure about this... */
	    
	    if ((mus_strlen(str) > 1) || (str[0] != '\n'))
	      remember_listener_string(str);
	    
#if HAVE_RUBY || HAVE_FORTH
	    form = Xen_eval_C_string(str);
#endif
	    
#if HAVE_SCHEME
	    /* very tricky -- we need the interface running to see C-g, and in ordinary (not-hung, but very slow-to-compute) code
	     *   we need to let other interface stuff run.  We can't look at each event and flush all but the C-g we're waiting
	     *   for because that confuses the rest of the GUI, and some such interactions are expected.  But if interface actions
	     *   are tied to scheme code, the check_for_event lets that code be evaluated, even though we're actually running
	     *   the evaluator already in a separate thread.  If we block on the thread ID (pthread_self), bad stuff still gets
	     *   through somehow.  
	     *
	     * s7 threads here only solves the s7 side of the problem.
	     *
	     * So... set begin_hook to a func that calls check_for_event;
	     *   if C-g, the begin_hook func returns true, and s7 calls s7_quit, and C_g_typed is true here.
	     *   Otherwise, I think anything is safe because we're only looking at the block start, and
	     *   we're protected there by a stack barrier.  
	     *
	     * But this polling at block starts is expensive, mainly because XtAppPending
	     *   are very slow.  So with_interrupts can turn off this check.
	     */
	    
	    if (s7_begin_hook(s7)) return;      /* s7 is already running (user typed <cr> during computation) */
	    
	    if ((mus_strlen(str) > 1) || (str[0] != '\n'))
	      {
		s7_int gc_loc;
		s7_pointer old_port;
		
		old_port = s7_set_current_error_port(s7, s7_open_output_string(s7));
		gc_loc = s7_gc_protect(s7, old_port);
		
		if (with_interrupts(ss)) 
		  s7_set_begin_hook(s7, listener_begin_hook);
		
		if (have_read_hook()) 
		  form = run_read_hook(str);
		else form = s7_eval_c_string_with_environment(s7, str, top_level_let);
		
		s7_set_begin_hook(s7, NULL);
		if (ss->C_g_typed)
		  {
		    errmsg = mus_strdup("\nSnd interrupted!");
		    ss->C_g_typed = false;
		  }
		else errmsg = mus_strdup(s7_get_output_string(s7, s7_current_error_port(s7)));
		
		s7_close_output_port(s7, s7_current_error_port(s7));
		s7_set_current_error_port(s7, old_port);
		s7_gc_unprotect_at(s7, gc_loc);
	      }
#endif
	    
	    if (errmsg)
	      {
		if (*errmsg)
		  snd_display_result(errmsg, NULL);
		free(errmsg);
	      }
	    else snd_report_listener_result(form); /* used to check for unbound form here, but that's no good in Ruby */
	    
	    free(str);
	    str = NULL;
	    GUI_UNSET_CURSOR(w, ss->arrow_cursor); 
	  }
	else
	  {
	    listener_append_and_prompt(NULL);
	  }
      }
  }

  cmd_eot = GUI_TEXT_END(w);
  add_listener_position(cmd_eot);
  GUI_TEXT_GOTO(w, cmd_eot - 1);
  GUI_TEXT_SET_INSERTION_POSITION(w, cmd_eot + 1);
#endif
}



/* -------------------------------------------------------------------------------- */


#define OVERRIDE_TOGGLE 1
/* Motif 2.0 defines control-button1 to be "take focus" -- this is not a good idea!! */


static void Tab_completion(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  int completer;
  intptr_t data;

  XtVaGetValues(w, XmNuserData, &data, NULL);
  completer = (int)data;

  if (completer >= 0)
    {
      int matches;
      char *old_text, *new_text;

      old_text = XmTextGetString(w);
      if (mus_strlen(old_text) == 0) return; /* C-x C-f TAB in kbd??, for example */

      new_text = complete_text(w, old_text, completer);
      if (mus_strlen(new_text) == 0) return; /* can this happen? */
      XmTextSetString(w, new_text);
      XmTextSetCursorPosition(w, XmTextGetLastPosition(w));

      matches = get_completion_matches();

      if ((mus_strcmp(old_text, new_text)) && 
	  (matches != -1))
	{
	  Pixel old_color;
	  XtVaGetValues(w, XmNforeground, &old_color, NULL);
	  if (matches > 1)
	    XtVaSetValues(w, XmNforeground, ss->green, NULL);
	  else 
	    if (matches == 0) 
	      XtVaSetValues(w, XmNforeground, ss->red, NULL);
	  if (matches != 1)
	    {
	      XmUpdateDisplay(w);
	      sleep(1);
	      XtVaSetValues(w, XmNforeground, old_color, NULL);
	      XmUpdateDisplay(w);
	    }

	  if (matches > 1)                          /* there are several possible completions -- let the widget decide how to handle it */
	    handle_completions(w, completer);
	}

      if (old_text) XtFree(old_text);
      if (new_text) free(new_text);
    }
}


/* listener completions */

static Widget listener_text = NULL;
static Widget listener_pane = NULL;  /* form widget that hold the listener scrolled text widget */

static Widget completions_list = NULL;
static Widget completions_pane = NULL;


static void perform_completion(XmString selection)
{
  int i, j, old_len, new_len;
  char *text = NULL, *old_text = NULL;

  text = (char *)XmStringUnparse(selection, NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);
  save_completion_choice(text);

  old_text = XmTextGetString(listener_text);
  old_len = mus_strlen(old_text);
  new_len = mus_strlen(text);
  for (i = old_len - 1, j = new_len - 1; j >= 0; j--)
    {
      if (old_text[i] != text[j])
	{
	  i = old_len - 1;
	  if (old_text[i] == text[j]) i--;
	  /* this added 15-Apr-02 for case like map-chan(nel) */
	  /*   probably should go back new_len and scan forwards instead */
	}
      else i--;
    }

  append_listener_text(XmTextGetLastPosition(listener_text), (char *)(text - 1 + old_len - i));
 
  if (text) XtFree(text);
  if (old_text) XtFree(old_text);
}


static void listener_completions_browse_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)info;
  /* choice = cbs->item_position - 1; */
  perform_completion(cbs->item);
  XtUnmanageChild(completions_pane);
}


static int alphabetize(const void *a, const void *b)
{
  return(strcmp(*((const char **)a), (*(const char **)b)));
}


static int find_prompt(Widget w, XmTextPosition start)
{
  Boolean found_prompt = false, found_newline;
  XmTextPosition loc = 0;

  if (start == 0) /* try to avoid strlen null in motif */
    return(0);

  /* the prompt defaults to ">", so a naive backwards search for the prompt is easily confused.
   *   (bugfix thanks to Tito Latini).
   */
  while (!found_prompt)
    {
      found_newline = XmTextFindString(w, start, (char *)"\n", XmTEXT_BACKWARD, &loc);
      start = found_newline ? loc : 0;
      found_prompt = XmTextFindString(w, start, listener_prompt(ss), XmTEXT_FORWARD, &loc);
      if ((found_prompt && loc <= (start + 1)) || (start == 0))
        break;
      start--;
    }

  if (!found_prompt) 
    return(0);
  else return((int)loc + mus_strlen(listener_prompt(ss)));
}


static void motif_listener_completion(Widget w, XEvent *event, char **str, Cardinal *num)  /* change name because emacs is confused */
{
  /* used only by the listener widget -- needs to be smart about text since overall string can be enormous 
   *   and we don't want to back up past the last prompt
   *   also if at start of line (or all white-space to previous \n), indent
   */
  int beg, end, replace_end, len;
  char *old_text;

  if ((completions_pane) &&
      (XtIsManaged(completions_pane)))
    {
      XmString *strs = NULL;
      XtVaGetValues(completions_list, 
		    XmNselectedItems, &strs, 
		    NULL);
      if (strs)
	{
	  perform_completion(strs[0]);
	  XtUnmanageChild(completions_pane);
	  return;
	}
    }

  end = XmTextGetInsertionPosition(w);
  replace_end = end;

  beg = find_prompt(w, (XmTextPosition)end);
  len = end - beg + 1;

  old_text = (char *)calloc(len + 1, sizeof(char));
  XmTextGetSubstring(w, beg, len, len + 1, old_text);
  /* now old_text is the stuff typed since the last prompt */

  if (old_text[len - 1] == '\n')
    {
      old_text[len - 1] = 0;
      end--;
    }

  if (old_text)
    {
      int matches = 0;
      char *new_text = NULL, *file_text = NULL;
      bool try_completion = true;
      new_text = complete_listener_text(old_text, end, &try_completion, &file_text);

      if (!try_completion)
	{
	  free(old_text);
	  return;
	}

      if (mus_strcmp(old_text, new_text))
	matches = get_completion_matches();
      else XmTextReplace(w, beg, replace_end, new_text);

      if (new_text) 
	{
	  free(new_text); 
	  new_text = NULL;
	}

      if (matches > 1)
	{
	  int num;
	  clear_possible_completions();
	  set_save_completions(true);
	  if (file_text) 
	    new_text = filename_completer(w, file_text, NULL);
	  else new_text = expression_completer(w, old_text, NULL);
	  if (new_text) 
	    {
	      free(new_text); 
	      new_text = NULL;
	    }
	  num = get_possible_completions_size();
	  if (num > 0)
	    {
	      int i;
	      XmString *match;
	      char **buffer;

	      if (!completions_list)
		{
		  Arg args[20];
		  int n = 0;

		  XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
		  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
		  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
		  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
		  XtSetArg(args[n], XmNscrollBarPlacement, XmBOTTOM_LEFT); n++;
		  XtSetArg(args[n], XmNbackground, ss->white); n++;
		  XtSetArg(args[n], XmNforeground, ss->black); n++;
		  XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;

		  completions_list = XmCreateScrolledList(listener_pane, (char *)"completion-help-text", args, n);
		  completions_pane = XtParent(completions_list);

		  XtAddCallback(completions_list, XmNbrowseSelectionCallback, listener_completions_browse_callback, NULL);
		  XtManageChild(completions_list);
		}

	      buffer = get_possible_completions();
	      qsort((void *)buffer, num, sizeof(char *), alphabetize);

	      match = (XmString *)calloc(num, sizeof(XmString));
	      for (i = 0; i < num; i++) 
		match[i] = XmStringCreateLocalized(buffer[i]);

	      XtVaSetValues(completions_list, 
			    XmNitems, match, 
			    XmNitemCount, num, 
			    XmNvisibleItemCount, mus_iclamp(1, num, 20), 
			    NULL);

	      if (!(XtIsManaged(completions_pane)))
		XtManageChild(completions_pane);

	      /* look at previous completions list first for a match, then
	       *   look back from "beg" for any member of the match list previously typed
	       */
	      {
		int row;
		row = find_best_completion(buffer, num);
		XmListSelectPos(completions_list, row, false);
		ensure_list_row_visible(completions_list, row);
	      }

	      for (i = 0; i < num; i++) 
		XmStringFree(match[i]);
	      free(match);
	    }
	  set_save_completions(false);
	}

      if (file_text) free(file_text);
      if (old_text) free(old_text);
    }
}




/* ---------------- text widget specializations ---------------- */

void textfield_focus_callback(Widget w, XtPointer context, XtPointer info)
{
  XtVaSetValues(w, XmNbackground, ss->text_focus_color, NULL);
  XtVaSetValues(w, XmNcursorPositionVisible, true, NULL);
}


void textfield_unfocus_callback(Widget w, XtPointer context, XtPointer info)
{
  XtVaSetValues(w, XmNbackground, ss->basic_color, NULL);
  XtVaSetValues(w, XmNcursorPositionVisible, false, NULL);
}


static void textfield_no_color_focus_callback(Widget w, XtPointer context, XtPointer info)
{
  XtVaSetValues(w, XmNcursorPositionVisible, true, NULL);
}


static void textfield_no_color_unfocus_callback(Widget w, XtPointer context, XtPointer info)
{
  XtVaSetValues(w, XmNcursorPositionVisible, false, NULL);
}


/* -------- specialized action procs -------- */

static bool actions_loaded = false;
#define CONTROL_KEY 4

static void No_op(Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  /* return does not cause widget activation in many textfield cases -- it is a true no-op */
}


#define snd_K_u XK_u 
#define snd_K_x XK_x 

static void Activate_keyboard(Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  /* make the current channel active preloading kbd cmd with str[0]+ctrl bit */
  chan_info *cp;
  cp = current_channel();
  if (cp) 
    {
      goto_graph(cp);
      keyboard_command(cp, (str[0][0] == 'u') ? snd_K_u : snd_K_x, CONTROL_KEY);
    }
}


static char *listener_selection = NULL;

static void Kill_line(Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  /* C-k with storage of killed text */
  XmTextPosition curpos, loc;
  Boolean found;
  curpos = XmTextGetCursorPosition(w);
  found = XmTextFindString(w, curpos, (char *)"\n", XmTEXT_FORWARD, &loc);
  if (!found) loc = XmTextGetLastPosition(w);
  if (loc > curpos)
    {
      if (listener_selection) {XtFree(listener_selection); listener_selection = NULL;}
      XmTextSetSelection(w, curpos, loc, CurrentTime);
      listener_selection = XmTextGetSelection(w); /* xm manual p329 sez storage is allocated here */
      XmTextCut(w, CurrentTime);
    }
}


static void Yank(Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  /* copy current selection at current cursor position */
  if (listener_selection) 
    {
      XmTextPosition curpos;
      curpos = XmTextGetCursorPosition(w);
      XmTextInsert(w, curpos, listener_selection);
      curpos += strlen(listener_selection);
      XmTextShowPosition(w, curpos);
      XmTextSetCursorPosition(w, curpos);
      XmTextClearSelection(w, ev->xkey.time); /* so C-y + edit doesn't forbid the edit */
    }
}


static void Begin_of_line(Widget w, XEvent *ev, char **ustr, Cardinal *num) 
{
  /* don't back up before listener prompt */
  XmTextPosition curpos, loc;
  Boolean found;
  curpos = XmTextGetCursorPosition(w) - 1;
  found = XmTextFindString(w, curpos, (char *)"\n", XmTEXT_BACKWARD, &loc);
  if (curpos >= ss->listener_prompt_length - 1)
    {
      char *str = NULL;
      str = (char *)calloc(ss->listener_prompt_length + 3, sizeof(char));
      loc = found ? loc + 1 : 0;
      XmTextGetSubstring(w, loc, ss->listener_prompt_length, ss->listener_prompt_length + 2, str);
      if (strncmp(listener_prompt(ss), str, ss->listener_prompt_length) == 0)
	XmTextSetCursorPosition(w, loc + ss->listener_prompt_length);
      else XmTextSetCursorPosition(w, loc);
      free(str);
    }
  else XmTextSetCursorPosition(w, 0);
}


static void Delete_region(Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  XmTextCut(w, CurrentTime);
}


static XmTextPosition down_pos, last_pos;
static Xen listener_click_hook; 

static void B1_press(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  XmTextPosition pos;
  XButtonEvent *ev = (XButtonEvent *)event;
  XmProcessTraversal(w, XmTRAVERSE_CURRENT);
  /* we're replacing the built-in take_focus action here, so do it by hand, but leave listener blue, so to speak */
  if (w != listener_text)
    XtVaSetValues(w, XmNbackground, ss->white, NULL);
  else XmTextClearSelection(listener_text, CurrentTime); /* should this happen in other windows as well? */
  pos = XmTextXYToPos(w, (Position)(ev->x), (Position)(ev->y));
  XmTextSetCursorPosition(w, pos);
  down_pos = pos;
  last_pos = pos;
  if (Xen_hook_has_list(listener_click_hook))
    run_hook(listener_click_hook,
	     Xen_list_1(C_int_to_Xen_integer((int)pos)),
	     S_listener_click_hook);
}


static void B1_move(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  XmTextPosition pos;
  XButtonEvent *ev = (XButtonEvent *)event;
  pos = XmTextXYToPos(w, (Position)(ev->x), (Position)(ev->y));
  if (last_pos > pos)                                 /* must have backed up the cursor */
    XmTextSetHighlight(w, pos, last_pos, XmHIGHLIGHT_NORMAL);
  if (down_pos != pos)
    XmTextSetHighlight(w, down_pos, pos, XmHIGHLIGHT_SELECTED);
  last_pos = pos;
}


static void B1_release(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  XmTextPosition pos;
  XButtonEvent *ev = (XButtonEvent *)event;
  pos = XmTextXYToPos(w, (Position)(ev->x), (Position)(ev->y));
  XmTextSetCursorPosition(w, pos);
  if (down_pos != pos)
    {
      XmTextSetHighlight(w, down_pos, pos, XmHIGHLIGHT_SELECTED);
      if (listener_selection) {XtFree(listener_selection); listener_selection = NULL;}
      XmTextSetSelection(w, down_pos, pos, CurrentTime);
      listener_selection = XmTextGetSelection(w);
    }
}


static void Text_transpose(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  XmTextPosition curpos;
  curpos = XmTextGetCursorPosition(w);
  if (curpos > 1)
    {
      char buf[3]; /* needs room for null */
      char tmp;
      XmTextGetSubstring(w, (XmTextPosition)(curpos - 1), 2, 3, buf);
      tmp = buf[0];
      buf[0] = buf[1];
      buf[1] = tmp;
      XmTextReplace(w, curpos - 1, curpos + 1, buf);
      XmTextSetCursorPosition(w, curpos + 1);
    }
}


static void Complain(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  char *old_text, *new_text;
  XmTextPosition curpos;
  int len;

  curpos = XmTextGetCursorPosition(w);
  old_text = XmTextGetString(w);
  len = mus_strlen(old_text) + 5;
  new_text = (char *)calloc(len, sizeof(char));
  snprintf(new_text, len, "%s C-%c", (old_text) ? old_text : "", str[0][0]);

  XmTextSetString(w, new_text);
  XmTextSetCursorPosition(w, curpos);

  if (old_text) XtFree(old_text);
  free(new_text);
}


static void text_at_cursor(Widget w)
{
  XmTextPosition curpos, endpos, start, end;
  int len, prompt_pos;
  char *buf;

  curpos = XmTextGetCursorPosition(w);
  if (curpos <= 1)
    curpos = XmTextGetInsertionPosition(w);
  if (curpos <= 1)
    {
      snd_help("Listener", "This is the 'listener', a text widget in which you can interact with Snd's extension language.  See extsnd.html.", WITH_WORD_WRAP);
      return;
    }

  prompt_pos = find_prompt(w, curpos);

  if (curpos > 40)
    start = curpos - 40;
  else start = 0;
  if (start < prompt_pos)
    start = prompt_pos;

  endpos = XmTextGetLastPosition(w);
  if ((endpos - curpos) > 40)
    end = curpos + 40;
  else end = endpos;

  len = end - start + 1;
  buf = (char *)calloc(len + 1, sizeof(char));
  XmTextGetSubstring(w, start, len, len + 1, buf);

  listener_help_at_cursor(buf, curpos - start - 1, len, prompt_pos);
  free(buf);
}


static void Help_At_Cursor(Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  text_at_cursor(w);
}


static void Word_upper(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  bool up, cap;
  XmTextPosition curpos, endpos;
  up = (str[0][0] == 'u');
  cap = (str[0][0] == 'c');
  curpos = XmTextGetCursorPosition(w);
  endpos = XmTextGetLastPosition(w);
  if (curpos < endpos)
    {
      int i, length, wstart, wend;
      char *buf = NULL;
      length = endpos - curpos;
      buf = (char *)calloc(length + 1, sizeof(char));
      XmTextGetSubstring(w, curpos, length, length + 1, buf);
      wstart = 0;
      wend = length;
      for (i = 0; i < length; i++)
	if (!isspace((int)(buf[i])))
	  {
	    wstart = i;
	    break;
	  }
      for (i = wstart + 1; i < length; i++)
	if (isspace((int)(buf[i])))
	  {
	    wend = i;
	    break;
	  }
      if (cap)
	{
	  buf[0] = toupper(buf[wstart]);
	  buf[1] = '\0';
	  XmTextReplace(w, curpos + wstart, curpos + wstart + 1, buf);
	}
      else
	{
	  int j;
	  for (i = wstart, j = 0; i < wend; i++, j++)
	    if (up) 
	      buf[j] = toupper(buf[i]);
	    else buf[j] = tolower(buf[i]);
	  buf[j] = '\0';
	  XmTextReplace(w, curpos + wstart, curpos + wend, buf);
	}
      XmTextSetCursorPosition(w, curpos + wend);
      if (buf) free(buf);
    }
}


void append_listener_text(int end, const char *msg)
{
  if (listener_text)
    {
      if (end == -1) end = XmTextGetLastPosition(listener_text);
      XmTextInsert(listener_text, end, (char *)msg);
      XmTextSetCursorPosition(listener_text, XmTextGetLastPosition(listener_text));
    }
}


static bool dont_check_motion = false;

void listener_delete_text(int new_end)
{
  int old_end;
  old_end = XmTextGetLastPosition(listener_text);
  if (old_end > new_end)
    {
      dont_check_motion = true;
      XmTextSetSelection(listener_text, new_end, old_end, CurrentTime);
      XmTextRemove(listener_text);
      dont_check_motion = false;
    }
}


static void Listener_Meta_P(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  listener_delete_text(find_prompt(w, XmTextGetInsertionPosition(w)));
  restore_listener_string(true);
}


static void Listener_Meta_N(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  listener_delete_text(find_prompt(w, XmTextGetInsertionPosition(w)));
  restore_listener_string(false);
}


int save_listener_text(FILE *fp)
{
  /* return -1 if fwrite problem */
  if (listener_text)
    {
      char *str = NULL;
      str = XmTextGetString(listener_text);
      if (str)
	{
	  size_t bytes;
	  bytes = fwrite((void *)str, sizeof(char), mus_strlen(str), fp);
	  XtFree(str);
	  if (bytes == 0) return(-1);
	}
    }
  return(0);
}


static void Listener_clear(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  clear_listener();
}


static void Listener_g(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  ss->C_g_typed = true;
  control_g(any_selected_sound());
}


static void Listener_Backup(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  backup_listener_to_previous_expression();
}


static void Listener_Arrow_Up(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  if ((completions_pane) &&
      (XtIsManaged(completions_pane)))
    {
      int *ns;
      int n;
      XmListGetSelectedPos(completions_list, &ns, &n);
      if (ns[0] > 1)
	XmListSelectPos(completions_list, ns[0] - 1, false);
      free(ns);
    }
  else XtCallActionProc(w, "previous-line", event, str, *num);
}


static void Listener_Arrow_Down(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  if ((completions_pane) &&
      (XtIsManaged(completions_pane)))
    {
      int *ns;
      int n;
      XmListGetSelectedPos(completions_list, &ns, &n);
      XmListSelectPos(completions_list, ns[0] + 1, false);
      free(ns);
    }
  else XtCallActionProc(w, "next-line", event, str, *num);
}


static void Listener_Return(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  if ((completions_pane) &&
      (XtIsManaged(completions_pane)))
    {
      XmString *strs = NULL;
      XtVaGetValues(completions_list, 
		    XmNselectedItems, &strs, 
		    NULL);
      if (strs)
	{
	  perform_completion(strs[0]);
	  XtUnmanageChild(completions_pane);
	}
    }
  else XtCallActionProc(w, "activate", event, str, *num);
}


#define NUM_ACTS 24
static XtActionsRec acts[NUM_ACTS] = {
  {(char *)"no-op",                      No_op},
  {(char *)"activate-keyboard",          Activate_keyboard},
  {(char *)"yank",                       Yank},
  {(char *)"delete-region",              Delete_region},
  {(char *)"kill-line",                  Kill_line},
  {(char *)"begin-of-line",              Begin_of_line},
  {(char *)"b1-press",                   B1_press},
  {(char *)"b1-move",                    B1_move},
  {(char *)"b1-release",                 B1_release},
  {(char *)"text-transpose",             Text_transpose},
  {(char *)"word-upper",                 Word_upper},
  {(char *)"tab-completion",             Tab_completion},
  {(char *)"listener-completion",        motif_listener_completion},
  {(char *)"listener-clear",             Listener_clear},
  {(char *)"listener-g",                 Listener_g},
  {(char *)"listener-meta-p",            Listener_Meta_P},
  {(char *)"listener-meta-n",            Listener_Meta_N},
  {(char *)"listener-next-line",         Listener_Arrow_Down},
  {(char *)"listener-previous-line",     Listener_Arrow_Up},
  {(char *)"listener-return",            Listener_Return},
  {(char *)"delete-to-previous-command", Listener_Backup},
  {(char *)"complain",                   Complain},
  {(char *)"help-at-cursor",             Help_At_Cursor},
};


/* translation tables for emacs compatibility and better inter-widget communication */
/* these values are listed in lib/Xm/Transltns.c */

/* for textfield (single-line) widgets */
static char TextTrans2[] =
       "Ctrl <Key>a:	    beginning-of-line()\n\
	Ctrl <Key>b:	    backward-character()\n\
	Mod1 <Key>b:	    backward-word()\n\
	Mod1 <Key>c:	    word-upper(c)\n\
        Ctrl <Key>c:        complain(c)\n\
	Ctrl <Key>d:	    delete-next-character()\n\
	Mod1 <Key>d:	    delete-next-word()\n\
	Ctrl <Key>e:	    end-of-line()\n\
	Ctrl <Key>f:	    forward-character()\n\
	Mod1 <Key>f:	    forward-word()\n\
	Ctrl <Key>g:	    activate()\n\
	Ctrl <Key>h:	    delete-previous-character()\n\
        Ctrl <Key>i:        complain(i)\n\
        Ctrl <Key>j:        complain(j)\n\
	Ctrl <Key>k:	    delete-to-end-of-line()\n\
	Mod1 <Key>l:	    word-upper(l)\n\
        Ctrl <Key>m:        complain(m)\n\
        Ctrl <Key>n:        complain(n)\n\
	Mod1 <Key>n:	    activate()\n\
        Ctrl <Key>o:        complain(o)\n\
	Mod1 <Key>p:	    activate()\n\
        Ctrl <Key>p:        complain(p)\n\
        Ctrl <Key>q:        complain(q)\n\
        Ctrl <Key>r:        activate()\n\
        Ctrl <Key>s:        activate()\n\
	Ctrl <Key>t:	    text-transpose()\n\
	Mod1 <Key>u:	    word-upper(u)\n\
        Ctrl <Key>u:        complain(u)\n\
        Ctrl <Key>v:        complain(v)\n\
        Ctrl <Key>w:        complain(w)\n\
	Ctrl <Key>x:	    activate-keyboard(x)\n\
        Ctrl <Key>y:        complain(y)\n\
        Ctrl <Key>z:        complain(z)\n\
	Mod1 <Key><:	    beginning-of-line()\n\
	Mod1 <Key>>:	    end-of-line()\n\
	<Key>Delete:	    delete-previous-character()\n\
	Mod1 <Key>Delete:   delete-to-start-of-line()\n\
	<Key>Tab:	    tab-completion()\n\
	<Key>Return:	    activate()\n";
static XtTranslations transTable2 = NULL;


/* same (but not activatable), try to avoid causing the currently active pushbutton widget to appear to be activated by <cr> in the text widget */
static char TextTrans6[] =
       "Ctrl <Key>a:	    beginning-of-line()\n\
	Ctrl <Key>b:	    backward-character()\n\
	Mod1 <Key>b:	    backward-word()\n\
	Mod1 <Key>c:	    word-upper(c)\n\
	Ctrl <Key>d:	    delete-next-character()\n\
	Mod1 <Key>d:	    delete-next-word()\n\
	Ctrl <Key>e:	    end-of-line()\n\
	Ctrl <Key>f:	    forward-character()\n\
	Mod1 <Key>f:	    forward-word()\n\
	Ctrl <Key>g:	    no-op()\n\
	Ctrl <Key>h:	    delete-previous-character()\n\
	Ctrl <Key>k:	    delete-to-end-of-line()\n\
	Mod1 <Key>l:	    word-upper(l)\n\
	Ctrl <Key>t:	    text-transpose()\n\
	Mod1 <Key>u:	    word-upper(u)\n\
	Mod1 <Key><:	    beginning-of-line()\n\
	Mod1 <Key>>:	    end-of-line()\n\
	<Key>Delete:	    delete-previous-character()\n\
	Mod1 <Key>Delete:   delete-to-start-of-line()\n\
	<Key>Tab:	    tab-completion()\n\
	<Key>Return:	    no-op()\n";
static XtTranslations transTable6 = NULL;


/* for text (multi-line) widgets */
static char TextTrans3[] =
       "Ctrl <Key>a:	    beginning-of-line()\n\
	Ctrl <Key>b:	    backward-character()\n\
	Mod1 <Key>b:	    backward-word()\n\
	Mod1 <Key>c:	    word-upper(c)\n\
	Ctrl <Key>d:	    delete-next-character()\n\
	Mod1 <Key>d:	    delete-next-word()\n\
	Ctrl <Key>e:	    end-of-line()\n\
	Ctrl <Key>f:	    forward-character()\n\
	Mod1 <Key>f:	    forward-word()\n\
	Ctrl <Key>g:	    activate()\n\
	Ctrl <Key>h:	    delete-previous-character()\n\
	Ctrl <Key>j:	    newline-and-indent()\n\
	Ctrl <Key>k:	    kill-line()\n\
	Mod1 <Key>l:	    word-upper(l)\n\
	Ctrl <Key>n:	    next-line()\n\
	Ctrl <Key>o:	    newline-and-backup()\n\
	Ctrl <Key>p:	    previous-line()\n\
	Ctrl <Key>t:	    text-transpose()\n\
	Mod1 <Key>u:	    word-upper(u)\n\
	Ctrl <Key>v:	    next-page()\n\
	Mod1 <Key>v:	    previous-page()\n\
	Ctrl <Key>w:	    delete-region()\n\
	Ctrl <Key>y:	    yank()\n\
	Ctrl <Key>z:	    activate()\n\
	Mod1 <Key>[:	    backward-paragraph()\n\
	Mod1 <Key>]:	    forward-paragraph()\n\
	Mod1 <Key><:	    beginning-of-file()\n\
	Mod1 <Key>>:	    end-of-file()\n\
	<Key>Delete:	    delete-previous-character()\n\
	Mod1 <Key>Delete:   delete-to-start-of-line()\n\
	Ctrl <Key>osfLeft:  page-left()\n\
	Ctrl <Key>osfRight: page-right()\n\
	Ctrl <Key>osfDown:  next-page()\n\
	Ctrl <Key>osfUp:    previous-page()\n\
	Ctrl <Key>space:    set-anchor()\n\
	<Btn1Down>:	    b1-press()\n\
	<Btn1Up>:	    b1-release()\n\
	<Btn1Motion>:	    b1-move()\n\
	<Key>Return:	    newline()\n";
static XtTranslations transTable3 = NULL;


/* for lisp listener */
static char TextTrans4[] =
       "Ctrl <Key>a:	    begin-of-line()\n\
	Ctrl <Key>b:	    backward-character()\n\
	Mod1 <Key>b:	    backward-word()\n\
	Mod1 <Key>c:	    word-upper(c)\n\
	Ctrl <Key>d:	    delete-next-character()\n\
	Mod1 <Key>d:	    delete-next-word()\n\
	Ctrl <Key>e:	    end-of-line()\n\
	Ctrl <Key>f:	    forward-character()\n\
	Mod1 <Key>f:	    forward-word()\n\
	Ctrl Meta <Key>g:   listener-clear()\n\
	Ctrl <Key>g:	    listener-g()\n\
	Ctrl <Key>h:	    delete-previous-character()\n\
	Ctrl <Key>j:	    newline-and-indent()\n\
	Ctrl <Key>k:	    kill-line()\n\
	Ctrl <Key>l:	    redraw-display()\n\
	Mod1 <Key>l:	    word-upper(l)\n\
	Ctrl <Key>n:	    next-line()\n\
        Mod1 <Key>n:        listener-meta-n()\n\
	Ctrl <Key>o:	    newline-and-backup()\n\
	Ctrl <Key>p:	    previous-line()\n\
        Mod1 <Key>p:        listener-meta-p()\n\
	Ctrl <Key>t:	    text-transpose()\n\
	Ctrl <Key>u:	    activate-keyboard(u)\n\
	Mod1 <Key>u:	    word-upper(u)\n\
	Ctrl <Key>v:	    next-page()\n\
	Mod1 <Key>v:	    previous-page()\n\
	Ctrl <Key>w:	    delete-region()\n\
	Ctrl <Key>x:	    activate-keyboard(x)\n\
	Ctrl <Key>y:	    yank()\n\
	Ctrl <Key>z:	    activate()\n\
        Ctrl <Key>?:        help-at-cursor()\n\
        Mod1 <Key>.:        help-at-cursor()\n\
	Mod1 <Key>[:	    backward-paragraph()\n\
	Mod1 <Key>]:	    forward-paragraph()\n\
	Mod1 <Key><:	    beginning-of-file()\n\
	Mod1 <Key>>:	    end-of-file()\n\
        Shift Ctrl <Key>-:  delete-to-previous-command()\n\
	<Key>Delete:	    delete-previous-character()\n\
	Mod1 <Key>Delete:   delete-to-start-of-line()\n\
	Ctrl <Key>osfLeft:  page-left()\n\
	Ctrl <Key>osfRight: page-right()\n\
	Ctrl <Key>osfDown:  next-page()\n\
	Ctrl <Key>osfUp:    previous-page()\n\
	<Key>osfDown:       listener-next-line()\n\
	<Key>osfUp:         listener-previous-line()\n\
	Ctrl <Key>space:    set-anchor()\n\
	<Btn1Down>:	    b1-press()\n\
	<Btn1Up>:	    b1-release()\n\
	<Btn1Motion>:	    b1-move()\n\
	<Key>Tab:	    listener-completion()\n\
	<Key>Return:	    listener-return()\n";
static XtTranslations transTable4 = NULL;


void add_completer_to_builtin_textfield(Widget w, int completer)
{
  /* used to make file selection dialog's file and filter text widgets act like other text field widgets */
  if (!actions_loaded) 
    {
      XtAppAddActions(main_app(ss), acts, NUM_ACTS); 
      actions_loaded = true;
    }
  if (!transTable2) 
    transTable2 = XtParseTranslationTable(TextTrans2);

  XtOverrideTranslations(w, transTable2);
  XtVaSetValues(w, XmNuserData, completer, NULL);
}



/* -------- text related widgets -------- */

static Xen mouse_enter_text_hook;
static Xen mouse_leave_text_hook;

void mouse_enter_text_callback(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  if (with_pointer_focus(ss))
    goto_window(w);

  if (Xen_hook_has_list(mouse_enter_text_hook))
    run_hook(mouse_enter_text_hook,
	     Xen_list_1(Xen_wrap_widget(w)),
	     S_mouse_enter_text_hook);
}


void mouse_leave_text_callback(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  if (Xen_hook_has_list(mouse_leave_text_hook))
    run_hook(mouse_leave_text_hook,
	     Xen_list_1(Xen_wrap_widget(w)),
	     S_mouse_leave_text_hook);
}


Widget make_textfield_widget(const char *name, Widget parent, Arg *args, int n, text_cr_t activatable, int completer)
{
  /* white background when active, emacs translations */
  Widget df;

  if (!actions_loaded) 
    {
      XtAppAddActions(main_app(ss), acts, NUM_ACTS); 
      actions_loaded = true;
    }

  XtSetArg(args[n], XmNuserData, completer); n++;
  XtSetArg(args[n], XmNhighlightThickness, 1); n++;
  XtSetArg(args[n], XmNcursorPositionVisible, false); n++;
  df = XtCreateManagedWidget(name, xmTextFieldWidgetClass, parent, args, n);

  if (activatable != ACTIVATABLE_BUT_NOT_FOCUSED)
    {
      XtAddCallback(df, XmNfocusCallback, textfield_focus_callback, NULL);
      XtAddCallback(df, XmNlosingFocusCallback, textfield_unfocus_callback, NULL);
    }
  else
    {
      XtAddCallback(df, XmNfocusCallback, textfield_no_color_focus_callback, NULL);
      XtAddCallback(df, XmNlosingFocusCallback, textfield_no_color_unfocus_callback, NULL);
    }

  XtAddEventHandler(df, EnterWindowMask, false, mouse_enter_text_callback, NULL);
  XtAddEventHandler(df, LeaveWindowMask, false, mouse_leave_text_callback, NULL);

  if ((activatable == ACTIVATABLE) ||
      (activatable == ACTIVATABLE_BUT_NOT_FOCUSED))
    {
      if (!transTable2) 
	transTable2 = XtParseTranslationTable(TextTrans2);
      XtOverrideTranslations(df, transTable2);
    }
  else
    {
      if (!transTable6) 
	transTable6 = XtParseTranslationTable(TextTrans6);
      XtOverrideTranslations(df, transTable6);
    }

  return(df);
}



Widget make_text_widget(const char *name, Widget parent, Arg *args, int n)
{
  /* white background when active, emacs translations */
  /* used only for comment widget in file data box (snd-xfile.c), but needs to be in this file to pick up actions etc */
  Widget df;
  if (!actions_loaded) 
    {
      XtAppAddActions(main_app(ss), acts, NUM_ACTS); 
      actions_loaded = true;
    }
  XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
  /* XmNblinkRate 0 turns off the cursor blink */
  XtSetArg(args[n], XmNcursorPositionVisible, false); n++;
  XtSetArg(args[n], XmNhighlightThickness, 1); n++;
  df = XmCreateScrolledText(parent, (char *)name, args, n);
  XtManageChild(df);
  XtAddCallback(df, XmNfocusCallback, textfield_focus_callback, NULL);
  XtAddCallback(df, XmNlosingFocusCallback, textfield_unfocus_callback, NULL);
  XtAddEventHandler(df, EnterWindowMask, false, mouse_enter_text_callback, NULL);
  XtAddEventHandler(df, LeaveWindowMask, false, mouse_leave_text_callback, NULL);
  if (!transTable3) 
    transTable3 = XtParseTranslationTable(TextTrans3);
  XtOverrideTranslations(df, transTable3);
  return(df);
}


/* ---------------- listener widget ---------------- */

static Widget lisp_window = NULL;

void listener_append(const char *msg)
{
  if (listener_text)
    XmTextInsert(listener_text, XmTextGetLastPosition(listener_text), (char *)msg);
}
 

void listener_append_and_prompt(const char *msg)
{
  if (listener_text)
    {
      XmTextPosition cmd_eot;
      if (msg)
	XmTextInsert(listener_text, XmTextGetLastPosition(listener_text), (char *)msg);
      cmd_eot = XmTextGetLastPosition(listener_text);
      XmTextInsert(listener_text, cmd_eot, listener_prompt_with_cr());
      cmd_eot = XmTextGetLastPosition(listener_text);
      XmTextShowPosition(listener_text, cmd_eot - 1);
    }
}


static void listener_return_callback(Widget w, XtPointer context, XtPointer info)
{
  listener_return(w, find_prompt(w, XmTextGetInsertionPosition(w)));
  /* prompt loc (last prompt pos) used only by read hook */
}


#if (!HAVE_FORTH) && (!HAVE_RUBY)

static int flashes = 0;
static int paren_pos = -1;
#define FLASH_TIME 150

static void flash_unbalanced_paren(XtPointer context, XtIntervalId *id)
{
  flashes--;
  XmTextSetHighlight(listener_text, paren_pos, paren_pos + 1, (flashes & 1) ? XmHIGHLIGHT_NORMAL : XmHIGHLIGHT_SELECTED);
  if (flashes > 0)
    XtAppAddTimeOut(main_app(ss),
		    (unsigned long)FLASH_TIME,
		    (XtTimerCallbackProc)flash_unbalanced_paren,
		    NULL);
  else 
    {
      XmTextSetHighlight(listener_text, paren_pos, paren_pos + 1, XmHIGHLIGHT_NORMAL);
      paren_pos = -1;
    }
}

static bool highlight_unbalanced_paren(void)
{
  /* if cursor is positioned at close paren, try to find reason for unbalanced expr and highlight it */
  int pos;
  bool success = true;
  pos = XmTextGetInsertionPosition(listener_text);
  if (pos > 2)
    {
      char *str;
      str = XmTextGetString(listener_text);
      if ((str[pos - 1] == ')') &&
	  ((str[pos - 2] != '\\') || (str[pos - 3] != '#')))
	{
	  int parens;
	  parens = find_matching_paren(str, 2, pos - 1, &paren_pos);
	  if (parens == 0)
	    {
	      XmTextSetHighlight(listener_text, paren_pos, paren_pos + 1, XmHIGHLIGHT_SELECTED);
	      flashes = 4;
	      XtAppAddTimeOut(main_app(ss),
			      (unsigned long)FLASH_TIME,
			      (XtTimerCallbackProc)flash_unbalanced_paren,
			      NULL);
	    }
	  else success = false;
	}
      if (str) XtFree(str);
    }
  return(success);
}
#endif


static int last_highlight_position = -1;

static void listener_motion_callback(Widget w, XtPointer context, XtPointer info)
{
  XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)info;
  int pos;

  cbs->doit = true; 
  if (dont_check_motion) return;
  if (last_highlight_position != -1)
    {
      XmTextSetHighlight(w, last_highlight_position, last_highlight_position + 1, XmHIGHLIGHT_NORMAL);
      last_highlight_position = -1;
    }

  pos = cbs->newInsert - 1;
  if (pos > 0)
    {
      char *str = NULL;
      str = XmTextGetString(w);
      if ((str[pos] == ')') && 
	  ((pos <= 1) || (str[pos - 1] != '\\') || (str[pos - 2] != '#')))
	{
	  int parens;
	  parens = find_matching_paren(str, 1, pos, &last_highlight_position);
	  if (parens == 0)
	    XmTextSetHighlight(w, last_highlight_position, last_highlight_position + 1, XmHIGHLIGHT_SECONDARY_SELECTED);
	}
      if (str) XtFree(str);
    }
}


static void listener_modify_callback(Widget w, XtPointer context, XtPointer info)
{
  XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)info;

  /* pure motion stuff (arrow keys) does not trigger this callback */

  if ((completions_pane) &&
      (XtIsManaged(completions_pane)))
    XtUnmanageChild(completions_pane);

  if (((cbs->text)->length > 0) || (dont_check_motion))
    cbs->doit = true;
  else
    { 
      char *str = NULL;
      int len;
      str = XmTextGetString(w);
      len = XmTextGetLastPosition(w);
      if (within_prompt(str, cbs->startPos, len))
	cbs->doit = false;
      else cbs->doit = true;
      if (str) XtFree(str);
    }
}


static Xen mouse_enter_listener_hook;
static Xen mouse_leave_listener_hook;

static void listener_focus_callback(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  if (with_pointer_focus(ss))
    goto_window(listener_text);

  if (Xen_hook_has_list(mouse_enter_listener_hook))
    run_hook(mouse_enter_listener_hook,
	     Xen_list_1(Xen_wrap_widget(listener_text)), /* not w */
	     S_mouse_enter_listener_hook);
}


static void listener_unfocus_callback(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  if (Xen_hook_has_list(mouse_leave_listener_hook))
    run_hook(mouse_leave_listener_hook,
	     Xen_list_1(Xen_wrap_widget(listener_text)), /* not w */
	     S_mouse_leave_listener_hook);
}


/* ---------------- popup callbacks ---------------- */

static Widget listener_popup = NULL;

static void listener_help_callback(Widget w, XtPointer context, XtPointer info)
{
  char *txt;
  txt = XmTextGetSelection(listener_text);
  if (txt)
    {
      char *trim_txt;
      trim_txt = trim(txt);
      if (trim_txt)
	{
	  snd_help(trim_txt, Xen_string_to_C_string(g_snd_help(C_string_to_Xen_string(trim_txt), 0)), WITH_WORD_WRAP);
	  free(trim_txt);
	}
      XtFree(txt);
    }
  else text_at_cursor(listener_text);
}

static void listener_save_callback(Widget w, XtPointer context, XtPointer info)
{
  FILE *fp = NULL;
  fp = FOPEN("listener.txt", "w");
  if (fp) 
    {
      save_listener_text(fp);
      snd_fclose(fp, "listener.txt");
    }
}


static void listener_clear_callback(Widget w, XtPointer context, XtPointer info)
{
  clear_listener();
}


#if HAVE_SCHEME
static void listener_stacktrace_callback(Widget w, XtPointer context, XtPointer info)
{
  s7_pointer str;
  str = s7_eval_c_string(s7, "(stacktrace)");
  if (s7_string_length(str) == 0)
    str = s7_eval_c_string(s7, "(object->string (owlet))");
  snd_display_result(s7_string(str), NULL);
}
#endif


static void listener_stop_callback(Widget w, XtPointer context, XtPointer info)
{
  control_g(any_selected_sound());
}


#if HAVE_SCHEME
static Widget stacktrace_popup_menu = NULL;
#endif

static void listener_popup_callback(Widget w, XtPointer context, XtPointer info)
{
  XmPopupHandlerCallbackStruct *cb = (XmPopupHandlerCallbackStruct *)info;
  XEvent *e;
  e = cb->event;
  if (e->type == ButtonPress)
    {
#if HAVE_SCHEME
      if (stacktrace_popup_menu)
	set_menu_label(stacktrace_popup_menu, (s7_is_null(s7, s7_curlet(s7))) ? "Error info" : "Stacktrace");
#endif
      cb->menuToPost = listener_popup;
    }
}


static void make_listener_widget(int height)
{
  if (!listener_text)
    {
      Arg args[32];
      Widget wv, wh, w;
      int n;

      if (!actions_loaded) {XtAppAddActions(main_app(ss), acts, NUM_ACTS); actions_loaded = true;}

      n = attach_all_sides(args, 0);
      XtSetArg(args[n], XmNheight, height); n++;
      XtSetArg(args[n], XmNpaneMaximum, LOTSA_PIXELS); n++; /* Xm/Paned initializes each pane max to 1000 apparently! */

      if ((sound_style(ss) == SOUNDS_IN_NOTEBOOK) || (sound_style(ss) == SOUNDS_HORIZONTAL))
	listener_pane = XtCreateManagedWidget("frm", xmFormWidgetClass, sound_pane_box(ss), args, n);
      else listener_pane = XtCreateManagedWidget("frm", xmFormWidgetClass, sound_pane(ss), args, n);
      /* this widget is not redundant at least in Metroworks Motif */

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->listener_color); n++;
      XtSetArg(args[n], XmNforeground, ss->listener_text_color); n++;
      if (ss->listener_fontlist) {XtSetArg(args[n], XM_FONT_RESOURCE, ss->listener_fontlist); n++;}
      n = attach_all_sides(args, n);
      XtSetArg(args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
      XtSetArg(args[n], XmNskipAdjust, true); n++;
      XtSetArg(args[n], XmNvalue, listener_prompt(ss)); n++;
      XtSetArg(args[n], XmNpendingDelete, false); n++; /* don't cut selection upon paste */
      XtSetArg(args[n], XmNpositionIndex, XmLAST_POSITION); n++;
      XtSetArg(args[n], XmNhighlightThickness, 1); n++;
      listener_text = XmCreateScrolledText(listener_pane, (char *)"lisp-listener", args, n);
      ss->listener_pane = listener_text;

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNpopupEnabled, XmPOPUP_AUTOMATIC); n++;
      listener_popup = XmCreatePopupMenu(listener_text, (char *)"listener-popup", args, n);

      w = XtCreateManagedWidget(I_STOP, xmPushButtonWidgetClass, listener_popup, args, n);
      XtAddCallback(w, XmNactivateCallback, listener_stop_callback, NULL);

#if HAVE_SCHEME
      w = XtCreateManagedWidget("Stacktrace", xmPushButtonWidgetClass, listener_popup, args, n);
      XtAddCallback(w, XmNactivateCallback, listener_stacktrace_callback, NULL);
      stacktrace_popup_menu = w;
#endif

      w = XtCreateManagedWidget(I_HELP, xmPushButtonWidgetClass, listener_popup, args, n);
      XtAddCallback(w, XmNactivateCallback, listener_help_callback, NULL);

      w = XtCreateManagedWidget("Clear", xmPushButtonWidgetClass, listener_popup, args, n);
      XtAddCallback(w, XmNactivateCallback, listener_clear_callback, NULL);

      w = XtCreateManagedWidget("Save", xmPushButtonWidgetClass, listener_popup, args, n);
      XtAddCallback(w, XmNactivateCallback, listener_save_callback, NULL);

      XtVaSetValues(main_shell(ss), XmNallowShellResize, false, NULL);

      XtManageChild(listener_text);
      XmTextSetCursorPosition(listener_text, ss->listener_prompt_length);
      if (!transTable4) 
	transTable4 = XtParseTranslationTable(TextTrans4);
      XtOverrideTranslations(listener_text, transTable4);
      XtAddCallback(listener_text, XmNactivateCallback, listener_return_callback, NULL);
      XtAddCallback(listener_text, XmNmodifyVerifyCallback, listener_modify_callback, NULL);
      XtAddCallback(listener_text, XmNmotionVerifyCallback, listener_motion_callback, NULL);

      lisp_window = XtParent(listener_text);
      XtAddEventHandler(lisp_window, EnterWindowMask, false, listener_focus_callback, NULL);
      XtAddEventHandler(lisp_window, LeaveWindowMask, false, listener_unfocus_callback, NULL);

      XtAddCallback(listener_text, XmNpopupHandlerCallback, listener_popup_callback, NULL);

      XmChangeColor(lisp_window, ss->basic_color);
      XtVaGetValues(lisp_window, XmNverticalScrollBar, &wv, 
		                 XmNhorizontalScrollBar, &wh, 
		                 NULL);
      XmChangeColor(wv, ss->basic_color);
      XmChangeColor(wh, ss->basic_color);
      map_over_children(sound_pane(ss), color_sashes);

      if (auto_resize(ss))
	XtVaSetValues(main_shell(ss), XmNallowShellResize, true, NULL);
    }
}


void goto_listener(void) 
{
  goto_window(listener_text);
  XmTextSetCursorPosition(listener_text, XmTextGetLastPosition(listener_text) + 1);
  XmTextSetInsertionPosition(listener_text, XmTextGetLastPosition(listener_text) + 1);
}


void color_listener(Pixel pix)
{
  ss->listener_color = pix;
#if HAVE_SCHEME
  s7_symbol_set_value(s7, ss->listener_color_symbol, Xen_wrap_pixel(pix));
#endif
  if (listener_text)
    XmChangeColor(listener_text, pix);
}


void color_listener_text(Pixel pix)
{
  ss->listener_text_color = pix;
#if HAVE_SCHEME
  s7_symbol_set_value(s7, ss->listener_text_color_symbol, Xen_wrap_pixel(pix));
#endif
  if (listener_text)
    XtVaSetValues(listener_text, XmNforeground, pix, NULL);
}


void handle_listener(bool open)
{
  if (open)
    {
      if (!listener_text)
	make_listener_widget(100);
      else 
	{
	  XtManageChild(listener_pane);
	  if (!(listener_is_visible()))
	    {
	      XtUnmanageChild(listener_pane);
	      XtVaSetValues(listener_pane, XmNpaneMinimum, 100, XmNpaneMaximum, LOTSA_PIXELS, NULL);
	      XtManageChild(listener_pane);
	      XtVaSetValues(listener_pane, XmNpaneMinimum, 1, NULL);
	    }
	}
    }
  else XtUnmanageChild(listener_pane);
}


bool listener_exists(void)
{
  return((bool)listener_text);
}


int listener_height(void)
{
  if ((listener_text) && (XtIsManaged(listener_pane)))
    return(widget_height(listener_text)); 
  else return(0);
}


int listener_width(void)
{
  if ((listener_text) && (XtIsManaged(listener_pane)))
    return(widget_width(listener_text)); 
  else return(0);
}


#if OVERRIDE_TOGGLE
static char ToggleTrans2[] =
       "c<Btn1Down>:   ArmAndActivate()\n";
static XtTranslations toggleTable2 = NULL;

static void override_toggle_translation(Widget w)
{
  if (!toggleTable2) toggleTable2 = XtParseTranslationTable(ToggleTrans2);
  XtOverrideTranslations(w, toggleTable2);
}
#endif


Widget make_togglebutton_widget(const char *name, Widget parent, Arg *args, int n)
{
  Widget w;
  w = XtCreateManagedWidget(name, xmToggleButtonWidgetClass, parent, args, n);
#if OVERRIDE_TOGGLE
  override_toggle_translation(w);
#endif
  return(w);
}


Widget make_pushbutton_widget(const char *name, Widget parent, Arg *args, int n)
{
  Widget w;
  w = XtCreateManagedWidget(name, xmPushButtonWidgetClass, parent, args, n);
#if OVERRIDE_TOGGLE
  override_toggle_translation(w); /* ??? activate here (rather than armandactivate) fails? */
#endif
  return(w);
}


static Xen g_listener_selection(void)
{
  #define H_listener_selection "(" S_listener_selection "): currently selected text in listener or " PROC_FALSE
  Xen res = Xen_false;
  if (listener_text)
    {
      char *txt;
      txt = XmTextGetSelection(listener_text);
      if (txt) 
	{
	  res = C_string_to_Xen_string(txt);
	  XtFree(txt);
	}
    }
  return(res);
}


static Xen g_reset_listener_cursor(void)
{
  #define H_reset_listener_cursor "(" S_reset_listener_cursor "): reset listener cursor to the default pointer"
  if (listener_text)
    XUndefineCursor(XtDisplay(listener_text), 
		    XtWindow(listener_text)); 
  return(Xen_false);
}


void clear_listener(void)
{
  if (listener_text)  /* this can be called even when there is no listener */
    {
      dont_check_motion = true;
      XmTextSetSelection(listener_text, 0, XmTextGetCursorPosition(listener_text), CurrentTime);
      XmTextRemove(listener_text);
      XmTextInsert(listener_text, 0, listener_prompt(ss));
      XmTextShowPosition(listener_text, ss->listener_prompt_length);
      dont_check_motion = false;
    }
}


void set_listener_text_font(void)
{
  if (listener_text)
    XtVaSetValues(listener_text, XM_FONT_RESOURCE, ss->listener_fontlist, NULL);
}


static Xen g_goto_listener_end(void)
{
  #define H_goto_listener_end "(" S_goto_listener_end "): move cursor and scroll to bottom of listener pane"
  if (listener_text)
    {
      XmTextPosition eot;
      eot = XmTextGetLastPosition(listener_text);
      XmTextShowPosition(listener_text, eot);
      XmTextSetInsertionPosition(listener_text, eot);
      return(C_int_to_Xen_integer(eot));
    }
  return(Xen_false);
}



#include <X11/XKBlib.h>

enum {W_top, W_form, W_main_window, W_edhist, W_wf_buttons, W_f, W_w, W_left_scrollers, W_zy, W_sy,
      W_bottom_scrollers, W_sx, W_zx, W_graph, W_gzy, W_gsy,
      CP_NUM_WIDGETS
};


#if ((XmVERSION >= 2) && (XmREVISION >= 3))
  #define DEFAULT_EDIT_HISTORY_WIDTH 2
#else
  #define DEFAULT_EDIT_HISTORY_WIDTH 1
#endif


Widget channel_main_pane(chan_info *cp)
{
  if (cp) return(cp->widgets[W_form]);
  return(NULL);
}


Widget channel_graph(chan_info *cp)      {return(cp->widgets[W_graph]);}
static Widget channel_sx(chan_info *cp)  {return(cp->widgets[W_sx]);}
static Widget channel_sy(chan_info *cp)  {return(cp->widgets[W_sy]);}
static Widget channel_zx(chan_info *cp)  {return(cp->widgets[W_zx]);}
static Widget channel_zy(chan_info *cp)  {return(cp->widgets[W_zy]);}
static Widget channel_gsy(chan_info *cp) {return(cp->widgets[W_gsy]);}
static Widget channel_gzy(chan_info *cp) {return(cp->widgets[W_gzy]);}
Widget channel_w(chan_info *cp)          {return(cp->widgets[W_w]);}
Widget channel_f(chan_info *cp)          {return(cp->widgets[W_f]);}


bool channel_graph_is_visible(chan_info *cp)
{
  return((cp) &&
	 (cp->widgets) &&
	 (channel_graph(cp)) &&
	 (XtIsManaged(channel_graph(cp))) &&
	 (cp->sound) &&
	 /* here we may have a sound wrapper for variable display in which case the sound widgets are not implemented */
	 (((cp->sound->inuse == SOUND_WRAPPER) || (cp->sound->inuse == SOUND_REGION)) ||
	  ((cp->sound->inuse == SOUND_NORMAL) &&
	   /* other choice: SOUND_IDLE -> no display */
	   (w_snd_pane(cp->sound)) &&
	   (XtIsManaged(w_snd_pane(cp->sound))))));
}


#define EDIT_HISTORY_LIST(Cp) (Cp)->widgets[W_edhist]


static mus_float_t sqr(mus_float_t a) {return(a * a);}

static mus_float_t cube(mus_float_t a) {return(a * a * a);}


static mus_float_t get_scrollbar(Widget w, int val, int scrollbar_max)
{
  int size;
  if (val == 0) return(0.0);
  XtVaGetValues(w, XmNsliderSize, &size, NULL);
  return((mus_float_t)val / (mus_float_t)(scrollbar_max - size));
}


static void sy_changed(int value, chan_info *cp)
{
  axis_info *ap;
  mus_float_t low;
  ap = cp->axis;
  low = get_scrollbar(channel_sy(cp), value, SCROLLBAR_MAX);
  ap->sy = (1.0 - ap->zy) * low;
  apply_y_axis_change(cp);
}


static void sx_changed(int value, chan_info *cp)
{
  /* treat as centered with non-slider trough as defining current bounds */
  axis_info *ap;
  double low;
  ap = cp->axis;
  low = get_scrollbar(channel_sx(cp), value, SCROLLBAR_MAX);
  ap->sx = low * (1.0 - ap->zx);
  apply_x_axis_change(cp);
}


static void zy_changed(int value, chan_info *cp)
{ 
  axis_info *ap;
  mus_float_t old_zy;
  ap = cp->axis;
  if (value < 1) value = 1;
  old_zy = ap->zy;
  ap->zy = sqr(get_scrollbar(channel_zy(cp), value, SCROLLBAR_MAX));
  if (ap->zy < 1e-5) ap->zy = 1e-5;   /* if this goes to 0, X can hang */
  ap->sy += (.5 * (old_zy - ap->zy)); /* try to keep wave centered */
  if (ap->sy < 0) ap->sy = 0;
  apply_y_axis_change(cp);
  resize_sy(cp);
}


#define X_RANGE_CHANGEOVER 20.0

static void zx_changed(int value, chan_info *cp)
{ /* scrollbar change */
  axis_info *ap;
  static int old_zx_value = -1;
  #define ZX_MIN 20

  if (value < ZX_MIN) value = ZX_MIN; /* less than this causes underflow in snd-axis describe_ticks */
                                      /* snd-gchn uses .01 -- its equivalent here would be 100 */
                                      /* perhaps the definition should be ((int)(0.002 * MAX_SCROLLBAR)) */
  if (old_zx_value == value) return;  /* try to keep click on slider from moving the window! */
  old_zx_value = value;

  ap = cp->axis;
  if (ap->xmax == 0.0) return;
  if (ap->xmax <= ap->xmin) 
    {
      ap->xmax = ap->xmin + .001;
      ap->x_ambit = .001;
    }
  if (ap->x_ambit < X_RANGE_CHANGEOVER)
    ap->zx = sqr(get_scrollbar(channel_zx(cp), value, SCROLLBAR_MAX));
  else ap->zx = cube(get_scrollbar(channel_zx(cp), value, SCROLLBAR_MAX));
  /* if cursor visible, focus on that, else selection, else mark, else left side */
  focus_x_axis_change(cp, zoom_focus_style(ss));
  resize_sx(cp);
}


static void set_scrollbar(Widget w, mus_float_t position, mus_float_t range, int scrollbar_max) /* position and range 0 to 1.0 */
{
  int size, val;
  size = (int)(scrollbar_max * range);
  if (size > scrollbar_max) 
    size = scrollbar_max; /* this can't happen!?! */
  if (size < 1) size = 1;
  val = (int)(scrollbar_max * position);
  if ((val + size) > scrollbar_max) val = scrollbar_max - size;
  if (val < 0) val = 0;
  XtVaSetValues(w,
		XmNsliderSize, size,
		XmNvalue, val,
		NULL);
}


static void change_gzy_1(mus_float_t val, chan_info *cp)
{
  mus_float_t chan_frac, new_gsy, new_size;
  cp->gzy = val;
  chan_frac = 1.0 / ((mus_float_t)(((snd_info *)(cp->sound))->nchans));
  new_size = chan_frac + ((1.0 - chan_frac) * cp->gzy);
  if ((cp->gsy + new_size) > 1.0) 
    new_gsy = 1.0 - new_size; 
  else new_gsy = cp->gsy;
  if (new_gsy < 0.0) new_gsy = 0.0;
  set_scrollbar(channel_gsy(cp), new_gsy, new_size, SCROLLBAR_MAX);
}


static void gzy_changed(int value, chan_info *cp)
{
  change_gzy_1(get_scrollbar(channel_gzy(cp), value, SCROLLBAR_MAX), cp);
  for_each_sound_chan(cp->sound, update_graph_or_warn);
}


void change_gzy(mus_float_t val, chan_info *cp)
{
  change_gzy_1(val, cp);
  set_scrollbar(channel_gzy(cp), val, 1.0 / (mus_float_t)(cp->sound->nchans), SCROLLBAR_MAX);
}


static void gsy_changed(int value, chan_info *cp)
{
  mus_float_t low;
  low = get_scrollbar(channel_gsy(cp), value, SCROLLBAR_MAX);
  cp->gsy = (1.0 - cp->gzy) * low;
  for_each_sound_chan(cp->sound, update_graph_or_warn);
}


mus_float_t gsy_value(chan_info *cp)
{
  Widget wcp;
  int ival;
  wcp = channel_gsy(cp);
  XtVaGetValues(wcp, XmNvalue, &ival, NULL);
  return((mus_float_t)ival / (mus_float_t)(SCROLLBAR_MAX));
}


mus_float_t gsy_size(chan_info *cp)
{
  Widget wcp;
  int ival;
  wcp = channel_gsy(cp);
  XtVaGetValues(wcp, XmNsliderSize, &ival, NULL);
  return((mus_float_t)ival / (mus_float_t)(SCROLLBAR_MAX));
}


static void set_zx_scrollbar(chan_info *cp, axis_info *ap)
{
  if (ap->x_ambit < X_RANGE_CHANGEOVER)
    set_scrollbar(channel_zx(cp), sqrt(ap->zx), .1, SCROLLBAR_MAX);  /* assume size is 10% of scrollbar length */
  else set_scrollbar(channel_zx(cp), pow(ap->zx, .333), .1, SCROLLBAR_MAX);
}


void set_z_scrollbars(chan_info *cp, axis_info *ap)
{
  set_zx_scrollbar(cp, ap);
  set_scrollbar(channel_zy(cp), sqrt(ap->zy), .1, SCROLLBAR_MAX);
}


void initialize_scrollbars(chan_info *cp)
{
  axis_info *ap;
  snd_info *sp;

  cp->gzy = 1.0;
  cp->gsy = 1.0;

  ap = cp->axis;
  sp = cp->sound;

  set_scrollbar(channel_sx(cp), ap->sx, ap->zx, SCROLLBAR_MAX);
  set_scrollbar(channel_sy(cp), ap->sy, ap->zy, SCROLLBAR_MAX);
  set_z_scrollbars(cp, ap);

  if ((sp->nchans > 1) && 
      (cp->chan == 0) && 
      (channel_gsy(cp)))
    {
      set_scrollbar(channel_gsy(cp), 1.0, 1.0, SCROLLBAR_MAX);
      set_scrollbar(channel_gzy(cp), 1.0, 1.0 / (mus_float_t)(sp->nchans), SCROLLBAR_MAX);
    }
}


void resize_sy(chan_info *cp)
{
  /* something changed the y axis view, so the scale scroller needs to reflect that change (in size and position) */
  axis_info *ap;
  ap = cp->axis;
  if (ap->y_ambit != 0.0)
    set_scrollbar(channel_sy(cp),
		  (ap->y0 - ap->ymin) / ap->y_ambit,
		  (ap->y1 - ap->y0) / ap->y_ambit,
		  SCROLLBAR_MAX);
}


void resize_sy_and_zy(chan_info *cp)
{
  resize_sy(cp);
  set_scrollbar(channel_zy(cp), sqrt(cp->axis->zy), .1, SCROLLBAR_MAX);  
}


void resize_sx(chan_info *cp)
{
  axis_info *ap;
  ap = cp->axis;
  if (ap->x_ambit != 0.0)
    set_scrollbar(channel_sx(cp),
		  (ap->x0 - ap->xmin) / ap->x_ambit,
		  (ap->x1 - ap->x0) / ap->x_ambit,
		  SCROLLBAR_MAX);
}


void resize_sx_and_zx(chan_info *cp)
{
  resize_sx(cp);
  /* change zx position (not its size) */
  set_zx_scrollbar(cp, cp->axis);
}


void channel_open_pane(chan_info *cp)
{
  XtManageChild(channel_main_pane(cp));
}


void channel_unlock_pane(chan_info *cp)
{
  XtVaSetValues(channel_main_pane(cp),
		XmNpaneMinimum, 5,
		XmNpaneMaximum, LOTSA_PIXELS,
		NULL);
}


static void channel_lock_pane(chan_info *cp, int val)
{
  if (val < 6) val = 6;
  XtUnmanageChild(channel_main_pane(cp));
  XtVaSetValues(channel_main_pane(cp),
		XmNpaneMinimum, val - 5,
		XmNpaneMaximum, val + 5,
		NULL);
}


static void sy_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp = (chan_info *)(context);
  if (cp->active == CHANNEL_HAS_AXES)
    sy_changed(((XmScrollBarCallbackStruct *)info)->value, cp);
}


static void sy_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp = (chan_info *)(context);
  if (cp->active == CHANNEL_HAS_AXES)
    sy_changed(((XmScrollBarCallbackStruct *)info)->value, cp);
}


static void sx_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp = (chan_info *)(context);
  if (cp->active == CHANNEL_HAS_AXES)
    sx_changed(((XmScrollBarCallbackStruct *)info)->value, cp);
}


static void sx_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp = (chan_info *)(context);
  if (cp->active == CHANNEL_HAS_AXES)
    sx_changed(((XmScrollBarCallbackStruct *)info)->value, cp);
}


static void sx_increment_callback(Widget w, XtPointer context, XtPointer info) 
{
  /* problem here is that in large files these increments, if determined via scrollbar values, are huge */
  /* so, move ahead one-tenth window on each tick */
  chan_info *cp = (chan_info *)(context);
  if (cp->active == CHANNEL_HAS_AXES)
    sx_incremented(cp, 0.1);
}


static void sx_decrement_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp = (chan_info *)(context);
  if (cp->active == CHANNEL_HAS_AXES)
    sx_incremented(cp, -0.1);
}


static void zy_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp = (chan_info *)(context);
  if (cp->active == CHANNEL_HAS_AXES)
    zy_changed(((XmScrollBarCallbackStruct *)info)->value, cp);
}


static void zy_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp = (chan_info *)(context);
  if (cp->active == CHANNEL_HAS_AXES)
    zy_changed(((XmScrollBarCallbackStruct *)info)->value, cp);
}


static void zx_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp = (chan_info *)(context);
  if (cp->active == CHANNEL_HAS_AXES)
    zx_changed(((XmScrollBarCallbackStruct *)info)->value, cp);
}


/* can't use value changed callback in scrollbars because they are called upon mouse release
 *   even when nothing changed, and the value passed appears to be the right edge of the
 *   slider -- this is too unpredictable, and the result is the window moves when the user
 *   did not want it to.
 */


static void gzy_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp = (chan_info *)(context);
  if (cp->active == CHANNEL_HAS_AXES)
    gzy_changed(((XmScrollBarCallbackStruct *)info)->value, cp);
}


static void gsy_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp = (chan_info *)(context);
  if (cp->active == CHANNEL_HAS_AXES)
    gsy_changed(((XmScrollBarCallbackStruct *)info)->value, cp);
}


static void gsy_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  chan_info *cp = (chan_info *)(context);
  if (cp->active == CHANNEL_HAS_AXES)
    gsy_changed(((XmScrollBarCallbackStruct *)info)->value, cp);
}

/* anything special for increment?  XmNincrementCallback sx_increment_callback */


static void f_toggle_callback(Widget w, XtPointer context, XtPointer info)
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  XButtonEvent *ev;
  ev = (XButtonEvent *)(cb->event);
  f_button_callback((chan_info *)context, cb->set, (ev->state & ControlMask));
}


static void w_toggle_callback(Widget w, XtPointer context, XtPointer info)
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  XButtonEvent *ev;
  ev = (XButtonEvent *)(cb->event);
  w_button_callback((chan_info *)context, cb->set, (ev->state & ControlMask));
}


static void channel_expose_callback(Widget w, XtPointer context, XtPointer info)
{
  static oclock_t last_expose_event_time = 0;
  static chan_info *last_cp = NULL;
  chan_info *cp = (chan_info *)context;
  XmDrawingAreaCallbackStruct *cb = (XmDrawingAreaCallbackStruct *)info;
  XExposeEvent *ev;
  oclock_t curtime;

  if ((!cp) || (cp->active < CHANNEL_HAS_AXES) || (!cp->sound)) return;

  ev = (XExposeEvent *)(cb->event);

  /* if multiple channels/sounds displayed, each gets an expose event, but the earlier ones
   *   have count>0, and the last can get more than 1, causing our notion of last_cp to
   *   be useless, and we'll drop the earlier ones anyway, so if cp != last_cp, expose
   *   last_cp if last_count>0 or times equal (not sure which is safest).
   */

  curtime = XtLastTimestampProcessed(main_display(ss));

  if ((ev->count == 0) ||
      (last_expose_event_time != curtime) ||
      (cp != last_cp))
    {
      snd_info *sp;
      sp = cp->sound;
      if (sp->channel_style != CHANNELS_SEPARATE)
	{
	  if ((cp->chan == 0) && (ev->width > 10) && (ev->height > 10))
	    for_each_sound_chan(sp, update_graph_or_warn);
	}
      else update_graph_or_warn(cp);
    }

  last_cp = cp;
  last_expose_event_time = curtime;
}


static void channel_resize_callback(Widget w, XtPointer context, XtPointer info)
{
  channel_resize((chan_info *)context);
}


static Xen mouse_enter_graph_hook;
static Xen mouse_leave_graph_hook;

static void graph_mouse_enter(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  intptr_t data;
  XEnterWindowEvent *ev = (XEnterWindowEvent *)event;

  if (with_pointer_focus(ss))
    goto_window(w);

  XtVaGetValues(w, XmNuserData, &data, NULL);

  if (Xen_hook_has_list(mouse_enter_graph_hook))
    run_hook(mouse_enter_graph_hook,
	     Xen_list_2(C_int_to_Xen_sound(unpack_sound(data)),
			C_int_to_Xen_integer(unpack_channel(data))),
	     S_mouse_enter_graph_hook);

  check_cursor_shape((chan_info *)context, ev->x, ev->y);
}


static void graph_mouse_leave(Widget w, XtPointer context, XEvent *event, Boolean *flag)
{
  intptr_t data;
  XLeaveWindowEvent *ev = (XLeaveWindowEvent *)event;

  XtVaGetValues(w, XmNuserData, &data, NULL);
  if (Xen_hook_has_list(mouse_leave_graph_hook))
    run_hook(mouse_leave_graph_hook,
	     Xen_list_2(C_int_to_Xen_sound(unpack_sound(data)),
			C_int_to_Xen_integer(unpack_channel(data))),
	     S_mouse_leave_graph_hook);

  /*
  XUndefineCursor(XtDisplay(w), XtWindow(w));
  */
  check_cursor_shape((chan_info *)context, ev->x, ev->y);
}


static void graph_button_press(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  XButtonEvent *ev = (XButtonEvent *)event;
  graph_button_press_callback((chan_info *)context, (void *)ev, ev->x, ev->y, ev->state, ev->button, ev->time);
}


static void graph_button_release(Widget w, XtPointer context, XEvent *event, Boolean *cont) /* cont = "continue to dispatch" */
{
  XButtonEvent *ev = (XButtonEvent *)event;
  graph_button_release_callback((chan_info *)context, ev->x, ev->y, ev->state, ev->button);
}

#if 0
static void graph_button_motion(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{ /* mouse drag */
  XMotionEvent *ev = (XMotionEvent *)event;
  graph_button_motion_callback((chan_info *)context, ev->x, ev->y, ev->time);
}
#endif


static void graph_mouse_motion(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{ /* mouse movement */
  XMotionEvent *ev = (XMotionEvent *)event;
  if ((ev->state & Button1Mask) == 0)
    check_cursor_shape((chan_info *)context, ev->x, ev->y);
  else graph_button_motion_callback((chan_info *)context, ev->x, ev->y, ev->time);
}


static int no_padding(Arg *args, int n)
{
  XtSetArg(args[n], XmNmarginHeight, 0); n++;
  XtSetArg(args[n], XmNmarginWidth, 0); n++;
  XtSetArg(args[n], XmNmarginTop, 0); n++;
  XtSetArg(args[n], XmNmarginBottom, 0); n++;
  XtSetArg(args[n], XmNmarginLeft, 0); n++;
  XtSetArg(args[n], XmNmarginRight, 0); n++;
  return(n);
}


static void hide_gz_scrollbars(snd_info *sp)
{
  Widget w;
  w = channel_gsy(sp->chans[0]);
  if ((w) && (XtIsManaged(w))) XtUnmanageChild(w);
  w = channel_gzy(sp->chans[0]);
  if ((w) && (XtIsManaged(w))) XtUnmanageChild(w);
}


static void show_gz_scrollbars(snd_info *sp)
{
  Widget w;
  w = channel_gsy(sp->chans[0]);
  if ((w) && (!XtIsManaged(w))) XtManageChild(w);
  w = channel_gzy(sp->chans[0]);
  if ((w) && (!XtIsManaged(w))) XtManageChild(w);
}


/* edit history support */

static void history_select_callback(Widget w, XtPointer context, XtPointer info) 
{
  /* undo/redo to reach selected position */
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)info;
  edit_history_select((chan_info *)context, cbs->item_position - 1);
}


#if WITH_RELATIVE_PANES

/* using undocumented callback here, as in snd-xsnd.c */
static void remake_edit_history(Widget lst, chan_info *cp, int from_graph)
{
  snd_info *sp;
  int i, eds;
  XmString *edits;

  if (cp->squelch_update) return;
  XmListDeleteAllItems(lst);
  sp = cp->sound;

  if (sp->channel_style != CHANNELS_SEPARATE)
    {
      chan_info *ncp;
      uint32_t k;
      int all_eds = 0, ed, filelen;
      char *title;

      for (k = 0; k < sp->nchans; k++)
	{
	  ncp = sp->chans[k];
	  eds = ncp->edit_ctr;
	  while ((eds < (ncp->edit_size - 1)) && (ncp->edits[eds + 1])) eds++;
	  all_eds += eds;
	}
      all_eds += 3 * sp->nchans;
      edits = (XmString *)calloc(all_eds, sizeof(XmString));
      filelen = 16 + strlen(sp->filename);
      title = (char *)calloc(filelen, sizeof(char));
      for (k = 0, ed = 0; k < sp->nchans; k++)
	{
	  ncp = sp->chans[k];
	  ncp->edhist_base = ed;
	  snprintf(title, filelen, "chan %d: %s", k + 1, sp->filename);
	  edits[ed++] = XmStringCreateLocalized(title);
	  eds = ncp->edit_ctr;
	  while ((eds < (ncp->edit_size - 1)) && (ncp->edits[eds + 1])) eds++;
	  for (i = 1; i <= eds; i++)
	    {
	      char *temp;
	      temp = edit_to_string(ncp, i);
	      edits[ed++] = XmStringCreateLocalized(temp);
	      free(temp);
	    }
	  if (k < sp->nchans - 1)
	    edits[ed++] = XmStringCreateLocalized((char *)"______________________________");
	}
      free(title);
      XtVaSetValues(lst, 
		    XmNitems, edits, 
		    XmNitemCount, ed, 
		    NULL);
      for (i = 0; i < ed; i++) 
	XmStringFree(edits[i]);
      free(edits);
      XmListSelectPos(lst, cp->edhist_base + cp->edit_ctr + 1, false);
      if (from_graph) goto_graph(cp);
    }
  else
    {
      int items = 0;
      eds = cp->edit_ctr;
      while ((eds < (cp->edit_size - 1)) && (cp->edits[eds + 1])) eds++;
      edits = (XmString *)calloc(eds + 1, sizeof(XmString));
      edits[0] = XmStringCreateLocalized(sp->filename);
      for (i = 1; i <= eds; i++)
	{
	  char *temp;
	  temp = edit_to_string(cp, i);
	  edits[i] = XmStringCreateLocalized(temp);
	  free(temp);
	}
      XtVaSetValues(lst, 
		    XmNitems, edits, 
		    XmNitemCount, eds + 1, 
		    NULL);
      for (i = 0; i <= eds; i++) 
	XmStringFree(edits[i]);
      free(edits);
      XmListSelectPos(lst, cp->edit_ctr + 1, false);
      XtVaGetValues(lst, XmNvisibleItemCount, &items, NULL);
      if (items <= eds)
	XtVaSetValues(lst, XmNtopItemPosition, eds - items + 2, NULL);
      if (from_graph) goto_graph(cp);
    }
}


static void watch_edit_history_sash(Widget w, XtPointer closure, XtPointer info)
{
  SashCallData call_data = (SashCallData)info;
  /* call_data->params[0]: Commit, Move, Key, Start (as strings) */
  if ((call_data->params) && 
      (mus_strcmp(call_data->params[0], "Start")))
    {
      chan_info *cp = (chan_info *)closure;
      Widget edhist;
      if ((cp) && (cp->widgets))
	{
	  edhist = EDIT_HISTORY_LIST(cp);
	  if (edhist)
	    remake_edit_history(edhist, cp, false);
	}
    }
}
#endif


void reflect_edit_history_change(chan_info *cp)
{
  /* new edit so it is added, and any trailing lines removed */
  snd_info *sp;
  Widget lst;

  if (cp->squelch_update) return;
  if (cp->in_as_one_edit > 0) return;
  sp = cp->sound;
  lst = EDIT_HISTORY_LIST(cp);
#if WITH_RELATIVE_PANES
  if ((lst) && (widget_width(lst) > 1))
    remake_edit_history(lst, cp, true);
  else
    {
      if ((cp->chan > 0) && (sp->channel_style != CHANNELS_SEPARATE))
	{
	  lst = EDIT_HISTORY_LIST(sp->chans[0]);
	  if ((lst) && (widget_width(lst) > 1))
	    remake_edit_history(lst, sp->chans[0], true);
	}
    }
#else
  /* old form */
  if (lst)
    {
      int i, eds, items = 0;
      XmString *edits;
      eds = cp->edit_ctr;
      while ((eds < (cp->edit_size - 1)) && (cp->edits[eds + 1])) eds++;
      if (eds >= 0)
	{
	  if ((eds == cp->edit_ctr) && (eds > 1)) /* need to force 0 (1) case to start list with sound file name */
	    {
	      XmString edit;
	      /* special common case -- we're appending a new edit description */
	      XtVaGetValues(lst, XmNitemCount, &items, NULL);
	      if (items > eds )
		XmListDeleteItemsPos(lst, cp->edit_size, eds + 1); 
	      /* cp->edit_size is too large, but the manual says this is the way to delete to the end */
	      {
		char *temp;
		temp = edit_to_string(cp, eds);
		edit = XmStringCreateLocalized(temp);
		free(temp);
	      }
	      XmListAddItemUnselected(lst, edit, eds + 1);
	      XmStringFree(edit);
	    }
	  else
	    {
	      edits = (XmString *)calloc(eds + 1, sizeof(XmString));
	      edits[0] = XmStringCreateLocalized(sp->filename);
	      for (i = 1; i <= eds; i++) 
		{
		  char *temp;
		  temp = edit_to_string(cp, i);
		  edits[i] = XmStringCreateLocalized(temp);
		  free(temp);
		}
	      XtVaSetValues(lst, 
			    XmNitems, edits, 
			    XmNitemCount, eds + 1, 
			    NULL);
	      for (i = 0; i <= eds; i++) 
		XmStringFree(edits[i]);
	      free(edits);
	    }
	  XmListSelectPos(lst, cp->edit_ctr + 1, false);
	  XtVaGetValues(lst, XmNvisibleItemCount, &items, NULL);
	  if (items <= eds)
	    XtVaSetValues(lst, XmNtopItemPosition, eds - items + 2, NULL);
	  goto_graph(cp);
	}
    }
#endif
}


void reflect_edit_counter_change(chan_info *cp)
{
  /* undo/redo/revert -- change which line is highlighted */
  Widget lst;

  if (cp->squelch_update) return;
  lst = EDIT_HISTORY_LIST(cp);
  if ((lst) && (widget_width(lst) > 1))
    {
      int len, top;
      XmListSelectPos(lst, cp->edit_ctr + 1, false);
      XtVaGetValues(lst, 
		    XmNvisibleItemCount, &len, 
		    XmNtopItemPosition, &top, 
		    NULL);
      if ((cp->edit_ctr + 1) < top) 
	XtVaSetValues(lst, XmNtopItemPosition, cp->edit_ctr + 1, NULL);
      else
	if ((cp->edit_ctr + 1) >= (top + len))
	  XtVaSetValues(lst, XmNtopItemPosition, cp->edit_ctr, NULL);
      goto_graph(cp);
    }
}


/* for combined cases, the incoming chan_info pointer is always chan[0], 
 * but the actual channel depends on placement if mouse oriented.
 * virtual_selected_channel(cp) (snd-chn.c) retains the current selected channel
 */

void graph_key_press(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  XKeyEvent *ev = (XKeyEvent *)event;
  KeySym keysym;
  int key_state;
  snd_info *sp = (snd_info *)context;
  key_state = ev->state;
  keysym = XkbKeycodeToKeysym(XtDisplay(w),
			      (int)(ev->keycode),
			      0, (key_state & ShiftMask) ? 1 : 0);
  key_press_callback(any_selected_channel(sp), ev->x, ev->y, ev->state, keysym);
}
 

static void cp_graph_key_press(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  /* called by every key-intercepting widget in the entire sound pane */
  XKeyEvent *ev = (XKeyEvent *)event;
  KeySym keysym;
  int key_state;
  chan_info *cp = (chan_info *)context;
  if ((!cp) || (!cp->sound)) return; /* can't happen */
  key_state = ev->state;
  keysym = XkbKeycodeToKeysym(XtDisplay(w),
			      (int)(ev->keycode),
			      0, (key_state & ShiftMask) ? 1 : 0);
  key_press_callback(cp, ev->x, ev->y, ev->state, keysym);
}


static void channel_drop_watcher(Widget w, const char *str, Position x, Position y, void *context)
{
  intptr_t data;
  data = (intptr_t)context;
  drag_and_drop_mix_at_x_y((int)data, str, x, y);
}


static void channel_drag_watcher(Widget w, const char *str, Position x, Position y, drag_style_t dtype, void *context)
{
  int snd, chn;
  intptr_t data;
  snd_info *sp;
  XtVaGetValues(w, XmNuserData, &data, NULL);
  chn = unpack_channel(data);
  snd = unpack_sound(data);

  sp = ss->sounds[snd];
  if (snd_ok(sp))
    {
      mus_float_t seconds;
      chan_info *cp;
      switch (dtype)
	{
	case DRAG_ENTER:
	case DRAG_MOTION:
	  cp = sp->chans[chn];
	  if ((sp->nchans > 1) && (sp->channel_style == CHANNELS_COMBINED))
	    cp = which_channel(sp, y);    
	  seconds = ungrf_x(cp->axis, x);
	  if (seconds < 0.0) seconds = 0.0;
	  if (sp->nchans > 1)
	    status_report(sp, "drop to mix file in chan %d at %.4f", cp->chan + 1, seconds);
	  else status_report(sp, "drop to mix file at %.4f", seconds);
	  break;

	case DRAG_LEAVE:
	  set_status(sp, " ", false); /* not clear_status_area here! => segfault */
	  break;
	}
    }
}


int add_channel_window(snd_info *sp, int channel, int chan_y, int insertion, Widget main, fw_button_t button_style, bool with_events)
{
  bool need_extra_scrollbars;
  Widget *cw;
  chan_info *cp;
  graphics_context *cax;
  bool make_widgets;

  /* if ((main) && ((!(XmIsForm(main))) || (!(XtWindow(main))))) return(-1); */ /* new gcc complains about the XmIsForm for some reason */
  if ((main) && (!(XtWindow(main)))) return(-1); /* can this happen? */

  make_widgets = (!sp->chans[channel]);
  sp->chans[channel] = make_chan_info(sp->chans[channel], channel, sp);
  cp = sp->chans[channel];

  if (!cp->widgets) 
    cp->widgets = (Widget *)calloc(CP_NUM_WIDGETS, sizeof(Widget));
  cw = cp->widgets;
  need_extra_scrollbars = ((!main) && (channel == 0));

  if (make_widgets)
    {
      XtCallbackList n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15;
      int n;
      Arg args[32];

      /* allocate the entire widget apparatus for this channel of this sound */

      if (!main)
	{
	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
	  if (insertion) {XtSetArg(args[n], XmNpositionIndex, (short)channel); n++;}
	  XtSetArg(args[n], XmNpaneMinimum, chan_y); n++;

	  cw[W_form] = XtCreateManagedWidget("chn-form", xmFormWidgetClass, w_snd_pane(sp), args, n);
	  if ((sp->channel_style == CHANNELS_COMBINED) && (channel > 0)) XtUnmanageChild(cw[W_form]);

	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
	  /* n = no_padding(args, n); */
	  n = attach_all_sides(args, n);
	  XtSetArg(args[n], XmNsashIndent, 2); n++;
	  XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
	  XtSetArg(args[n], XmNpaneMaximum, LOTSA_PIXELS); n++; 
	  cw[W_top] = XtCreateManagedWidget("chn-top", xmPanedWindowWidgetClass, cw[W_form], args, n);
	  XtAddEventHandler(cw[W_top], KeyPressMask, false, graph_key_press, (XtPointer)sp);

	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->white); n++;
	  XtSetArg(args[n], XmNpaneMaximum, DEFAULT_EDIT_HISTORY_WIDTH); n++;
	  XtSetArg(args[n], XmNlistSizePolicy, XmCONSTANT); n++;
	  cw[W_edhist] = XmCreateScrolledList(cw[W_top], (char *)"chn-edhist", args, n);
	  XtManageChild(cw[W_edhist]);

	  XtAddCallback(cw[W_edhist], XmNbrowseSelectionCallback, history_select_callback, cp);
	  XtAddEventHandler(cw[W_edhist], KeyPressMask, false, graph_key_press, (XtPointer)sp);
	  XtAddEventHandler(XtParent(cw[W_edhist]), KeyPressMask, false, graph_key_press, (XtPointer)sp);

	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
	  XtSetArg(args[n], XmNpaneMaximum, LOTSA_PIXELS); n++;
	  cw[W_main_window] = XtCreateManagedWidget("chn-main-window", xmFormWidgetClass, cw[W_top], args, n);
	  XtAddEventHandler(cw[W_main_window], KeyPressMask, false, graph_key_press, (XtPointer)sp);

#if WITH_RELATIVE_PANES
	{
	  int k;
	  Widget child;
	  CompositeWidget w = (CompositeWidget)(cw[W_top]);
	  for (k = w->composite.num_children - 1; k >= 0; k--)
	    {
	      child = w->composite.children[k];
	      if ((XtIsWidget(child)) && 
		  (XtIsSubclass(child, xmSashWidgetClass)))
		{
		  XtAddCallback(child, XmNcallback, watch_edit_history_sash, (XtPointer)cp);
		  break; /* there seems to be more than 1?? */
		}
	    }
	}
#endif
	}
      else cw[W_main_window] = main;

      n = 0;  
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      n = no_padding(args, n);
      XtSetArg(args[n], XmNpacking, XmPACK_COLUMN); n++;
      XtSetArg(args[n], XmNnumColumns, 1); n++;
      XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
      cw[W_wf_buttons] = XtCreateManagedWidget("chn-buttons", xmRowColumnWidgetClass, cw[W_main_window], args, n);	

      if (button_style == WITH_FW_BUTTONS)
	{
	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
	  XtSetArg(args[n], XmNspacing, 1); n++;
	  XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
	  cw[W_f] = make_togglebutton_widget("f", cw[W_wf_buttons], args, n);
	  XtAddCallback(cw[W_f], XmNvalueChangedCallback, f_toggle_callback, cp);
	  XtAddEventHandler(cw[W_f], KeyPressMask, false, graph_key_press, (XtPointer)sp);
	  
	  XtSetArg(args[n], XmNset, true); n++;
	  cw[W_w] = make_togglebutton_widget("w", cw[W_wf_buttons], args, n);
	  XtAddCallback(cw[W_w], XmNvalueChangedCallback, w_toggle_callback, cp);
	  XtAddEventHandler(cw[W_w], KeyPressMask, false, graph_key_press, (XtPointer)sp);
	}
      else
	{
	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
	  XtSetArg(args[n], XmNarrowDirection, XmARROW_UP); n++;
	  XtSetArg(args[n], XmNsensitive, false); n++;
	  cw[W_f] = XtCreateManagedWidget("up", xmArrowButtonWidgetClass, cw[W_wf_buttons], args, n);

	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
	  XtSetArg(args[n], XmNarrowDirection, XmARROW_DOWN); n++;
	  XtSetArg(args[n], XmNsensitive, false); n++;
	  cw[W_w] = XtCreateManagedWidget("down", xmArrowButtonWidgetClass, cw[W_wf_buttons], args, n);
	}

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, cw[W_wf_buttons]); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNspacing, 0); n++;
      cw[W_left_scrollers] = XtCreateManagedWidget("chn-left", xmRowColumnWidgetClass, cw[W_main_window], args, n);
      XtAddEventHandler(cw[W_left_scrollers], KeyPressMask, false, graph_key_press, (XtPointer)sp);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->zoom_color); n++;
      XtSetArg(args[n], XmNwidth, ss->position_slider_width); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; 
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++; 
      XtSetArg(args[n], XmNincrement, 1); n++;
      XtSetArg(args[n], XmNprocessingDirection, XmMAX_ON_TOP); n++;
      XtSetArg(args[n], XmNdragCallback, n1 = make_callback_list(zy_drag_callback, (XtPointer)cp)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n2 = make_callback_list(zy_valuechanged_callback, (XtPointer)cp)); n++;
      cw[W_zy] = XtCreateManagedWidget("chn-zy", xmScrollBarWidgetClass, cw[W_left_scrollers], args, n);
      XtAddEventHandler(cw[W_zy], KeyPressMask, false, graph_key_press, (XtPointer)sp);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->position_color); n++;
      XtSetArg(args[n], XmNwidth, ss->zoom_slider_width); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, cw[W_zy]); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNincrement, 1); n++;
      XtSetArg(args[n], XmNprocessingDirection, XmMAX_ON_TOP); n++;
      XtSetArg(args[n], XmNdragCallback, n3 = make_callback_list(sy_drag_callback, (XtPointer)cp)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n4 = make_callback_list(sy_valuechanged_callback, (XtPointer)cp)); n++;
      cw[W_sy] = XtCreateManagedWidget("chn-sy", xmScrollBarWidgetClass, cw[W_left_scrollers], args, n);
      XtAddEventHandler(cw[W_sy], KeyPressMask, false, graph_key_press, (XtPointer)sp);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, cw[W_wf_buttons]); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNspacing, 0); n++;
      cw[W_bottom_scrollers] = XtCreateManagedWidget("chn-bottom", xmRowColumnWidgetClass, cw[W_main_window], args, n);
      XtAddEventHandler(cw[W_bottom_scrollers], KeyPressMask, false, graph_key_press, (XtPointer)sp);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->position_color); n++;
      XtSetArg(args[n], XmNheight, ss->position_slider_width); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNincrement, 1); n++;
      XtSetArg(args[n], XmNdragCallback, n5 = make_callback_list(sx_drag_callback, (XtPointer)cp)); n++;
      XtSetArg(args[n], XmNincrementCallback, n6 = make_callback_list(sx_increment_callback, (XtPointer)cp)); n++;
      XtSetArg(args[n], XmNdecrementCallback, n7 = make_callback_list(sx_decrement_callback, (XtPointer)cp)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n8 = make_callback_list(sx_valuechanged_callback, (XtPointer)cp)); n++;
      cw[W_sx] = XtCreateManagedWidget("chn-sx", xmScrollBarWidgetClass, cw[W_bottom_scrollers], args, n);
      XtAddEventHandler(cw[W_sx], KeyPressMask, false, graph_key_press, (XtPointer)sp);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->zoom_color); n++;
      XtSetArg(args[n], XmNheight, ss->zoom_slider_width + 2); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, cw[W_sx]); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNincrement, 1); n++;
      XtSetArg(args[n], XmNdragCallback, n9 = make_callback_list(zx_drag_callback, (XtPointer)cp)); n++;
      XtSetArg(args[n], XmNincrementCallback, n10 = make_callback_list(zx_drag_callback, (XtPointer)cp)); n++;
      XtSetArg(args[n], XmNdecrementCallback, n11 = make_callback_list(zx_drag_callback, (XtPointer)cp)); n++;
      XtSetArg(args[n], XmNpageIncrementCallback, n12 = make_callback_list(zx_drag_callback, (XtPointer)cp)); n++;
      XtSetArg(args[n], XmNpageDecrementCallback, n13 = make_callback_list(zx_drag_callback, (XtPointer)cp)); n++;
      XtSetArg(args[n], XmNtoTopCallback, n14 = make_callback_list(zx_drag_callback, (XtPointer)cp)); n++;
      XtSetArg(args[n], XmNtoBottomCallback, n15 = make_callback_list(zx_drag_callback, (XtPointer)cp)); n++;

      cw[W_zx] = XtCreateManagedWidget("chn-zx", xmScrollBarWidgetClass, cw[W_bottom_scrollers], args, n);
      XtAddEventHandler(cw[W_zx], KeyPressMask, false, graph_key_press, (XtPointer)sp);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->graph_color); n++;
      XtSetArg(args[n], XmNforeground, ss->data_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, cw[W_bottom_scrollers]); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, cw[W_left_scrollers]); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNuserData, pack_sound_and_channel(sp->index, cp->chan)); n++;
      /* this collides with W_gzy below, but a consistent version came up with half a window blank */
      XtSetArg(args[n], XmNnavigationType, XmNONE); n++;
      cw[W_graph] = XtCreateManagedWidget("chn-graph", xmDrawingAreaWidgetClass, cw[W_main_window], args, n);

      if (with_events)
	{
	  /* region regraph sets up its own callbacks */
	  XtAddCallback(cw[W_graph], XmNresizeCallback, channel_resize_callback, (XtPointer)cp);
	  XtAddCallback(cw[W_graph], XmNexposeCallback, channel_expose_callback, (XtPointer)cp);
	}
      /* allow cursor in all cases (zoom to cursor in region window for example, or fft axis drag in variable display) */
      XtAddEventHandler(cw[W_graph], ButtonPressMask, false, graph_button_press, (XtPointer)cp);
      XtAddEventHandler(cw[W_graph], ButtonReleaseMask, false, graph_button_release, (XtPointer)cp);
      /* XtAddEventHandler(cw[W_graph], ButtonMotionMask, false, graph_button_motion, (XtPointer)cp); */
      XtAddEventHandler(cw[W_graph], PointerMotionMask, false, graph_mouse_motion, (XtPointer)cp);
      if (!main)
	{
	  intptr_t data;
	  XtAddEventHandler(cw[W_graph], EnterWindowMask, false, graph_mouse_enter, (XtPointer)cp);
	  XtAddEventHandler(cw[W_graph], LeaveWindowMask, false, graph_mouse_leave, (XtPointer)cp);
	  XtAddEventHandler(cw[W_graph], KeyPressMask, false, cp_graph_key_press, (XtPointer)cp);

	  data = (intptr_t)pack_sound_and_channel(sp->index, cp->chan);
	  add_drag_and_drop(cw[W_graph], channel_drop_watcher, channel_drag_watcher, (void *)data);
	}

      free(n1);
      free(n2);
      free(n3);
      free(n4);
      free(n5);
      free(n6);
      free(n7);
      free(n8);
      free(n9);
      free(n10);
      free(n11);
      free(n12);
      free(n13);
      free(n14);
      free(n15);

      if (need_extra_scrollbars)
	{
	  /* that is: not region browser chan, might need combined graph, channel 0 is the controller in that case */
	  /* this is independent of sp->nchans because these structs are re-used and added to as needed */
	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->zoom_color); n++;
	  XtSetArg(args[n], XmNwidth, ss->position_slider_width); n++;
	  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
	  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
	  XtSetArg(args[n], XmNbottomWidget, cw[W_bottom_scrollers]); n++;
	  XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
	  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
	  XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
	  XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++; 
	  XtSetArg(args[n], XmNincrement, 1); n++;
	  XtSetArg(args[n], XmNprocessingDirection, XmMAX_ON_TOP); n++;

	  XtSetArg(args[n], XmNincrementCallback, n1 = make_callback_list(gzy_drag_callback, (XtPointer)cp)); n++;
	  XtSetArg(args[n], XmNdecrementCallback, n2 = make_callback_list(gzy_drag_callback, (XtPointer)cp)); n++;
	  XtSetArg(args[n], XmNpageIncrementCallback, n3 = make_callback_list(gzy_drag_callback, (XtPointer)cp)); n++;
	  XtSetArg(args[n], XmNpageDecrementCallback, n4 = make_callback_list(gzy_drag_callback, (XtPointer)cp)); n++;
	  XtSetArg(args[n], XmNtoTopCallback, n5 = make_callback_list(gzy_drag_callback, (XtPointer)cp)); n++;
	  XtSetArg(args[n], XmNtoBottomCallback, n6 = make_callback_list(gzy_drag_callback, (XtPointer)cp)); n++;
	  XtSetArg(args[n], XmNdragCallback, n7 = make_callback_list(gzy_drag_callback, (XtPointer)cp)); n++;

	  cw[W_gzy] = XtCreateManagedWidget("chn-gzy", xmScrollBarWidgetClass, cw[W_main_window], args, n);
	  XtAddEventHandler(cw[W_gzy], KeyPressMask, false, graph_key_press, (XtPointer)sp);


	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->position_color); n++;
	  XtSetArg(args[n], XmNwidth, ss->position_slider_width); n++;
	  XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
	  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
	  XtSetArg(args[n], XmNbottomWidget, cw[W_bottom_scrollers]); n++;
	  XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
	  XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
	  XtSetArg(args[n], XmNrightWidget, cw[W_gzy]); n++;
	  XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
	  XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
	  XtSetArg(args[n], XmNincrement, 1); n++;
	  XtSetArg(args[n], XmNprocessingDirection, XmMAX_ON_TOP); n++;
	  XtSetArg(args[n], XmNdragCallback, n8 = make_callback_list(gsy_drag_callback, (XtPointer)cp)); n++;
	  XtSetArg(args[n], XmNvalueChangedCallback, n9 = make_callback_list(gsy_valuechanged_callback, (XtPointer)cp)); n++;
	  cw[W_gsy] = XtCreateManagedWidget("chn-gsy", xmScrollBarWidgetClass, cw[W_main_window], args, n);
	  XtAddEventHandler(cw[W_gsy], KeyPressMask, false, graph_key_press, (XtPointer)sp);
	  
	  free(n1);
	  free(n2);
	  free(n3);
	  free(n4);
	  free(n5);
	  free(n6);
	  free(n7);

	  free(n8);
	  free(n9);
	}
      else
	{
	  cw[W_gsy] = NULL;
	  cw[W_gzy] = NULL;
	}
      run_new_widget_hook(cw[W_main_window]);
      /* also position of current graph in overall sound as window */

    } /* end alloc new chan */

  else
    { 
      int i;
      /* re-manage currently inactive chan */
      XtVaSetValues(cw[W_main_window], XmNpaneMinimum, chan_y, NULL);
      if (cw[W_edhist]) 
	XtVaSetValues(XtParent(cw[W_edhist]), XmNpaneMaximum, 1, NULL);
      if ((sp->channel_style != CHANNELS_COMBINED) || 
	  (channel == 0))
	for (i = 0; i < CP_NUM_WIDGETS - 1; i++)
	  if ((cw[i]) && (!XtIsManaged(cw[i])))
	    XtManageChild(cw[i]);
      recolor_graph(cp, false); /* in case selection color left over from previous use */
    }

  if (cw[W_edhist]) 
    XtVaSetValues(XtParent(cw[W_edhist]), XmNpaneMaximum, LOTSA_PIXELS, NULL);

  if ((need_extra_scrollbars) && 
      (sp->channel_style != CHANNELS_COMBINED)) 
    hide_gz_scrollbars(sp); /* default is on in this case */  

  cax = cp->ax;
  cax->wn = XtWindow(cw[W_graph]);
  cax->dp = XtDisplay(cw[W_graph]);
  cax->gc = ss->basic_gc;
  return(0);
}


static void set_graph_font(chan_info *cp, graphics_context *ax, XFontStruct *bf)
{
  if (!ax) return;
  ax->current_font = bf->fid;  
  XSetFont(XtDisplay(channel_to_widget(cp)), copy_GC(cp), bf->fid);
}


void set_peak_numbers_font(chan_info *cp, graphics_context *ax) {set_graph_font(cp, ax, PEAKS_FONT(ss));}

void set_tiny_numbers_font(chan_info *cp, graphics_context *ax) {set_graph_font(cp, ax, TINY_FONT(ss));}

void set_bold_peak_numbers_font(chan_info *cp, graphics_context *ax) {set_graph_font(cp, ax, BOLD_PEAKS_FONT(ss));}


color_t get_foreground_color(graphics_context *ax)
{
  XGCValues gv;
  XGetGCValues(main_display(ss), ax->gc, GCForeground, &gv);
  return(gv.foreground);
}


void set_foreground_color(graphics_context *ax, Pixel color)
{
  XSetForeground(main_display(ss), ax->gc, color);
}


GC copy_GC(chan_info *cp)
{
  if (cp->selected) return(ss->selected_basic_gc);
  return(ss->basic_gc);
}


GC erase_GC(chan_info *cp)
{
  /* used only to clear partial bgs in chan graphs */
  snd_info *sp;
  sp = cp->sound;
  if ((cp->selected) ||
      ((sp) && 
       (sp->channel_style == CHANNELS_SUPERIMPOSED) && 
       (sp->index == ss->selected_sound)))
    return(ss->selected_erase_gc);
  return(ss->erase_gc);
}


void free_fft_pix(chan_info *cp)
{
  if ((cp->fft_pix != None) &&
      (channel_graph(cp)))
    XFreePixmap(XtDisplay(channel_graph(cp)),
		cp->fft_pix);
  cp->fft_pix = None;
  cp->fft_pix_ready = false;
}


bool restore_fft_pix(chan_info *cp, graphics_context *ax)
{
  XCopyArea(ax->dp,
	    cp->fft_pix, 
	    ax->wn,
	    copy_GC(cp),
	    0, 0,                          /* source x y */
	    cp->fft_pix_width, cp->fft_pix_height,
	    cp->fft_pix_x0, cp->fft_pix_y0);
  return(true);
}


void save_fft_pix(chan_info *cp, graphics_context *ax, int fwidth, int fheight, int x0, int y1)
{
  if ((fwidth == 0) || (fheight == 0)) return;
  if (cp->fft_pix == None)
    {
      /* make new pixmap */
      cp->fft_pix_width = fwidth;
      cp->fft_pix_height = fheight;
      cp->fft_pix_x0 = x0;
      cp->fft_pix_y0 = y1;
      cp->fft_pix_cutoff = cp->spectrum_end;
      cp->fft_pix = XCreatePixmap(ax->dp,
				  RootWindowOfScreen(XtScreen(channel_graph(cp))),
				  fwidth, fheight,
				  DefaultDepthOfScreen(XtScreen(channel_graph(cp))));
    }
  XCopyArea(ax->dp,
	    ax->wn,
	    cp->fft_pix, 
	    copy_GC(cp),
	    cp->fft_pix_x0, cp->fft_pix_y0,
	    cp->fft_pix_width, cp->fft_pix_height,
	    0, 0);
  cp->fft_pix_ready = true;
}


void cleanup_cw(chan_info *cp)
{
  if (cp)
    {
      Widget *cw;
      free_fft_pix(cp);

      cp->selected = false;
      cw = cp->widgets;
      if (cw)
	{
	  if (cw[W_w])
	    {
	      XtVaSetValues(cw[W_w], XmNset, true, NULL);
	      XtVaSetValues(cw[W_f], XmNset, false, NULL);
	    }
	  if (channel_main_pane(cp))
	    XtUnmanageChild(channel_main_pane(cp));
	}
    }
}


void change_channel_style(snd_info *sp, channel_style_t new_style)
{
  if ((sp) && 
      (sp->nchans > 1))
    {
      channel_style_t old_style;
      chan_info *selected_cp;

      selected_cp = any_selected_channel(sp); /* chan 0 is none is selected */
      old_style = sp->channel_style;
      sp->channel_style = new_style;

      if (new_style != old_style)
	{
	  uint32_t i, height;

#if WITH_RELATIVE_PANES
	  if ((new_style == CHANNELS_SEPARATE) || (old_style == CHANNELS_SEPARATE))
	    {
	      Widget lst;
	      lst = EDIT_HISTORY_LIST(sp->chans[0]);
	      if ((lst) && (widget_width(lst) > 1))
		remake_edit_history(lst, sp->chans[0], true);
	    }
#endif

	  if (old_style == CHANNELS_COMBINED)
	    {
	      hide_gz_scrollbars(sp);
	      for (i = 1; i < sp->nchans; i++) channel_set_mix_tags_erased(sp->chans[i]);
	    }
	  else 
	    {
	      if (new_style == CHANNELS_COMBINED)
		{
		  show_gz_scrollbars(sp);
		  for (i = 1; i < sp->nchans; i++) channel_set_mix_tags_erased(sp->chans[i]);
		}
	    }

	  if (old_style == CHANNELS_SUPERIMPOSED)
	    {
	      syncb(sp, sp->previous_sync);
	      XtVaSetValues(unite_button(sp), XmNselectColor, ss->selection_color, NULL);
	    }
	  else
	    {
	      if (new_style == CHANNELS_SUPERIMPOSED)
		{
		  sp->previous_sync = sp->sync;
		  if (sp->sync == 0) syncb(sp, 1);
		  XtVaSetValues(unite_button(sp), XmNselectColor, ss->green, NULL);

		  apply_y_axis_change(selected_cp);
		  apply_x_axis_change(selected_cp);

		  for (i = 0; i < sp->nchans; i++) 
		    {
		      if ((int)i != selected_cp->chan)
			{
			  chan_info *ncp;
			  ncp = sp->chans[i];
			  cursor_sample(ncp) = cursor_sample(selected_cp);
			  if (selected_cp->graph_transform_on != ncp->graph_transform_on)
			    {
			      ncp->graph_transform_on = selected_cp->graph_transform_on;
			      set_toggle_button(channel_f(ncp), selected_cp->graph_transform_on, false, (void *)ncp);
			    }
			  if (selected_cp->graph_time_on != ncp->graph_time_on)
			    {
			      ncp->graph_time_on = selected_cp->graph_time_on;
			      set_toggle_button(channel_w(ncp), selected_cp->graph_time_on, false, (void *)ncp);
			    }
			}
		    }
		}
	    }

	  height = widget_height(w_snd_pane(sp)) - control_panel_height(sp);
	  if (old_style == CHANNELS_SEPARATE)
	    {
	      axis_info *ap;
	      ap = selected_cp->axis;
	      channel_lock_pane(sp->chans[0], height);

	      for (i = 0; i < sp->nchans; i++) 
		{
		  if ((int)i != selected_cp->chan)
		    set_axes(sp->chans[i], ap->x0, ap->x1, ap->y0, ap->y1);
		  if (i > 0)
		    cleanup_cw(sp->chans[i]);
		}

	      channel_open_pane(sp->chans[0]);
	      channel_unlock_pane(sp->chans[0]);
	      XmToggleButtonSetState(unite_button(sp), true, false);
	    }
	  else
	    {
	      if (new_style == CHANNELS_SEPARATE)
		{
		  /* height = total space available */
		  height /= sp->nchans;
		  for_each_sound_chan_with_int(sp, channel_lock_pane, height);
		  for_each_sound_chan(sp, channel_open_pane);
		  for_each_sound_chan(sp, channel_unlock_pane);

		  for (i = 1; i < sp->nchans; i++)
		    {
		      Widget *cw;
		      chan_info *cp;
		      int j;

		      cp = sp->chans[i];
		      cw = cp->widgets;

		      for (j = 0; j < CP_NUM_WIDGETS - 1; j++)
			if ((cw[j]) && (!XtIsManaged(cw[j]))) 
			  XtManageChild(cw[j]);

		      XmToggleButtonSetState(cw[W_f], (Boolean)(cp->graph_transform_on), false);
		      XmToggleButtonSetState(cw[W_w], (Boolean)(cp->graph_time_on), false);
		      /* these can get out of sync if changes are made in the unseparated case */
		    }

		  XmToggleButtonSetState(unite_button(sp), false, false);
		  if (sp->selected_channel > 0) color_selected_channel(sp);
		}
	    }

	  if ((new_style == CHANNELS_COMBINED) && 
	      (sp->selected_channel > 0)) 
	    color_selected_channel(sp);
	}
    }
}


static Xen g_channel_widgets(Xen snd, Xen chn)
{
  #define H_channel_widgets "(" S_channel_widgets " :optional snd chn): a list of widgets: ((0)graph (1)w (2)f (3)sx (4)sy (5)zx (6)zy (7)edhist)"
  chan_info *cp;
  Snd_assert_channel(S_channel_widgets, snd, chn, 1);
  cp = get_cp(snd, chn, S_channel_widgets);
  if (!cp) return(Xen_false);
  return(Xen_cons(Xen_wrap_widget(channel_graph(cp)),
	   Xen_cons(Xen_wrap_widget(channel_w(cp)),
	     Xen_cons(Xen_wrap_widget(channel_f(cp)),
	       Xen_cons(Xen_wrap_widget(channel_sx(cp)),
	         Xen_cons(Xen_wrap_widget(channel_sy(cp)),
	           Xen_cons(Xen_wrap_widget(channel_zx(cp)),
	             Xen_cons(Xen_wrap_widget(channel_zy(cp)),
		       Xen_cons(Xen_wrap_widget(EDIT_HISTORY_LIST(cp)),
			 Xen_cons(Xen_wrap_widget(channel_gsy(cp)),
			   Xen_cons(Xen_wrap_widget(channel_gzy(cp)),
			     Xen_cons(Xen_wrap_widget(channel_main_pane(cp)),
	                       Xen_empty_list))))))))))));
}


/* previous snd-xxen.c contents) */

static void timed_eval(XtPointer in_code, XtIntervalId *id)
{
#if HAVE_EXTENSION_LANGUAGE
  Xen lst = (XEN)in_code;
  Xen_call_with_no_args(Xen_cadr(lst), "timed callback func");
  snd_unprotect_at(Xen_integer_to_C_int(Xen_car(lst)));
#endif
}


static Xen g_in(Xen ms, Xen code)
{
  #define H_in "(" S_in " msecs thunk): invoke thunk in msecs milliseconds (named call_in in Ruby)"

#if HAVE_EXTENSION_LANGUAGE
  Xen_check_type(Xen_is_number(ms), ms, 1, S_in, "a number");
  Xen_check_type(Xen_is_procedure(code), code, 2, S_in, "a procedure");

  if (Xen_is_aritable(code, 0))
    {
      int secs;
      Xen_check_type(Xen_is_integer(ms), ms, 3, S_in, "an integer");
      secs = Xen_integer_to_C_int(ms);
      if (secs < 0) 
	Xen_out_of_range_error(S_in, 1, ms, "a positive integer");
      else
	{
	  Xen lst;
	  lst = Xen_list_2(Xen_false, code);
	  Xen_list_set(lst, 0, C_int_to_Xen_integer(snd_protect(lst)));
	  XtAppAddTimeOut(main_app(ss), 
			  (unsigned long)secs,
			  (XtTimerCallbackProc)timed_eval, 
			  (XtPointer)lst);
	}
    }
  else Xen_bad_arity_error(S_in, 2, code, "should take no args");
#endif

  return(ms);
}


void color_unselected_graphs(color_t color)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      snd_info *sp;
      int j;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse != SOUND_WRAPPER))
	for (j = 0; j < sp->allocated_chans; j++)
	  {
	    chan_info *cp;
	    cp = sp->chans[j];
	    if ((cp) && ((i != ss->selected_sound) || (j != sp->selected_channel)))
	      XtVaSetValues(channel_graph(cp), XmNbackground, color, NULL);
	  }
    }
}


void color_chan_components(color_t color, slider_choice_t which_component)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      snd_info *sp;
      int j;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse != SOUND_WRAPPER))
	for (j = 0; j < sp->allocated_chans; j++)
	  {
	    chan_info *cp;
	    cp = sp->chans[j];
	    if (cp)
	      {
		if (which_component == COLOR_POSITION)
		  {
		    XtVaSetValues(channel_sx(cp), XmNbackground, color, NULL);
		    XtVaSetValues(channel_sy(cp), XmNbackground, color, NULL);
		  }
		else
		  {
		    XtVaSetValues(channel_zy(cp), XmNbackground, color, NULL);
		    XtVaSetValues(channel_zx(cp), XmNbackground, color, NULL);
		  }
	      }
	  }
    }
}


static Xen g_graph_cursor(void)
{
  #define H_graph_cursor "(" S_graph_cursor "): current graph cursor shape"
  return(C_int_to_Xen_integer(in_graph_cursor(ss)));
}


static Xen g_set_graph_cursor(Xen curs)
{
  int val;
  Xen_check_type(Xen_is_integer(curs), curs, 1, S_set S_graph_cursor, "an integer");
  /* X11/cursorfont.h has various even-numbered glyphs, but the odd numbers are ok, and XC_num_glyphs is a lie */
  /*   if you use too high a number here, X dies */
  val = Xen_integer_to_C_int(curs);
  if ((val >= 0) && (val <= XC_xterm))
    {
      ss->Graph_Cursor = val;
      ss->graph_cursor = XCreateFontCursor(XtDisplay(main_shell(ss)), in_graph_cursor(ss));
    }
  else Xen_out_of_range_error(S_set S_graph_cursor, 1, curs, "invalid cursor");
  return(curs);
}


#include <X11/xpm.h>

#define sound_env_editor(Sp) ((env_editor *)(sp->flt))
#define TOGGLE_MARGIN 0

enum {W_pane,
      W_name_form, W_amp_form,
      W_amp, W_amp_label, W_amp_number, 
      W_speed, W_speed_label, W_speed_number, W_speed_arrow,
      W_expand, W_expand_label, W_expand_number, W_expand_button,
      W_contrast, W_contrast_label, W_contrast_number, W_contrast_button,
      W_revscl, W_revscl_label, W_revscl_number,
      W_revlen, W_revlen_label, W_revlen_number, W_reverb_button,
      W_filter_label, W_filter_order, W_filter_env, W_filter, W_filter_button, W_filter_dB, W_filter_hz, W_filter_frame,
      W_filter_order_down, W_filter_order_up,
      W_name, W_lock_or_bomb, W_stop_icon, W_info,
      W_play, W_sync, W_unite, W_close,
      SP_NUM_WIDGETS
};


Widget unite_button(snd_info *sp) {return(sp->widgets[W_unite]);}
Widget w_snd_pane(snd_info *sp)   {return(sp->widgets[W_pane]);}

#define SND_PANE(Sp)             Sp->widgets[W_pane]
#define SND_NAME(Sp)             Sp->widgets[W_name]

#define NAME_BOX(Sp)             Sp->widgets[W_name_form]
#define LOCK_OR_BOMB(Sp)         Sp->widgets[W_lock_or_bomb]
#define STOP_ICON(Sp)            Sp->widgets[W_stop_icon]
#define NAME_LABEL(Sp)           Sp->widgets[W_name]
#define STATUS_AREA(Sp)          Sp->widgets[W_info]
#define SYNC_BUTTON(Sp)          Sp->widgets[W_sync]
#define PLAY_BUTTON(Sp)          Sp->widgets[W_play]
#define UNITE_BUTTON(Sp)         Sp->widgets[W_unite]
#define CLOSE_BUTTON(Sp)         Sp->widgets[W_close]

#define CONTROLS(Sp)             Sp->widgets[W_amp_form]
#define AMP_SCROLLBAR(Sp)        Sp->widgets[W_amp]
#define AMP_LABEL(Sp)            Sp->widgets[W_amp_number]
#define AMP_BUTTON(Sp)           Sp->widgets[W_amp_label]

#define SPEED_SCROLLBAR(Sp)      Sp->widgets[W_speed]
#define SPEED_ARROW(Sp)          Sp->widgets[W_speed_arrow]
#define SPEED_LABEL(Sp)          Sp->widgets[W_speed_number]
#define SPEED_BUTTON(Sp)         Sp->widgets[W_speed_label]

#define EXPAND_SCROLLBAR(Sp)     Sp->widgets[W_expand]
#define EXPAND_LABEL(Sp)         Sp->widgets[W_expand_number]
#define EXPAND_RIGHT_BUTTON(Sp)  Sp->widgets[W_expand_button]
#define EXPAND_LEFT_BUTTON(Sp)   Sp->widgets[W_expand_label]

#define CONTRAST_SCROLLBAR(Sp)   Sp->widgets[W_contrast]
#define CONTRAST_LABEL(Sp)       Sp->widgets[W_contrast_number]
#define CONTRAST_RIGHT_BUTTON(Sp) Sp->widgets[W_contrast_button]
#define CONTRAST_LEFT_BUTTON(Sp) Sp->widgets[W_contrast_label]

#define REVSCL_SCROLLBAR(Sp)     Sp->widgets[W_revscl]
#define REVLEN_SCROLLBAR(Sp)     Sp->widgets[W_revlen]
#define REVSCL_LABEL(Sp)         Sp->widgets[W_revscl_number]
#define REVLEN_LABEL(Sp)         Sp->widgets[W_revlen_number]
#define REVSCL_BUTTON(Sp)        Sp->widgets[W_revscl_label]
#define REVLEN_BUTTON(Sp)        Sp->widgets[W_revlen_label]
#define REVERB_BUTTON(Sp)        Sp->widgets[W_reverb_button]

#define FILTER_ORDER_TEXT(Sp)    Sp->widgets[W_filter_order]
#define FILTER_COEFFS_TEXT(Sp)   Sp->widgets[W_filter]
#define FILTER_BUTTON(Sp)        Sp->widgets[W_filter_button]
#define FILTER_DB_BUTTON(Sp)     Sp->widgets[W_filter_dB]
#define FILTER_HZ_BUTTON(Sp)     Sp->widgets[W_filter_hz]
#define FILTER_LABEL(Sp)         Sp->widgets[W_filter_label]
#define FILTER_GRAPH(Sp)         Sp->widgets[W_filter_env]
#define FILTER_ORDER_UP(Sp)      Sp->widgets[W_filter_order_up]
#define FILTER_ORDER_DOWN(Sp)    Sp->widgets[W_filter_order_down]
#define FILTER_FRAME(Sp)         Sp->widgets[W_filter_frame]

#define PROGRESS_ICON(Cp)        (Cp)->sound->progress_widgets[(Cp)->chan]


int snd_pane_height(snd_info *sp)
{
  Dimension height;
  XtVaGetValues(SND_PANE(sp), XmNheight, &height, NULL);
  return((int)height);
}


void set_status(snd_info *sp, const char *str, bool update) 
{
  if ((sp->inuse != SOUND_NORMAL) || (!has_widgets(sp))) return;
  XmTextSetString(STATUS_AREA(sp), (char *)str);
  /* updating clears the entire graph widget and triggers an expose event -- this is evil if we're currently displaying! */
  /* there's also a bug in libxcb (fixed, but not propagated yet) that causes a segfault here if more than
   *   one thread is affected by this global X queue flush.
   */

  if (update) XmUpdateDisplay(STATUS_AREA(sp));
}


static void name_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  char *str;
  snd_info *sp = (snd_info *)context;
  str = sp_name_click(sp);
  if (str)
    {
      status_report(sp, "%s", str);
      free(str);
    }
}


static void stop_sign_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  snd_info *sp = (snd_info *)context;
  if ((ss->checking_explicitly) || (play_in_progress()))
    ss->stopped_explicitly = true; 
  stop_playing_all_sounds(PLAY_C_G);
  if (sp->applying) stop_applying(sp);
  for_each_sound_chan(sp, stop_fft_in_progress);
}


/* The 0.9 * SCROLLBAR_MAX reflects the fact that the slider is 10% of the trough, and the left edge of the slider is the readback value */

/* ---------------- AMP-CONTROL ---------------- */

int amp_to_scroll(mus_float_t minval, mus_float_t val, mus_float_t maxval)
{
  if (val <= minval) return(0);
  if (val >= maxval) return((int)(0.9 * SCROLLBAR_MAX));
  if (val >= 1.0)
    return(snd_round(0.9 * 0.5 * SCROLLBAR_MAX * (1.0 + (val - 1.0) / (maxval - 1.0))));
  return(snd_round(0.9 * 0.5 * SCROLLBAR_MAX * ((val - minval) / (1.0 - minval))));
}


static int scroll_to_amp(snd_info *sp, int val)
{
  char amp_number_buffer[6];
  if (val <= 0) 
    sp->amp_control = sp->amp_control_min;
  else
    {
      if (val >= (0.9 * SCROLLBAR_MAX)) 
	sp->amp_control = sp->amp_control_max;
      else
	{
	  if (val > (0.5 * 0.9 * SCROLLBAR_MAX))
	    sp->amp_control = (((val / (0.5 * 0.9 * SCROLLBAR_MAX)) - 1.0) * (sp->amp_control_max - 1.0)) + 1.0;
	  else sp->amp_control = (val * (1.0 - sp->amp_control_min) / (0.5 * 0.9 * SCROLLBAR_MAX)) + sp->amp_control_min;
	}
    }
  snprintf(amp_number_buffer, 6, "%.3f", sp->amp_control);
  set_label(AMP_LABEL(sp), amp_number_buffer);
  return(val);
}


void set_amp(snd_info *sp, mus_float_t val)
{
  if (!has_widgets(sp))
    sp->amp_control = val;
  else XtVaSetValues(AMP_SCROLLBAR(sp),
		     XmNvalue,
		     scroll_to_amp(sp, amp_to_scroll(sp->amp_control_min, val, sp->amp_control_max)),
		     NULL);
}


static void amp_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmPushButtonCallbackStruct *cb = (XmPushButtonCallbackStruct *)info;
  snd_info *sp = (snd_info *)context;
  XButtonEvent *ev;
  ev = (XButtonEvent *)(cb->event);
  if (ev->state & (ControlMask | MetaMask)) 
    set_amp(sp, sp->last_amp_control);
  else set_amp(sp, 1.0);
}


static void amp_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  scroll_to_amp((snd_info *)context, ((XmScrollBarCallbackStruct *)info)->value);
}


static void amp_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScrollBarCallbackStruct *cb = (XmScrollBarCallbackStruct *)info;
  snd_info *sp = (snd_info *)context;
  scroll_to_amp(sp, cb->value);
  sp->last_amp_control = sp->saved_amp_control;
  sp->saved_amp_control = sp->amp_control;
}


/* ---------------- SPEED-CONTROL ---------------- */

XmString initial_speed_label(speed_style_t style)
{
  /* used also in snd-xmix.c */
  switch (style)
    {
    case SPEED_CONTROL_AS_RATIO:    return(XmStringCreateLocalized((char *)"  1/1")); break;
    case SPEED_CONTROL_AS_SEMITONE: return(XmStringCreateLocalized((char *)"    0")); break;
    default:                        return(XmStringCreateLocalized((char *)" 1.00")); break;
    }
}


static int speed_to_scroll(mus_float_t minval, mus_float_t val, mus_float_t maxval)
{
  if (val <= minval) return(0);
  if (val >= maxval) return((int)(0.9 * SCROLLBAR_MAX));
  return(snd_round(0.9 * SCROLLBAR_MAX * ((log(val) - log(minval)) / (log(maxval) - log(minval)))));
}


static int scroll_to_speed(snd_info *sp, int ival)
{
  char speed_number_buffer[6];
  sp->speed_control = speed_changed(exp((ival * (log(sp->speed_control_max) - log(sp->speed_control_min)) / 
					 (0.9 * SCROLLBAR_MAX)) + 
					log(sp->speed_control_min)),
				    speed_number_buffer,
				    sp->speed_control_style,
				    sp->speed_control_tones,
				    6);
  set_label(SPEED_LABEL(sp), speed_number_buffer);
  /* set_label works with buttons or labels */
  return(ival);
}


void set_speed(snd_info *sp, mus_float_t val)
{
  if (!has_widgets(sp))
    sp->speed_control = val;
  else XtVaSetValues(SPEED_SCROLLBAR(sp),
		     XmNvalue,
		     scroll_to_speed(sp, speed_to_scroll(sp->speed_control_min, val, sp->speed_control_max)),
		     NULL);
}


static void speed_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmPushButtonCallbackStruct *cb = (XmPushButtonCallbackStruct *)info;
  snd_info *sp = (snd_info *)context;
  XButtonEvent *ev;


  ev = (XButtonEvent *)(cb->event);
  if (ev->state & (ControlMask | MetaMask)) 
    set_speed(sp, sp->last_speed_control);
  else set_speed(sp, 1.0);

#if XEN_HAVE_RATIOS
  if (sp->speed_control_style == SPEED_CONTROL_AS_RATIO)
    snd_rationalize(sp->speed_control, &(sp->speed_control_numerator), &(sp->speed_control_denominator));
#endif
}


static void speed_label_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  snd_info *sp = (snd_info *)context;
  switch (sp->speed_control_style)
    {
    default:
    case SPEED_CONTROL_AS_FLOAT:    sp->speed_control_style = SPEED_CONTROL_AS_RATIO;    break;
    case SPEED_CONTROL_AS_RATIO:    sp->speed_control_style = SPEED_CONTROL_AS_SEMITONE; break;
    case SPEED_CONTROL_AS_SEMITONE: sp->speed_control_style = SPEED_CONTROL_AS_FLOAT;    break;
    }
  set_speed(sp, sp->speed_control);  /* remake label */
}


static void speed_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  snd_info *sp = (snd_info *)context;
  scroll_to_speed(sp, ((XmScrollBarCallbackStruct *)info)->value);

#if XEN_HAVE_RATIOS
  if (sp->speed_control_style == SPEED_CONTROL_AS_RATIO)
    snd_rationalize(sp->speed_control, &(sp->speed_control_numerator), &(sp->speed_control_denominator));
#endif
}


static void speed_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScrollBarCallbackStruct *cb = (XmScrollBarCallbackStruct *)info;
  snd_info *sp = (snd_info *)context;
  scroll_to_speed(sp, cb->value);

#if XEN_HAVE_RATIOS
  if (sp->speed_control_style == SPEED_CONTROL_AS_RATIO)
    snd_rationalize(sp->speed_control, &(sp->speed_control_numerator), &(sp->speed_control_denominator));
#endif
  sp->last_speed_control = sp->saved_speed_control;
  sp->saved_speed_control = sp->speed_control;
}


void toggle_direction_arrow(snd_info *sp, bool state)
{
  if (!has_widgets(sp))
    sp->speed_control_direction = ((state) ? -1 : 1);
  else XmToggleButtonSetState(SPEED_ARROW(sp), (Boolean)state, true);
}


/* ---------------- EXPAND-CONTROL ---------------- */

static int expand_to_scroll(mus_float_t minval, mus_float_t val, mus_float_t maxval)
{
  if (val <= minval) return(0);
  if (val >= maxval) return((int)(0.9 * SCROLLBAR_MAX));
  return(snd_round(0.9 * SCROLLBAR_MAX * ((log(val) - log(minval)) / (log(maxval) - log(minval)))));
}


static int scroll_to_expand(snd_info *sp, int val)
{
  char expand_number_buffer[6];

  if (val <= 0) 
    sp->expand_control = sp->expand_control_min;
  else
    {
      if (val >= (0.9 * SCROLLBAR_MAX)) 
	sp->expand_control = sp->expand_control_max;
      else sp->expand_control = exp((val * (log(sp->expand_control_max) - log(sp->expand_control_min)) / (0.9 * SCROLLBAR_MAX)) + log(sp->expand_control_min));
    }

  if (sp->playing) dac_set_expand(sp, sp->expand_control);
  snprintf(expand_number_buffer, 6, "%.3f", sp->expand_control);
  set_label(EXPAND_LABEL(sp), expand_number_buffer);
  return(val);
}


void set_expand(snd_info *sp, mus_float_t val)
{
  if (!has_widgets(sp))
    sp->expand_control = val;
  else XtVaSetValues(EXPAND_SCROLLBAR(sp),
		     XmNvalue,
		     scroll_to_expand(sp, expand_to_scroll(sp->expand_control_min, val, sp->expand_control_max)),
		     NULL);
}


static void expand_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmPushButtonCallbackStruct *cb = (XmPushButtonCallbackStruct *)info;
  snd_info *sp = (snd_info *)context;
  XButtonEvent *ev;
  ev = (XButtonEvent *)(cb->event);
  if (ev->state & (ControlMask | MetaMask))
    set_expand(sp, sp->last_expand_control);
  else set_expand(sp, 1.0);
}


static void expand_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  scroll_to_expand((snd_info *)context, ((XmScrollBarCallbackStruct *)info)->value);
}


static void expand_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScrollBarCallbackStruct *cb = (XmScrollBarCallbackStruct *)info;
  snd_info *sp = (snd_info *)context;
  scroll_to_expand(sp, cb->value);
  sp->last_expand_control = sp->saved_expand_control;
  sp->saved_expand_control = sp->expand_control;
}


static void expand_button_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info; 
  snd_info *sp = (snd_info *)context;


  sp->expand_control_on = cb->set;
  XmChangeColor(EXPAND_SCROLLBAR(sp), (Pixel)((sp->expand_control_on) ? (ss->position_color) : (ss->basic_color)));
}


void toggle_expand_button(snd_info *sp, bool state)
{
  if (!has_widgets(sp))
    sp->expand_control_on = state;
  else XmToggleButtonSetState(EXPAND_RIGHT_BUTTON(sp), (Boolean)state, true);
}


/* ---------------- CONTRAST-CONTROL ---------------- */

static int contrast_to_scroll(mus_float_t minval, mus_float_t val, mus_float_t maxval)
{
  if (val <= minval) return(0);
  if (val >= maxval) return((int)(0.9 * SCROLLBAR_MAX));
  return(snd_round((val - minval) / (maxval - minval) * 0.9 * SCROLLBAR_MAX));
}


static int scroll_to_contrast(snd_info *sp, int val)
{
  char contrast_number_buffer[6];
  sp->contrast_control = sp->contrast_control_min + val * (sp->contrast_control_max - sp->contrast_control_min) / (0.9 * SCROLLBAR_MAX);
  snprintf(contrast_number_buffer, 6, "%.3f", sp->contrast_control);
  set_label(CONTRAST_LABEL(sp), contrast_number_buffer);
  return(val);
}


void set_contrast(snd_info *sp, mus_float_t val)
{
  if (!has_widgets(sp))
    sp->contrast_control = val;
  else XtVaSetValues(CONTRAST_SCROLLBAR(sp),
		     XmNvalue,
		     scroll_to_contrast(sp, contrast_to_scroll(sp->contrast_control_min, val, sp->contrast_control_max)),
		     NULL);
}


static void contrast_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmPushButtonCallbackStruct *cb = (XmPushButtonCallbackStruct *)info;
  snd_info *sp = (snd_info *)context;
  XButtonEvent *ev;
  ev = (XButtonEvent *)(cb->event);
  if (ev->state & (ControlMask | MetaMask))
    set_contrast(sp, sp->last_contrast_control);
  else set_contrast(sp, 0.0);
}


static void contrast_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  scroll_to_contrast((snd_info *)context, ((XmScrollBarCallbackStruct *)info)->value);
}


static void contrast_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScrollBarCallbackStruct *cb = (XmScrollBarCallbackStruct *)info;
  snd_info *sp = (snd_info *)context;
  scroll_to_contrast(sp, cb->value);
  sp->last_contrast_control = sp->saved_contrast_control;
  sp->saved_contrast_control = sp->contrast_control;
}


static void contrast_button_callback(Widget w, XtPointer context, XtPointer info) 
{
  snd_info *sp = (snd_info *)context;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  sp->contrast_control_on = cb->set;
  XmChangeColor(CONTRAST_SCROLLBAR(sp), (Pixel)((sp->contrast_control_on) ? (ss->position_color) : (ss->basic_color)));
}


void toggle_contrast_button(snd_info *sp, bool state)
{
  if (!has_widgets(sp))
    sp->contrast_control_on = state;
  else XmToggleButtonSetState(CONTRAST_RIGHT_BUTTON(sp), (Boolean)state, true);
}


/* ---------------- REVERB-CONTROL-SCALE ---------------- */

static int revscl_to_scroll(mus_float_t minval, mus_float_t val, mus_float_t maxval)
{
  if (val <= minval) return(0);
  if (val >= maxval) return((int)(0.9 * SCROLLBAR_MAX));
  return(snd_round(0.9 * SCROLLBAR_MAX * (pow(val, 0.333) - pow(minval, 0.333)) / (pow(maxval, 0.333) - pow(minval, 0.333))));
}


/* static mus_float_t cube(mus_float_t a) {return(a*a*a);} */


static int scroll_to_revscl(snd_info *sp, int val)
{
  char revscl_number_buffer[7];

  if (val <= 0) 
    sp->reverb_control_scale = sp->reverb_control_scale_min;
  else
    {
      if (val >= (0.9 * SCROLLBAR_MAX)) 
	sp->reverb_control_scale = sp->reverb_control_scale_max;
      else sp->reverb_control_scale = cube((val * (pow(sp->reverb_control_scale_max, 0.333) - pow(sp->reverb_control_scale_min, 0.333)) / 
					    (0.9 * SCROLLBAR_MAX)) + 
					   pow(sp->reverb_control_scale_min, 0.333));
    }

  snprintf(revscl_number_buffer, 7, "%.4f", sp->reverb_control_scale);
  set_label(REVSCL_LABEL(sp), revscl_number_buffer);
  return(val);
}


void set_revscl(snd_info *sp, mus_float_t val)
{
  if (!has_widgets(sp))
    sp->reverb_control_scale = val;
  else XtVaSetValues(REVSCL_SCROLLBAR(sp),
		     XmNvalue,
		     scroll_to_revscl(sp, revscl_to_scroll(sp->reverb_control_scale_min, val, sp->reverb_control_scale_max)),
		     NULL);
}


static void revscl_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmPushButtonCallbackStruct *cb = (XmPushButtonCallbackStruct *)info;
  snd_info *sp = (snd_info *)context;
  XButtonEvent *ev;
  ev = (XButtonEvent *)(cb->event);
  if (ev->state & (ControlMask | MetaMask))
    set_revscl(sp, sp->last_reverb_control_scale);
  else set_revscl(sp, 0.0);
}


static void revscl_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  scroll_to_revscl((snd_info *)context, ((XmScrollBarCallbackStruct *)info)->value);
}


static void revscl_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScrollBarCallbackStruct *cb = (XmScrollBarCallbackStruct *)info;
  snd_info *sp = (snd_info *)context;
  scroll_to_revscl(sp, cb->value);
  sp->last_reverb_control_scale = sp->saved_reverb_control_scale;
  sp->saved_reverb_control_scale = sp->reverb_control_scale;
}


/* ---------------- REVERB-CONTROL-LENGTH ---------------- */

static int revlen_to_scroll(mus_float_t minval, mus_float_t val, mus_float_t maxval)
{
  if (val <= minval) return(0);
  if (val >= maxval) return((int)(0.9 * SCROLLBAR_MAX));
  return(snd_round((val - minval) / (maxval - minval) * 0.9 * SCROLLBAR_MAX));
}


static int scroll_to_revlen(snd_info *sp, int val)
{
  char revlen_number_buffer[5];

  sp->reverb_control_length = sp->reverb_control_length_min + 
    (sp->reverb_control_length_max - sp->reverb_control_length_min) * (mus_float_t)val / (0.9 * SCROLLBAR_MAX);
  snprintf(revlen_number_buffer, 5, "%.2f", sp->reverb_control_length);
  set_label(REVLEN_LABEL(sp), revlen_number_buffer);
  return(val);
}


void set_revlen(snd_info *sp, mus_float_t val)
{
  if (!has_widgets(sp))
    sp->reverb_control_length = val;
  else XtVaSetValues(REVLEN_SCROLLBAR(sp),
		     XmNvalue,
		     scroll_to_revlen(sp, revlen_to_scroll(sp->reverb_control_length_min, val, sp->reverb_control_length_max)),
		     NULL);
}


static void revlen_click_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmPushButtonCallbackStruct *cb = (XmPushButtonCallbackStruct *)info;
  snd_info *sp = (snd_info *)context;
  XButtonEvent *ev;
  ev = (XButtonEvent *)(cb->event);
  if (ev->state & (ControlMask | MetaMask)) 
    set_revlen(sp, sp->last_reverb_control_length);
  else set_revlen(sp, 1.0);
}


static void revlen_drag_callback(Widget w, XtPointer context, XtPointer info) 
{
  scroll_to_revlen((snd_info *)context, ((XmScrollBarCallbackStruct *)info)->value);
}


static void revlen_valuechanged_callback(Widget w, XtPointer context, XtPointer info) 
{
  XmScrollBarCallbackStruct *cb = (XmScrollBarCallbackStruct *)info;
  snd_info *sp = (snd_info *)context;
  scroll_to_revlen(sp, cb->value);
  sp->last_reverb_control_length = sp->saved_reverb_control_length;
  sp->saved_reverb_control_length = sp->reverb_control_length;
}


static void reverb_button_callback(Widget w, XtPointer context, XtPointer info) 
{
  snd_info *sp = (snd_info *)context;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  sp->reverb_control_on = cb->set;
  XmChangeColor(REVLEN_SCROLLBAR(sp), (Pixel)((sp->reverb_control_on) ? (ss->position_color) : (ss->basic_color)));
  XmChangeColor(REVSCL_SCROLLBAR(sp), (Pixel)((sp->reverb_control_on) ? (ss->position_color) : (ss->basic_color)));
}


void toggle_reverb_button(snd_info *sp, bool state)
{
  if (!has_widgets(sp))
    sp->reverb_control_on = state;
  else XmToggleButtonSetState(REVERB_BUTTON(sp), (Boolean)state, true);
}


/* ---------------- FILTER_CONTROL ---------------- */

static void filter_button_callback(Widget w, XtPointer context, XtPointer info) 
{
  snd_info *sp = (snd_info *)context;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  sp->filter_control_on = cb->set;
}


void toggle_filter_button(snd_info *sp, bool state)
{
  if (!has_widgets(sp))
    sp->filter_control_on = state;
  else XmToggleButtonSetState(FILTER_BUTTON(sp), (Boolean)state, true);
}


static void filter_textfield_deactivate(snd_info *sp)
{
  chan_info *active_chan;
  active_chan = any_selected_channel(sp);
  if (active_chan)
    goto_window(channel_graph(active_chan));
}


#define MIN_FILTER_GRAPH_HEIGHT 20

void display_filter_env(snd_info *sp)
{
  graphics_context *ax;
  int height, width;
  Widget drawer;
  env_editor *edp;

  if (!(snd_ok(sp))) return; /* autotest close + lagging X updates */

  edp = sp->flt;
  drawer = FILTER_GRAPH(sp);
  height = widget_height(drawer);
  if (height < MIN_FILTER_GRAPH_HEIGHT) return;

  width = widget_width(drawer);
  ax = (graphics_context *)calloc(1, sizeof(graphics_context));
  ax->gc = ss->fltenv_basic_gc;
  ax->wn = XtWindow(drawer);
  ax->dp = XtDisplay(drawer);

  XClearWindow(ax->dp, ax->wn);
  edp->in_dB = sp->filter_control_in_dB;
  edp->with_dots = true;

  if (sp->filter_control_in_hz)
    sp->filter_control_xmax = (mus_float_t)(snd_srate(sp) / 2);
  else sp->filter_control_xmax = 1.0;

  if (!sp->filter_control_envelope) 
    sp->filter_control_envelope = default_env(sp->filter_control_xmax, 1.0);

  env_editor_display_env(edp, sp->filter_control_envelope, ax, "frequency response", 0, 0, width, height, NOT_PRINTING);
  if (edp->edited)
    {
      ax->gc = ss->fltenv_data_gc;
      display_frequency_response(sp->filter_control_envelope, 
				 (sound_env_editor(sp))->axis, ax, 
				 sp->filter_control_order, 
				 sp->filter_control_in_dB);
    }
  free(ax);
}


void set_filter_text(snd_info *sp, const char *str)
{
  if (has_widgets(sp))
    XmTextSetString(FILTER_COEFFS_TEXT(sp), (char *)str);
}


static void filter_drawer_button_motion(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  snd_info *sp = (snd_info *)context;
  XMotionEvent *ev = (XMotionEvent *)event;
  env_editor *edp;
#ifdef __APPLE__
  if ((press_x == ev->x) && (press_y == ev->y)) return;
#endif
  edp = sp->flt;
  edp->in_dB = sp->filter_control_in_dB;
  env_editor_button_motion(edp, ev->x, ev->y, ev->time, sp->filter_control_envelope);
  display_filter_env(sp);
  sp->filter_control_changed = true;
}


static void filter_drawer_button_press(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  snd_info *sp = (snd_info *)context;
  XButtonEvent *ev = (XButtonEvent *)event;
  env_editor *edp;
  if (!(sp->filter_control_envelope)) return;
#ifdef __APPLE__
  press_x = ev->x;
  press_y = ev->y;
#endif
  edp = sp->flt;
  edp->in_dB = sp->filter_control_in_dB;
  if (env_editor_button_press(edp, ev->x, ev->y, ev->time, sp->filter_control_envelope))
    display_filter_env(sp);
}


static void filter_drawer_button_release(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  char *tmpstr = NULL;
  snd_info *sp = (snd_info *)context;
  env_editor_button_release(sound_env_editor(sp), sp->filter_control_envelope);
  display_filter_env(sp);
  set_filter_text(sp, tmpstr = env_to_string(sp->filter_control_envelope));
  if (tmpstr) free(tmpstr);
  sp->filter_control_changed = true;
}


static void filter_drawer_resize(Widget w, XtPointer context, XtPointer info) 
{
  snd_info *sp = (snd_info *)context;
  display_filter_env(sp);
}


static void filter_dB_callback(Widget w, XtPointer context, XtPointer info) 
{
  snd_info *sp = (snd_info *)context;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  sp->filter_control_in_dB = (cb->set);
  display_filter_env(sp);
}


void set_filter_in_dB(snd_info *sp, bool val)
{
  sp->filter_control_in_dB = val;
  if (has_widgets(sp))
    {
      XmToggleButtonSetState(FILTER_DB_BUTTON(sp), (Boolean)val, false);
      display_filter_env(sp);
    }
}


static void new_in_hz(snd_info *sp, bool val)
{
  sp->filter_control_in_hz = val;
  if (val)
    sp->filter_control_xmax = (mus_float_t)(snd_srate(sp) / 2);
  else sp->filter_control_xmax = 1.0;
  if (sp->filter_control_envelope) free_env(sp->filter_control_envelope);
  sp->filter_control_envelope = default_env(sp->filter_control_xmax, 1.0);
}


static void filter_hz_callback(Widget w, XtPointer context, XtPointer info) 
{
  snd_info *sp = (snd_info *)context;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  new_in_hz(sp, cb->set);
  display_filter_env(sp);
}


void set_filter_in_hz(snd_info *sp, bool val)
{
  new_in_hz(sp, val);
  if (has_widgets(sp))
    {
      XmToggleButtonSetState(FILTER_HZ_BUTTON(sp), (Boolean)val, false);
      display_filter_env(sp);
    }
}


void set_filter_order(snd_info *sp, int order)
{
  if (order & 1) order++;
  if (order <= 0) order = 2;
  sp->filter_control_order = order;
  if (has_widgets(sp))
    {
      widget_int_to_text(FILTER_ORDER_TEXT(sp), order);
      display_filter_env(sp);
    }
  sp->filter_control_changed = true;
}


static void filter_order_up_callback(Widget w, XtPointer context, XtPointer info)
{
  snd_info *sp = (snd_info *)context;
  set_filter_order(sp, sp->filter_control_order + 2);
}


static void filter_order_down_callback(Widget w, XtPointer context, XtPointer info)
{
  snd_info *sp = (snd_info *)context;
  if (sp->filter_control_order > 2)
    set_filter_order(sp, sp->filter_control_order - 2);
}


static void get_filter_order(snd_info *sp, char *str)
{
  int order;
  redirect_errors_to(errors_to_status_area, (void *)sp);
  order = string_to_int(str, 1, "filter order");
  redirect_errors_to(NULL, NULL);
  if (order & 1) order++;
  if (order <= 0) order = 2;
  sp->filter_control_order = order;
}


static void filter_activate_callback(Widget w, XtPointer context, XtPointer info)
{
  /* make an envelope out of the data */
  snd_info *sp = (snd_info *)context;
  char *str = NULL;

  str = XmTextGetString(w);
  if (sp->filter_control_envelope) sp->filter_control_envelope = free_env(sp->filter_control_envelope);
  redirect_errors_to(errors_to_status_area, (void *)sp);
  sp->filter_control_envelope = string_to_env((const char *)str);
  redirect_errors_to(NULL, NULL);
  if (str) XtFree(str);

  if (!(sp->filter_control_envelope)) /* maybe user cleared text field? */
    sp->filter_control_envelope = default_env(sp->filter_control_xmax, 1.0);

  str = XmTextGetString(FILTER_ORDER_TEXT(sp));
  if ((str) && (*str))
    {
      get_filter_order(sp, str);
      XtFree(str);
    }
  (sound_env_editor(sp))->edited = true;
  display_filter_env(sp);
  filter_textfield_deactivate(sp);
  sp->filter_control_changed = true;
}


static void filter_order_activate_callback(Widget w, XtPointer context, XtPointer info)
{
  char *str;
  snd_info *sp = (snd_info *)context;
  str = XmTextGetString(w);
  if ((str) && (*str))
    {
      get_filter_order(sp, str);
      sp->filter_control_changed = true;
      display_filter_env(sp);
      XtFree(str);
    }
  filter_textfield_deactivate(sp);
}


void filter_env_changed(snd_info *sp, env *e)
{
  /* turn e back into a string for textfield widget */
  if (has_widgets(sp))
    {
      char *tmpstr = NULL;
      XmTextSetString(FILTER_COEFFS_TEXT(sp), tmpstr = env_to_string(e));
      if (tmpstr) free(tmpstr);
      (sound_env_editor(sp))->edited = true;
      display_filter_env(sp);
    }
  sp->filter_control_changed = true;
}


/* ---------------- PLAY BUTTON ---------------- */

void set_play_button(snd_info *sp, bool val)
{
#if WITH_AUDIO
  if (has_widgets(sp))
    XmToggleButtonSetState(PLAY_BUTTON(sp), (Boolean)val, false);
  set_open_file_play_button(val);
#endif
}


#if WITH_AUDIO
static void play_button_callback(Widget w, XtPointer context, XtPointer info)
{
  snd_info *sp = (snd_info *)context;
  chan_info *cp;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  XButtonEvent *ev;

  ev = (XButtonEvent *)(cb->event);

  if (sp->playing) 
    stop_playing_sound(sp, PLAY_BUTTON_UNSET);

  ss->tracking = ((with_tracking_cursor(ss) != DONT_TRACK) ||
		  ((cb->set) && 
		   (ev->state & (ControlMask | MetaMask))));

  cp = any_selected_channel(sp);
  goto_graph(cp);
  if (cb->set) 
    {
      XtVaSetValues(w, XmNselectColor, (ss->tracking) ? ss->green : ss->selection_color, NULL);
      play_sound(sp, 0, NO_END_SPECIFIED);
    }
}
#endif


typedef struct {bool pausing; } pause_data;

#if WITH_AUDIO
static void set_play_button_pause(snd_info *sp, void *ptr)
{
  if ((sp->playing) && (has_widgets(sp)))
    {
      pause_data *pd = (pause_data *)ptr;
      Widget w;
      w = PLAY_BUTTON(sp);
      if (pd->pausing)
	XtVaSetValues(w, XmNselectColor, ss->red, NULL);
      else XtVaSetValues(w, XmNselectColor, (ss->tracking) ? ss->green : ss->selection_color, NULL);
    }
}
#endif


void play_button_pause(bool pausing)
{
#if WITH_AUDIO
  pause_data *pd;
  pd = (pause_data *)calloc(1, sizeof(pause_data));
  pd->pausing = pausing;
  for_each_sound_with_void(set_play_button_pause, (void *)pd);
  free(pd);
#endif
}


void set_control_panel_play_button(snd_info *sp)
{
#if WITH_AUDIO
  if (has_widgets(sp))
    {
      set_toggle_button(PLAY_BUTTON(sp), false, false, sp);
      XtVaSetValues(PLAY_BUTTON(sp), XmNselectColor, ss->selection_color, NULL);
    }
#endif
}


static void play_arrow_callback(Widget w, XtPointer context, XtPointer info)
{
#if WITH_AUDIO
  snd_info *sp = (snd_info *)context;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  bool dir;
  dir = (bool)(cb->set);
  if (dir) sp->speed_control_direction = -1; else sp->speed_control_direction = 1;
#endif
}


/* ---------------- SYNC BUTTON ---------------- */

static void set_sync_color(snd_info *sp)
{
  Widget syb;
  syb = SYNC_BUTTON(sp);
  switch (sp->sync)
    {
    case 1: case 0: XtVaSetValues(syb, XmNselectColor, ss->selection_color, NULL); break;
    case 2:         XtVaSetValues(syb, XmNselectColor, ss->green, NULL);               break;
    case 3:         XtVaSetValues(syb, XmNselectColor, ss->yellow, NULL);              break;
    case 4:         XtVaSetValues(syb, XmNselectColor, ss->red, NULL);                 break;
    default:        XtVaSetValues(syb, XmNselectColor, ss->black, NULL);               break;
    }
}


void syncb(snd_info *sp, int on)
{
  sp->sync = on;
  if (on > ss->sound_sync_max) ss->sound_sync_max = on;
  if (has_widgets(sp))
    {
      set_sync_color(sp);
      XmToggleButtonSetState(SYNC_BUTTON(sp), (on != 0), false); /* need actual bool here, not a cast! */
    }
}


static void sync_button_callback(Widget w, XtPointer context, XtPointer info)
{
  snd_info *sp = (snd_info *)context;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  XButtonEvent *ev;


  ev = (XButtonEvent *)(cb->event);
  if (cb->set)
    if (ev->state & ControlMask) 
      if (ev->state & MetaMask)
	if (ev->state & ShiftMask)
	  sp->sync = 4;
	else sp->sync = 3;
      else sp->sync = 2;
    else sp->sync = 1;
  else sp->sync = 0;

  set_sync_color(sp);

  if (sp->sync != 0) 
    {
      chan_info *cp;
      if (sp->sync > ss->sound_sync_max) ss->sound_sync_max = sp->sync;
      cp = sp->lacp;
      if (!cp) cp = any_selected_channel(sp);
      goto_graph(cp);
      if (cp->cursor_on) sync_cursors(cp, cursor_sample(cp));
      apply_x_axis_change(cp);
    }
}


/* ---------------- UNITE BUTTON ---------------- */

static void unite_button_callback(Widget w, XtPointer context, XtPointer info)
{
  /* click if set unsets, click if unset->combine, ctrl-click->superimpose */
  snd_info *sp = (snd_info *)context;
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)info;
  XButtonEvent *ev;
  channel_style_t val;
  ev = (XButtonEvent *)(cb->event);

  if (cb->set)
    {
      if (ev->state & (ControlMask | MetaMask)) 
	val = CHANNELS_SUPERIMPOSED;
      else val = CHANNELS_COMBINED;
    }
  else val = CHANNELS_SEPARATE;

  set_sound_channel_style(sp, val);
}


/* ---------------- CLOSE BUTTON ---------------- */

static void close_button_callback(Widget w, XtPointer context, XtPointer info) 
{
  snd_close_file((snd_info *)context);
}



/* apply is only safe if the DAC is currently inactive and remains safe only
 * if all other apply buttons are locked out (and play).
 */

/* relative panes needs to notice overall window resize, but there's no way to do so in Motif, as far as I can tell */

#if WITH_RELATIVE_PANES
/* It would be nice if we could set a paned window to keep its children relative
 *   amounts the same upon outside resize, but the Paned Window widget doesn't
 *   have a resize callback, and no obvious way to advise the resize mechanism.
 *   An attempt to get the same effect by wrapping w_pane in a drawingarea widget
 *   ran into other troubles (the thing is seriously confused about its size).
 *   You'd naively think the Actions "Start" and "Commit" could be used, since
 *   XtActions are said to be a list of XtActionProcs, but I can't find a way to add
 *   my action without deactivating the built-in action of the same name --
 *   XtAugmentTranslations ignores new actions if the old exists, XtOverride
 *   replaces the old, etc. (And XtActions involve far more complexity than
 *   anyone should have to endure).
 *
 * so... drop down into the sashes...(using undocumented stuff throughout this code)
 */

static void sash_lock_control_panel(snd_info *sp)
{
  if (showing_controls(sp))
    {
      /* lock to its current size */
      int hgt;
      hgt = control_panel_height(sp);
      XtVaSetValues(CONTROLS(sp),
		    XmNpaneMinimum, hgt,
		    XmNpaneMaximum, hgt,
		    NULL);
    }
}


static void sash_unlock_control_panel(snd_info *sp)
{
  if (showing_controls(sp))
    {
      XtVaSetValues(CONTROLS(sp),
		    XmNpaneMinimum, 1,
		    XmNpaneMaximum, LOTSA_PIXELS, 
		    NULL);
    }
}


static int outer_panes = 0;
static int *inner_panes = NULL;
static Dimension *outer_sizes = NULL;
static Dimension **inner_sizes = NULL;

static void watch_sash(Widget w, XtPointer closure, XtPointer info)
{
  SashCallData call_data = (SashCallData)info;
  /* call_data->params[0]: Commit, Move, Key, Start (as strings) */

  if ((call_data->params) && 
      (call_data->params[0]) && 
      (with_relative_panes(ss)) &&
      (sound_style(ss) == SOUNDS_VERTICAL))
    {
      int i, k;
      snd_info *sp;
      if (mus_strcmp(call_data->params[0], "Start"))
	{
	  for (i = 0; i < ss->max_sounds; i++)
	    {
	      sp = ss->sounds[i];
	      if ((sp) &&
		  (sp->inuse == SOUND_NORMAL) &&
		  (sp->nchans > 1) &&
		  (sp->channel_style == CHANNELS_SEPARATE))
		outer_panes++;
	    }

	  for_each_sound(sash_lock_control_panel);

	  if (outer_panes > 0)
	    {
	      int outer_ctr;
	      inner_panes = (int *)calloc(outer_panes, sizeof(int));
	      outer_sizes = (Dimension *)calloc(outer_panes, sizeof(Dimension));
	      inner_sizes = (Dimension **)calloc(outer_panes, sizeof(Dimension *));
	      outer_ctr = 0;

	      for (i = 0; i < ss->max_sounds; i++)
		{
		  sp = ss->sounds[i];
		  if ((sp) &&
		      (sp->inuse == SOUND_NORMAL) &&
		      (sp->nchans > 1) &&
		      (sp->channel_style == CHANNELS_SEPARATE))
		    {
		      Widget child;
		      child = SND_PANE(sp);
		      inner_panes[outer_ctr] = sp->nchans;
		      inner_sizes[outer_ctr] = (Dimension *)calloc(sp->nchans, sizeof(Dimension));
		      XtVaGetValues(child, XmNheight, &(outer_sizes[outer_ctr]), NULL);

		      for (k = 0; k < (int)sp->nchans; k++)
			XtVaGetValues(channel_main_pane(sp->chans[k]), XmNheight, &(inner_sizes[outer_ctr][k]), NULL);

		      outer_ctr++;
		      if (outer_ctr >= outer_panes) break;
		    }
		}
	    }
	}
      else
	{
	  if (mus_strcmp(call_data->params[0], "Commit")) /* release sash */
	    {
	      if (outer_panes > 0)
		{
		  int outer_ctr = 0;
		  Dimension cur_outer_size = 0;
		  
		  for (i = 0; i < ss->max_sounds; i++)
		    {
		      sp = ss->sounds[i];
		      if ((sp) &&
			  (sp->inuse == SOUND_NORMAL) &&
			  (sp->nchans > 1) &&
			  (sp->channel_style == CHANNELS_SEPARATE))
			{
			  XtVaGetValues(SND_PANE(sp), XmNheight, &cur_outer_size, NULL);
			  
			  if ((cur_outer_size > 40) && 
			      (abs(cur_outer_size - outer_sizes[outer_ctr]) > (int)(sp->nchans * 2)))
			    {
			      /* this pane has multiple chans and its size has changed enough to matter */
			      Dimension total_inner = 0, diff;
			      double ratio;
			      
			      for (k = 0; k < (int)sp->nchans; k++)
				total_inner += inner_sizes[outer_ctr][k];
			      diff = outer_sizes[outer_ctr] - total_inner; /* this is non-channel stuff */
			      
			      for (k = 0; k < (int)sp->nchans; k++)
				XtUnmanageChild(channel_main_pane(sp->chans[k]));
			      
			      ratio = (double)(cur_outer_size - diff) / (double)(outer_sizes[outer_ctr] - diff);
			      if (ratio > 0.0)
				{
				  for (k = 0; k < (int)sp->nchans; k++)
				    {
				      int size;
				      size = (int)(ratio * inner_sizes[outer_ctr][k]);
				      XtVaSetValues(channel_main_pane(sp->chans[k]), 
						    XmNpaneMinimum, size - 1,
						    XmNpaneMaximum, size + 1, 
						    NULL);
				    }
				  for (k = 0; k < (int)sp->nchans; k++)
				    XtManageChild(channel_main_pane(sp->chans[k]));
				  for (k = 0; k < (int)sp->nchans; k++)
				    XtVaSetValues(channel_main_pane(sp->chans[k]), 
						  XmNpaneMinimum, 1,
						  XmNpaneMaximum, LOTSA_PIXELS, 
						  NULL);
				}
			    }
			  outer_ctr++;
			}
		    }

		  for (i = 0; i < outer_panes; i++)
		    if (inner_sizes[i])
		      free(inner_sizes[i]);
		  free(inner_panes);
		  free(inner_sizes);
		  free(outer_sizes);
		  outer_panes = 0;
		}

	      for_each_sound(sash_unlock_control_panel);
	    }
	}
    }
}

static Widget *sashes = NULL;
static int sashes_size = 0;

static void remember_sash(Widget w)
{
  /* add callback only once (means remembering which widgets already have our callback */
  int loc = -1;
  if (sashes_size == 0)
    {
      sashes = (Widget *)calloc(16, sizeof(Widget));
      sashes_size = 16;
      loc = 0;
    }
  else
    {
      int i;
      for (i = 0; i < sashes_size; i++)
	{
	  if (sashes[i] == w) return;
	  if (!sashes[i])
	    {
	      loc = i;
	      break;
	    }
	}
      if (loc == -1)
	{
	  sashes = (Widget *)realloc(sashes, sashes_size * 2 * sizeof(Widget));
	  for (i = sashes_size; i < sashes_size * 2; i++) sashes[i] = NULL;
	  loc = sashes_size;
	  sashes_size *= 2;
	}
    }
  sashes[loc] = w;
  XtAddCallback(w, XmNcallback, watch_sash, NULL);
}


static void add_sash_watchers(Widget w)
{
  /* if relative panes, add sash watchers to the outer paned window sashes (sound_pane(ss)) */
  uint32_t i;
  CompositeWidget cw = (CompositeWidget)w;
  for (i = 0; i < cw->composite.num_children; i++) /* only outermost sashes count here */
    {
      Widget child;
      child = cw->composite.children[i];
      if ((XtIsWidget(child)) && 
	  (XtIsManaged(child)) && 
	  (XtIsSubclass(child, xmSashWidgetClass)))
	remember_sash(child);
    }
}

#endif


static bool cant_write(char *name)
{
#ifndef _MSC_VER
  return((access(name, W_OK)) != 0);
#else
  return(false);
#endif
}


/* bitmaps for the playback direction arrow */

static unsigned char speed_r_bits1[] = {
   0x00, 0x04, 0x10, 0x08, 0x00, 0x10, 0x04, 0x20, 0x00, 0x40, 0xa5, 0xbf,
   0x00, 0x40, 0x04, 0x20, 0x00, 0x10, 0x10, 0x08, 0x00, 0x04, 0x00, 0x00};
static unsigned char speed_l_bits1[] = {
   0x20, 0x00, 0x10, 0x08, 0x08, 0x00, 0x04, 0x20, 0x02, 0x00, 0xfd, 0xa5,
   0x02, 0x00, 0x04, 0x20, 0x08, 0x00, 0x10, 0x08, 0x20, 0x00, 0x00, 0x00};

#define NUM_BOMBS 15

static Pixmap mini_lock = 0;
static Pixmap close_icon = 0;
static Pixmap blank_pixmap = 0;
static bool mini_lock_allocated = false;
static Pixmap bombs[NUM_BOMBS];
static Pixmap hourglasses[NUM_HOURGLASSES];
static Pixmap stop_sign = 0;

void show_lock(snd_info *sp)
{
  if (!has_widgets(sp)) return;
  if (mini_lock)
    XtVaSetValues(LOCK_OR_BOMB(sp), XmNlabelPixmap, mini_lock, NULL);
}


void hide_lock(snd_info *sp)
{
  if (!has_widgets(sp)) return;
  if (mini_lock)
    XtVaSetValues(LOCK_OR_BOMB(sp), XmNlabelPixmap, blank_pixmap, NULL);
  /* these Pixmaps can be null if the colormap is screwed up */
}


static void show_stop_sign(snd_info *sp)
{
  if (!has_widgets(sp)) return;
  if (stop_sign)
    XtVaSetValues(STOP_ICON(sp), XmNlabelPixmap, stop_sign, NULL);
}


static void hide_stop_sign(snd_info *sp)
{
  if (!has_widgets(sp)) return;
  if (blank_pixmap)
    XtVaSetValues(STOP_ICON(sp), XmNlabelPixmap, blank_pixmap, NULL);
}


static void show_bomb(snd_info *sp)
{
  if (!has_widgets(sp)) return;
  if (sp->bomb_ctr >= NUM_BOMBS) 
    sp->bomb_ctr = 0;
  if (bombs[sp->bomb_ctr])
    XtVaSetValues(LOCK_OR_BOMB(sp), XmNlabelPixmap, bombs[sp->bomb_ctr], NULL);
  sp->bomb_ctr++; 
}


static void hide_bomb(snd_info *sp)
{
  if (!has_widgets(sp)) return;
  XtVaSetValues(LOCK_OR_BOMB(sp), XmNlabelPixmap, blank_pixmap, NULL);
  sp->bomb_ctr = 0;
}


#define BOMB_TIME 200

static void tick_bomb(XtPointer context, XtIntervalId *id)
{
  snd_info *sp = (snd_info *)context;
  if (!has_widgets(sp)) return;
  if ((sp->need_update) || (sp->file_unreadable))
    {
      show_bomb(sp);
      XtAppAddTimeOut(main_app(ss),
		      (unsigned long)BOMB_TIME,
		      (XtTimerCallbackProc)tick_bomb,
		      context);
    }
  else 
    {
      hide_bomb(sp);
      sp->bomb_in_progress = false;
    }
}


void start_bomb(snd_info *sp)
{
  if (!has_widgets(sp)) return;
  sp->bomb_ctr = 0;
  if (!(sp->bomb_in_progress))
    {
      sp->bomb_in_progress = true;
      XtAppAddTimeOut(main_app(ss),
		      (unsigned long)BOMB_TIME,
		      (XtTimerCallbackProc)tick_bomb,
		      (void *)sp);
    }
}


void stop_bomb(snd_info *sp)
{
  if (!has_widgets(sp)) return;
  hide_bomb(sp);
  sp->bomb_in_progress = false;
}


static char *bits_to_string(const char **icon)
{
  /* show first few lines */
  char *buf;
  buf = (char *)calloc(128, sizeof(char));
  snprintf(buf, 128, "\n%s\n%s\n%s...", icon[0], icon[1], icon[2]);
  return(buf);
}


static void allocate_icons(Widget w)
{ 
  Pixmap shape1, shape2, shape3, shape4; 
  XpmAttributes attributes; 
  XpmColorSymbol symbols[1];
  int scr, k, pixerr;
  Display *dp;
  Drawable wn;

  dp = XtDisplay(w);
  wn = XtWindow(w);
  scr = DefaultScreen(dp);
  XtVaGetValues(w, XmNdepth, &attributes.depth, XmNcolormap, &attributes.colormap, NULL);
  attributes.visual = DefaultVisual(dp, scr);
  symbols[0].name = (char *)"basiccolor";
  symbols[0].value = NULL;
  symbols[0].pixel = ss->basic_color;
  attributes.colorsymbols = symbols;
  attributes.numsymbols = 1;
  attributes.valuemask = XpmColorSymbols | XpmDepth | XpmColormap | XpmVisual;

  pixerr = XpmCreatePixmapFromData(dp, wn, (char **)mini_lock_bits(), &mini_lock, &shape1, &attributes);
  if (pixerr != XpmSuccess)
    snd_error("lock pixmap trouble: %s from %s\n", XpmGetErrorString(pixerr), bits_to_string(mini_lock_bits()));

  pixerr = XpmCreatePixmapFromData(dp, wn, (char **)blank_bits(), &blank_pixmap, &shape1, &attributes);
  if (pixerr != XpmSuccess) 
    snd_error("blank pixmap trouble: %s from %s\n", XpmGetErrorString(pixerr), bits_to_string(blank_bits()));

  pixerr = XpmCreatePixmapFromData(dp, wn, (char **)stop_sign_bits(), &stop_sign, &shape4, &attributes);
  if (pixerr != XpmSuccess) 
    snd_error("stop sign pixmap trouble: %s from %s\n", XpmGetErrorString(pixerr), bits_to_string(stop_sign_bits()));

  pixerr = XpmCreatePixmapFromData(dp, wn, (char **)close_icon_bits(), &close_icon, &shape1, &attributes);
  if (pixerr != XpmSuccess) 
    snd_error("stop sign pixmap trouble: %s from %s\n", XpmGetErrorString(pixerr), bits_to_string(close_icon_bits()));

  for (k = 0; k < NUM_BOMBS; k++)
    {
      pixerr = XpmCreatePixmapFromData(dp, wn, (char **)mini_bomb_bits(k), &(bombs[k]), &shape2, &attributes);
      if (pixerr != XpmSuccess) 
	{
	  snd_error("bomb pixmap trouble: %s from %s\n", XpmGetErrorString(pixerr), bits_to_string(mini_bomb_bits(k)));
	  break;
	}
      pixerr = XpmCreatePixmapFromData(dp, wn, (char **)mini_glass_bits(k), &(hourglasses[k]), &shape3, &attributes);
      /* NUM_HOURGLASSES == NUM_BOMBS so this is safe */
      if (pixerr != XpmSuccess) 
	{
	  snd_error("glass pixmap trouble: %s from %s\n", XpmGetErrorString(pixerr), bits_to_string(mini_glass_bits(k))); 
	  break;
	}
    }
  mini_lock_allocated = true;
}


static void change_pixmap_background(Widget w, Pixmap orig, Pixel old_color, Pixel new_color, int width, int height)
{
  XImage *before;
  Display *dp;
  Drawable wn;
  Visual *vis;
  XGCValues v;
  GC draw_gc;
  int depth, depth_bytes, x, y;
  char *data;

  dp = XtDisplay(w);
  wn = XtWindow(w);
  vis = DefaultVisual(dp, DefaultScreen(dp));
  XtVaGetValues(w, XmNdepth, &depth, NULL);
  depth_bytes = (depth >> 3);

  data = (char *)calloc((width + 1) * (height + 1) * depth_bytes, sizeof(char)); /* not calloc since X will free this */
  /* there's overflow in X here, apparently -- the +1's fix it according to valgrind */
  /*   perhaps this is supposed to be rounded up to byte boundaries? */

  before = XCreateImage(dp, vis, depth, XYPixmap, 0, data, width, height, 8, 0);
  XGetSubImage(dp, orig, 0, 0, width, height, AllPlanes, XYPixmap, before, 0, 0);

  v.background = new_color;
  draw_gc = XCreateGC(dp, wn, GCBackground, &v);
  XSetBackground(dp, draw_gc, new_color); 

  for (x = 0; x < width; x++) 
    for (y = 0; y < height; y++) 
      if (XGetPixel(before, x, y) == old_color)
	XPutPixel(before, x, y, new_color);

  XPutImage(dp, orig, draw_gc, before, 0, 0, 0, 0, width, height);
  XDestroyImage(before);  /* frees data as well */
  XFreeGC(dp, draw_gc);
}


void make_sound_icons_transparent_again(Pixel old_color, Pixel new_color)
{
  int i;
  if (!mini_lock_allocated) allocate_icons(main_shell(ss));
  change_pixmap_background(main_shell(ss), mini_lock, old_color, new_color, 16, 14);
  change_pixmap_background(main_shell(ss), blank_pixmap, old_color, new_color, 16, 14);
  change_pixmap_background(main_shell(ss), close_icon, old_color, new_color, 16, 14);
  /* change_pixmap_background(main_shell(ss), stop_sign, old_color, new_color, 17, 17); */
  /* memory corruption here! */
  for (i = 0; i < NUM_BOMBS; i++)
    change_pixmap_background(main_shell(ss), bombs[i], old_color, new_color, 16, 14);
  for (i = 0; i < NUM_HOURGLASSES; i++)
    change_pixmap_background(main_shell(ss), hourglasses[i], old_color, new_color, 16, 14);
}


static Pixmap spd_r, spd_l;
static bool spd_ok = false;

static void close_sound_dialog(Widget w, XtPointer context, XtPointer info) 
{
  snd_info *sp = (snd_info *)context;
  if (sp) snd_close_file(sp);
}


static void manage_sync_button(snd_info *sp)
{
  XtManageChild(SYNC_BUTTON(sp));
}


static void attach_status_area(snd_info *sp)
{
  XtUnmanageChild(STATUS_AREA(sp));
  XtVaSetValues(STATUS_AREA(sp),
		XmNrightAttachment, XmATTACH_WIDGET,
		XmNrightWidget, (XtIsManaged(UNITE_BUTTON(sp))) ? UNITE_BUTTON(sp) : ((XtIsManaged(SYNC_BUTTON(sp))) ? SYNC_BUTTON(sp) : PLAY_BUTTON(sp)),
		NULL);
  XtManageChild(STATUS_AREA(sp));
}


snd_info *add_sound_window(char *filename, read_only_t read_only, file_info *hdr)
{  
  snd_info *sp = NULL, *osp;
  Widget *sw;
  XmString s1;
  int snd_slot, nchans = 1, i, old_chans;
  bool make_widgets;
  Arg args[32];
  char *old_name = NULL, *title;
  Dimension app_dy, screen_y, chan_min_y;
  Position app_y;
  /* these dimensions are used to try to get a reasonable channel graph size without falling off the screen bottom */
  Pixmap rb, lb;
  int depth;
  bool free_filename = false;
  Widget form;
  XtCallbackList n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12;
  Atom sound_delete;

  if (ss->translated_filename) 
    {
      old_name = filename;
      filename = ss->translated_filename;
      free_filename = true;
      ss->translated_filename = NULL;
    }

  nchans = hdr->chans;
  if (nchans <= 0) nchans = 1;
  XtVaGetValues(main_shell(ss),
		XmNy, &app_y,
		XmNheight, &app_dy,
		NULL);
  screen_y = DisplayHeight(main_display(ss),
			   DefaultScreen(main_display(ss)));
  app_dy = (screen_y - app_y - app_dy - 20 * nchans);
  chan_min_y = (Dimension)(app_dy / (Dimension)nchans);
  if (chan_min_y > (Dimension)(ss->channel_min_height)) 
    chan_min_y = ss->channel_min_height; 
  else 
    if (chan_min_y < 5) 
      chan_min_y = 5;

  snd_slot = find_free_sound_slot(nchans); /* expands sound list if needed */
  if (ss->sounds[snd_slot]) /* we're trying to re-use an old, inactive set of widgets and whatnot */
    {
      osp = ss->sounds[snd_slot];
      old_chans = osp->allocated_chans;
    }
  else old_chans = 0;
  make_widgets = (!ss->sounds[snd_slot]);
  ss->sounds[snd_slot] = make_snd_info(ss->sounds[snd_slot], filename, hdr, snd_slot, read_only);
  sp = ss->sounds[snd_slot];
  sp->inuse = SOUND_NORMAL;
  sp->bomb_ctr = 0;
  sp->write_date = file_write_date(filename); /* needed early in this process by the peak-env handlers */

  if (!sp->widgets) 
    sp->widgets = (Widget *)calloc(SP_NUM_WIDGETS, sizeof(Widget));
  sw = sp->widgets;

  if ((!make_widgets) && (old_chans < nchans))
    {
      for (i = old_chans; i < nchans; i++) 
	add_channel_window(sp, i, chan_min_y, 1, NULL, WITH_FW_BUTTONS, WITH_EVENTS);
    }

  if (make_widgets)
    {
      int n;
      if (sound_style(ss) == SOUNDS_IN_SEPARATE_WINDOWS)
	{
	  title = (char *)calloc(PRINT_BUFFER_SIZE, sizeof(char));
	  snprintf(title, PRINT_BUFFER_SIZE, "%d: %s", snd_slot, sp->short_filename);
	  if (!sp->dialog)
	    {
	      n = 0;
	      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
	      XtSetArg(args[n], XmNautoUnmanage, false); n++;
	      XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
	      XtSetArg(args[n], XmNnoResize, false); n++;
	      XtSetArg(args[n], XmNtransient, false); n++;
	      sp->dialog = XtCreatePopupShell(title, xmDialogShellWidgetClass, main_shell(ss), args, n);
	      /* using popup shell here gets around the problem that the shell passes resize requests to all its children
	       * -- as a popup, it's not considered a child, but that means we don't inherit things like popup menus from 
	       * the main shell.
	       */
	      sound_delete = XmInternAtom(XtDisplay(sp->dialog), (char *)"WM_DELETE_WINDOW", false);
	      XmAddWMProtocolCallback(sp->dialog, sound_delete, close_sound_dialog, (XtPointer)sp);
	    }
	  else XtVaSetValues(sp->dialog, XmNtitle, title, NULL);
	  free(title);
	  if (!XtIsManaged(sp->dialog)) XtManageChild(sp->dialog);
	}

      n = 0;      
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      n = attach_all_sides(args, n);
      XtSetArg(args[n], XmNallowResize, true); n++;
      XtSetArg(args[n], XmNsashIndent, ss->channel_sash_indent); n++;
      XtSetArg(args[n], XmNpaneMaximum, LOTSA_PIXELS); n++; /* Xm/Paned.c initializes this to 1000! */
      if (ss->channel_sash_size != 0)
	{
	  XtSetArg(args[n], XmNsashHeight, ss->channel_sash_size); n++;
	  XtSetArg(args[n], XmNsashWidth, ss->channel_sash_size); n++;
	}

      /* if (mumble_style(ss) == CHANNELS_HORIZONTAL) {XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;} */
      /* this doesn't work yet because the control panel is screwed up when trying to display itself horizontally */
      /* Perhaps another layer of panes? */

      if (sound_style(ss) == SOUNDS_VERTICAL)
	{
	  if (ss->toolbar)
	    XtSetArg(args[n], XmNpositionIndex, snd_slot + 1);
	  else XtSetArg(args[n], XmNpositionIndex, snd_slot);
	  n++;
	}
      XtSetArg(args[n], XmNuserData, sp->index); n++;

      if (sound_style(ss) == SOUNDS_IN_SEPARATE_WINDOWS)
	SND_PANE(sp) = XtCreateManagedWidget("snd-pane", xmPanedWindowWidgetClass, sp->dialog, args, n);
      else 
	{
	  uint32_t i;
	  CompositeWidget cw = (CompositeWidget)sound_pane(ss);
	  SND_PANE(sp) = XtCreateManagedWidget("snd-pane", xmPanedWindowWidgetClass, sound_pane(ss), args, n);

	  /* try to make the division between sounds more obvious */
	  for (i = 0; i < cw->composite.num_children; i++)
	    {
	      Widget child;
	      child = cw->composite.children[i];
	      if (((XtIsWidget(child))|| (XmIsGadget(child))) &&
		  (XtIsManaged(child)) && 
		  ((XmIsSeparator(child)) || (XmIsSeparatorGadget(child))))
		XtVaSetValues(child, XmNseparatorType, XmDOUBLE_LINE, 
			      XmNbackground, ss->white,
			      NULL);
	    }
	}

      XtAddEventHandler(SND_PANE(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      /* if user clicks in controls, then starts typing, try to send key events to current active channel */
      /* all widgets in the control-pane that would otherwise intercept the key events get this event handler */

      for (i = 0; i < nchans; i++)
	add_channel_window(sp, i, chan_min_y, 0, NULL, WITH_FW_BUTTONS, WITH_EVENTS); 
      /* creates channel (horizontal) paned window widget as child of w_snd_pane(sp) == SND_PANE(sp) */
      

      /* -------- sound file name, status area, various buttons -------- */

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNpaneMinimum, 20); n++;
      XtSetArg(args[n], XmNpaneMaximum, 20); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      NAME_BOX(sp) = XtCreateManagedWidget("snd-name-form", xmFormWidgetClass, SND_PANE(sp), args, n);
      XtAddEventHandler(NAME_BOX(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);

      if (!mini_lock_allocated) 
	allocate_icons(NAME_BOX(sp));

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNlabelType, XmPIXMAP); n++;
      XtSetArg(args[n], XmNlabelPixmap, close_icon); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNwidth, 32); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      CLOSE_BUTTON(sp) = XtCreateManagedWidget("close-button", xmPushButtonWidgetClass, NAME_BOX(sp), args, n);
      XtAddCallback(CLOSE_BUTTON(sp), XmNactivateCallback, close_button_callback, (XtPointer)sp);

      n = 0;      
      s1 = XmStringCreateLocalized(shortname_indexed(sp));
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      /* XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++; */

      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, CLOSE_BUTTON(sp)); n++;

      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      NAME_LABEL(sp) = XtCreateManagedWidget("snd-name", xmPushButtonWidgetClass, NAME_BOX(sp), args, n);
      XtAddEventHandler(NAME_LABEL(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(NAME_LABEL(sp), XmNactivateCallback, name_click_callback, (XtPointer)sp);
      XmStringFree(s1);

      n = 0;      
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, NAME_LABEL(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;

      if (blank_pixmap)
	{
	  /* if xpm failed (blank_pixmap == 0), this can cause X to kill Snd! */
	  XtSetArg(args[n], XmNlabelType, XmPIXMAP); n++;
	  XtSetArg(args[n], XmNlabelPixmap, blank_pixmap); n++;
	}

      LOCK_OR_BOMB(sp) = XtCreateManagedWidget("", xmLabelWidgetClass, NAME_BOX(sp), args, n);

      {
	uint32_t i;
	Widget left_widget;

	left_widget = LOCK_OR_BOMB(sp);
	sp->progress_widgets = (Widget *)calloc(sp->nchans, sizeof(Widget));
	sp->num_progress_widgets = sp->nchans;
	/* when an unused sound is reopened in snd-data.c, it's possible for its channel number
	 *   to be increased.  If we then try to draw the clock icon in the new channel, its
	 *   widget will be unallocated -> segfault, so to keep things simple, we check this number.
	 */

	for (i = 0; i < sp->nchans; i++)
	  {
	    n = 0;      
	    XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
	    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
	    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
	    XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
	    XtSetArg(args[n], XmNleftWidget, left_widget); n++;
	    XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;

	    if (blank_pixmap)
	      {
		/* if xpm failed (blank_pixmap == 0), this can cause X to kill Snd! */
		XtSetArg(args[n], XmNlabelType, XmPIXMAP); n++;
		XtSetArg(args[n], XmNlabelPixmap, blank_pixmap); n++;
	      }

	    sp->progress_widgets[i] = XtCreateManagedWidget("", xmLabelWidgetClass, NAME_BOX(sp), args, n);
	    left_widget = sp->progress_widgets[i];
	  }

	n = 0;      
	XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
	XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
	XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
	XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
	XtSetArg(args[n], XmNleftWidget, left_widget); n++;
	XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;

	if (blank_pixmap)
	  {
	    XtSetArg(args[n], XmNlabelType, XmPIXMAP); n++;
	    XtSetArg(args[n], XmNlabelPixmap, blank_pixmap); n++;
	  }

	XtSetArg(args[n], XmNshadowThickness, 0); n++;
	XtSetArg(args[n], XmNhighlightThickness, 0); n++;
	XtSetArg(args[n], XmNfillOnArm, false); n++;
	STOP_ICON(sp) = XtCreateManagedWidget("", xmPushButtonWidgetClass, NAME_BOX(sp), args, n);
	XtAddCallback(STOP_ICON(sp), XmNactivateCallback, stop_sign_click_callback, (XtPointer)sp);
      }

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, STOP_ICON(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNresizeWidth, true); n++;
      XtSetArg(args[n], XmNmarginHeight, 1); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNeditable, false); n++;
      XtSetArg(args[n], XmNcursorPositionVisible, false); n++;
      STATUS_AREA(sp) = XtCreateManagedWidget("snd-info", xmTextFieldWidgetClass, NAME_BOX(sp), args, n);

#if WITH_AUDIO
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
#ifdef TOGGLE_MARGIN
      XtSetArg(args[n], XmNmarginHeight, TOGGLE_MARGIN); n++;
      XtSetArg(args[n], XmNmarginTop, TOGGLE_MARGIN); n++;
#endif
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      /* in Motif 2.2 this sets up a tooltip:
	XtSetArg(args[n], XmNtoolTipString, XmStringCreateLocalized((char *)"play this sound")); n++;
      */
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      PLAY_BUTTON(sp) = make_togglebutton_widget("play", NAME_BOX(sp), args, n);
      XtAddCallback(PLAY_BUTTON(sp), XmNvalueChangedCallback, play_button_callback, (XtPointer)sp);
#endif

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
#ifdef TOGGLE_MARGIN
      XtSetArg(args[n], XmNmarginHeight, TOGGLE_MARGIN); n++;
      XtSetArg(args[n], XmNmarginTop, TOGGLE_MARGIN); n++;
#endif
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
#if WITH_AUDIO
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, PLAY_BUTTON(sp)); n++;
#else
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
#endif
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      SYNC_BUTTON(sp) = make_togglebutton_widget("sync", NAME_BOX(sp), args, n);
      XtAddEventHandler(SYNC_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(SYNC_BUTTON(sp), XmNvalueChangedCallback, sync_button_callback, (XtPointer)sp);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNbottomWidget, SYNC_BUTTON(sp)); n++;
#ifdef TOGGLE_MARGIN
      XtSetArg(args[n], XmNmarginHeight, TOGGLE_MARGIN); n++;
      XtSetArg(args[n], XmNmarginTop, TOGGLE_MARGIN); n++;
#endif
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, SYNC_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      UNITE_BUTTON(sp) = make_togglebutton_widget("unite", NAME_BOX(sp), args, n);
      XtAddEventHandler(UNITE_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(UNITE_BUTTON(sp), XmNvalueChangedCallback, unite_button_callback, (XtPointer)sp);


      /* ---------------- control panel ---------------- */
      n = 0;      
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      CONTROLS(sp) = XtCreateManagedWidget("snd-amp", xmFormWidgetClass, SND_PANE(sp), args, n);
      XtAddEventHandler(CONTROLS(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);

      n = 0;      
      /* AMP */
      s1 = XmStringCreateLocalized((char *)"amp:");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      AMP_BUTTON(sp) = make_pushbutton_widget("amp-label", CONTROLS(sp), args, n);
      XtAddEventHandler(AMP_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(AMP_BUTTON(sp), XmNactivateCallback, amp_click_callback, (XtPointer)sp);
      XmStringFree(s1);

      n = 0;
      s1 = XmStringCreateLocalized((char *)"1.0   ");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, AMP_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, AMP_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginRight, 3); n++;
      AMP_LABEL(sp) = XtCreateManagedWidget("amp-number", xmLabelWidgetClass, CONTROLS(sp), args, n);
      XmStringFree(s1);

      n = 0;      
      XtSetArg(args[n], XmNbackground, ss->position_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, AMP_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, AMP_LABEL(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNvalue, amp_to_scroll(sp->amp_control_min, 1.0, sp->amp_control_max)); n++;
      XtSetArg(args[n], XmNdragCallback, n1 = make_callback_list(amp_drag_callback, (XtPointer)sp)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n2 = make_callback_list(amp_valuechanged_callback, (XtPointer)sp)); n++;
      AMP_SCROLLBAR(sp) = XtCreateManagedWidget("amp", xmScrollBarWidgetClass, CONTROLS(sp), args, n);
      XtAddEventHandler(AMP_SCROLLBAR(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);

      n = 0;
      /* SPEED */
      s1 = XmStringCreateLocalized((char *)"speed:");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, AMP_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++; 
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      SPEED_BUTTON(sp) = make_pushbutton_widget("speed-label", CONTROLS(sp), args, n);
      XtAddEventHandler(SPEED_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(SPEED_BUTTON(sp), XmNactivateCallback, speed_click_callback, (XtPointer)sp);
      XmStringFree(s1);

      n = 0;
      s1 = initial_speed_label(sp->speed_control_style);
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, SPEED_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, SPEED_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++; 
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNmarginRight, 3); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      SPEED_LABEL(sp) = make_pushbutton_widget("speed-number", CONTROLS(sp), args, n);
      XtAddEventHandler(SPEED_LABEL(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(SPEED_LABEL(sp), XmNactivateCallback, speed_label_click_callback, (XtPointer)sp);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, SPEED_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNindicatorOn, false); n++;
      XtSetArg(args[n], XmNlabelType, XmPIXMAP); n++;
      XtSetArg(args[n], XmNmarginHeight, 0); n++;
      XtSetArg(args[n], XmNmarginWidth, 0); n++;
      XtSetArg(args[n], XmNmarginTop, 0); n++;
      XtSetArg(args[n], XmNtopOffset, 0); n++;
      SPEED_ARROW(sp) = make_togglebutton_widget("dir", CONTROLS(sp), args, n);
      form = SPEED_ARROW(sp);
      if (!spd_ok)
	{
	  rb = XCreateBitmapFromData(XtDisplay(form), RootWindowOfScreen(XtScreen(form)), (const char *)speed_r_bits1, 16, 12);
	  lb = XCreateBitmapFromData(XtDisplay(form), RootWindowOfScreen(XtScreen(form)), (const char *)speed_l_bits1, 16, 12);
	  XtVaGetValues(form, XmNdepth, &depth, NULL);
	  spd_r = XCreatePixmap(XtDisplay(form), RootWindowOfScreen(XtScreen(form)), 16, 12, depth);
	  spd_l = XCreatePixmap(XtDisplay(form), RootWindowOfScreen(XtScreen(form)), 16, 12, depth);
	  XCopyPlane(XtDisplay(form), rb, spd_r, ss->fltenv_basic_gc, 0, 0, 16, 12, 0, 0, 1);
	  XCopyPlane(XtDisplay(form), lb, spd_l, ss->fltenv_basic_gc, 0, 0, 16, 12, 0, 0, 1);
	  XFreePixmap(XtDisplay(form), rb);
	  XFreePixmap(XtDisplay(form), lb);
	  spd_ok = true;
	}
      XtVaSetValues(form, XmNselectPixmap, spd_l, XmNlabelPixmap, spd_r, NULL);
      XtAddEventHandler(SPEED_ARROW(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(SPEED_ARROW(sp), XmNvalueChangedCallback, play_arrow_callback, (XtPointer)sp);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->position_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, SPEED_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, SPEED_LABEL(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, SPEED_ARROW(sp)); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNvalue, speed_to_scroll(sp->speed_control_min, 1.0, sp->speed_control_max)); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNdragCallback, n3 = make_callback_list(speed_drag_callback, (XtPointer)sp)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n4 = make_callback_list(speed_valuechanged_callback, (XtPointer)sp)); n++;
      SPEED_SCROLLBAR(sp) = XtCreateManagedWidget("speed-scroll", xmScrollBarWidgetClass, CONTROLS(sp), args, n);
      XtAddEventHandler(SPEED_SCROLLBAR(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);

      n = 0;
      /* EXPAND */
      s1 = XmStringCreateLocalized((char *)"expand:");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, SPEED_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      EXPAND_LEFT_BUTTON(sp) = make_pushbutton_widget("expand-label", CONTROLS(sp), args, n);
      XtAddEventHandler(EXPAND_LEFT_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(EXPAND_LEFT_BUTTON(sp), XmNactivateCallback, expand_click_callback, (XtPointer)sp);
      XmStringFree(s1);
      
      n = 0;
      s1 = XmStringCreateLocalized((char *)"1.0   ");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, EXPAND_LEFT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, EXPAND_LEFT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginRight, 3); n++;
      EXPAND_LABEL(sp) = XtCreateManagedWidget("expand-number", xmLabelWidgetClass, CONTROLS(sp), args, n);
      XmStringFree(s1);

      n = 0;
      s1 = XmStringCreateLocalized((char *)"");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, EXPAND_LEFT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNmarginWidth, 0); n++;
      XtSetArg(args[n], XmNtopOffset, 1); n++;
      XtSetArg(args[n], XmNspacing, 0); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      if (ss->toggle_size > 0) {XtSetArg(args[n], XmNindicatorSize, ss->toggle_size); n++;}
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      EXPAND_RIGHT_BUTTON(sp) = make_togglebutton_widget("expoff", CONTROLS(sp), args, n);
      XtAddEventHandler(EXPAND_RIGHT_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(EXPAND_RIGHT_BUTTON(sp), XmNvalueChangedCallback, expand_button_callback, (XtPointer)sp);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, EXPAND_LEFT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, EXPAND_LABEL(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, EXPAND_RIGHT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNvalue, expand_to_scroll(sp->expand_control_min, 1.0, sp->expand_control_max)); n++; 
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNdragCallback, n5 = make_callback_list(expand_drag_callback, (XtPointer)sp)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n6 = make_callback_list(expand_valuechanged_callback, (XtPointer)sp)); n++;
      EXPAND_SCROLLBAR(sp) = XtCreateManagedWidget("expand-scroll", xmScrollBarWidgetClass, CONTROLS(sp), args, n);
      XtAddEventHandler(EXPAND_SCROLLBAR(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);


      /* CONTRAST */
      n = 0;
      s1 = XmStringCreateLocalized((char *)"contrast:");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, EXPAND_LEFT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      CONTRAST_LEFT_BUTTON(sp) = make_pushbutton_widget("contrast-label", CONTROLS(sp), args, n);
      XtAddEventHandler(CONTRAST_LEFT_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(CONTRAST_LEFT_BUTTON(sp), XmNactivateCallback, contrast_click_callback, (XtPointer)sp);
      XmStringFree(s1);
      
      n = 0;
      s1 = XmStringCreateLocalized((char *)"1.0   ");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, CONTRAST_LEFT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, CONTRAST_LEFT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginRight, 3); n++;
      CONTRAST_LABEL(sp) = XtCreateManagedWidget("contrast-number", xmLabelWidgetClass, CONTROLS(sp), args, n);
      XmStringFree(s1);
      
      n = 0;
      s1 = XmStringCreateLocalized((char *)"");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, CONTRAST_LEFT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNmarginWidth, 0); n++;
      XtSetArg(args[n], XmNtopOffset, 1); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNspacing, 0); n++;
      if (ss->toggle_size > 0) {XtSetArg(args[n], XmNindicatorSize, ss->toggle_size); n++;}
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      CONTRAST_RIGHT_BUTTON(sp) = make_togglebutton_widget("conoff", CONTROLS(sp), args, n);
      XtAddEventHandler(CONTRAST_RIGHT_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(CONTRAST_RIGHT_BUTTON(sp), XmNvalueChangedCallback, contrast_button_callback, (XtPointer)sp);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, CONTRAST_LEFT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, CONTRAST_LABEL(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, CONTRAST_RIGHT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNvalue, 0); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNdragCallback, n7 = make_callback_list(contrast_drag_callback, (XtPointer)sp)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n8 = make_callback_list(contrast_valuechanged_callback, (XtPointer)sp)); n++;
      CONTRAST_SCROLLBAR(sp) = XtCreateManagedWidget("contrast-scroll", xmScrollBarWidgetClass, CONTROLS(sp), args, n);
      XtAddEventHandler(CONTRAST_SCROLLBAR(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);

      /* REVERB */
      /* REVSCL */
      n = 0;
      s1 = XmStringCreateLocalized((char *)"reverb:");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, CONTRAST_LEFT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      REVSCL_BUTTON(sp) = make_pushbutton_widget("revscl-label", CONTROLS(sp), args, n);
      XtAddEventHandler(REVSCL_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(REVSCL_BUTTON(sp), XmNactivateCallback, revscl_click_callback, (XtPointer)sp);
      XmStringFree(s1);
      
      n = 0;
      s1 = XmStringCreateLocalized((char *)"0.0     ");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, REVSCL_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, REVSCL_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNmarginRight, 3); n++;
      REVSCL_LABEL(sp) = XtCreateManagedWidget("revscl-number", xmLabelWidgetClass, CONTROLS(sp), args, n);
      XmStringFree(s1);
      
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, REVSCL_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, REVSCL_LABEL(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 60); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNvalue, 0); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNdragCallback, n9 = make_callback_list(revscl_drag_callback, (XtPointer)sp)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n10 = make_callback_list(revscl_valuechanged_callback, (XtPointer)sp)); n++;
      REVSCL_SCROLLBAR(sp) = XtCreateManagedWidget("revscl-scroll", xmScrollBarWidgetClass, CONTROLS(sp), args, n);
      XtAddEventHandler(REVSCL_SCROLLBAR(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);

      /* REVOFF */
      n = 0;
      s1 = XmStringCreateLocalized((char *)"");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, REVSCL_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, CONTRAST_RIGHT_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNmarginWidth, 0); n++;
      XtSetArg(args[n], XmNtopOffset, 1); n++;
      XtSetArg(args[n], XmNspacing, 0); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      if (ss->toggle_size > 0) {XtSetArg(args[n], XmNindicatorSize, ss->toggle_size); n++;}
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      REVERB_BUTTON(sp) = make_togglebutton_widget("revoff", CONTROLS(sp), args, n);
      XtAddEventHandler(REVERB_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(REVERB_BUTTON(sp), XmNvalueChangedCallback, reverb_button_callback, (XtPointer)sp);
      XmStringFree(s1);


      /* REVLEN */
      n = 0;
      s1 = XmStringCreateLocalized((char *)"len:");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, REVSCL_SCROLLBAR(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNleftPosition, 60); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      REVLEN_BUTTON(sp) = make_pushbutton_widget("revlen-label", CONTROLS(sp), args, n);
      XtAddEventHandler(REVLEN_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(REVLEN_BUTTON(sp), XmNactivateCallback, revlen_click_callback, (XtPointer)sp);
      XmStringFree(s1);

      n = 0;
      s1 = XmStringCreateLocalized((char *)"1.0 ");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, REVLEN_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, REVLEN_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNmarginRight, 3); n++;
      REVLEN_LABEL(sp) = XtCreateManagedWidget("revlen-number", xmLabelWidgetClass, CONTROLS(sp), args, n);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, REVLEN_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, REVLEN_LABEL(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, REVERB_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNmaximum, SCROLLBAR_MAX); n++;
      XtSetArg(args[n], XmNvalue, revlen_to_scroll(sp->reverb_control_length_min, 1.0, sp->reverb_control_length_max)); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNdragCallback, n11 = make_callback_list(revlen_drag_callback, (XtPointer)sp)); n++;
      XtSetArg(args[n], XmNvalueChangedCallback, n12 = make_callback_list(revlen_valuechanged_callback, (XtPointer)sp)); n++;
      REVLEN_SCROLLBAR(sp) = XtCreateManagedWidget("revlen-scroll", xmScrollBarWidgetClass, CONTROLS(sp), args, n);
      XtAddEventHandler(REVLEN_SCROLLBAR(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);


      /* FILTER */
      n = 0;
      s1 = XmStringCreateLocalized((char *)"filter:");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, REVSCL_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      XtSetArg(args[n], XmNshadowThickness, 0); n++;
      XtSetArg(args[n], XmNhighlightThickness, 0); n++;
      XtSetArg(args[n], XmNfillOnArm, false); n++;
      FILTER_LABEL(sp) = XtCreateManagedWidget("filter-label", xmLabelWidgetClass, CONTROLS(sp), args, n);
      XmStringFree(s1);

      /* filter order */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNresizeWidth, false); n++;
      XtSetArg(args[n], XmNcolumns, 3); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;	
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, FILTER_LABEL(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, FILTER_LABEL(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      XtSetArg(args[n], XmNrecomputeSize, false); n++;
      FILTER_ORDER_TEXT(sp) = make_textfield_widget("filter-order", CONTROLS(sp), args, n, ACTIVATABLE, NO_COMPLETER);
      XmTextSetString(FILTER_ORDER_TEXT(sp), (char *)" 20");
      XtAddCallback(FILTER_ORDER_TEXT(sp), XmNactivateCallback, filter_order_activate_callback, (XtPointer)sp);

      #define ARROW_SIZE 12

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, FILTER_ORDER_TEXT(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, FILTER_ORDER_TEXT(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNheight, ARROW_SIZE); n++;
      XtSetArg(args[n], XmNwidth, ARROW_SIZE); n++;
      XtSetArg(args[n], XmNborderWidth, 0); n++;
      XtSetArg(args[n], XmNmarginWidth, 0); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      FILTER_ORDER_DOWN(sp) = make_pushbutton_widget("", CONTROLS(sp), args, n);
      XtAddEventHandler(FILTER_ORDER_DOWN(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(FILTER_ORDER_DOWN(sp), XmNactivateCallback, filter_order_down_callback, (XtPointer)sp);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, FILTER_ORDER_DOWN(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, FILTER_ORDER_TEXT(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNheight, ARROW_SIZE); n++;
      XtSetArg(args[n], XmNwidth, ARROW_SIZE); n++;
      XtSetArg(args[n], XmNborderWidth, 0); n++;
      XtSetArg(args[n], XmNmarginWidth, 0); n++;
      XtSetArg(args[n], XmNarmColor, ss->selection_color); n++;
      FILTER_ORDER_UP(sp) = make_pushbutton_widget("", CONTROLS(sp), args, n);
      XtAddEventHandler(FILTER_ORDER_UP(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(FILTER_ORDER_UP(sp), XmNactivateCallback, filter_order_up_callback, (XtPointer)sp);

      n = 0;
      s1 = XmStringCreateLocalized((char *)"");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, REVERB_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNheight, 16); n++;
      XtSetArg(args[n], XmNmarginWidth, 0); n++;
      XtSetArg(args[n], XmNtopOffset, 2); n++;
      XtSetArg(args[n], XmNspacing, 0); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++; 
      if (ss->toggle_size > 0) {XtSetArg(args[n], XmNindicatorSize, ss->toggle_size); n++;}
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      FILTER_BUTTON(sp) = make_togglebutton_widget("fltoff", CONTROLS(sp), args, n);
      XtAddEventHandler(FILTER_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(FILTER_BUTTON(sp), XmNvalueChangedCallback, filter_button_callback, (XtPointer)sp);
      XmStringFree(s1);

      n = 0;
      s1 = XmStringCreateLocalized((char *)"hz");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, FILTER_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, FILTER_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++; 
      XtSetArg(args[n], XmNvalue, sp->filter_control_in_hz); n++;
      if (ss->toggle_size > 0) {XtSetArg(args[n], XmNindicatorSize, ss->toggle_size); n++;}
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      FILTER_HZ_BUTTON(sp) = make_togglebutton_widget("flthz", CONTROLS(sp), args, n);
      XtAddEventHandler(FILTER_HZ_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(FILTER_HZ_BUTTON(sp), XmNvalueChangedCallback, filter_hz_callback, (XtPointer)sp);
      XmStringFree(s1);

      n = 0;
      s1 = XmStringCreateLocalized((char *)"dB");
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, FILTER_HZ_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, FILTER_HZ_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNlabelString, s1); n++; 
      XtSetArg(args[n], XmNvalue, sp->filter_control_in_dB); n++;
      if (ss->toggle_size > 0) {XtSetArg(args[n], XmNindicatorSize, ss->toggle_size); n++;}
      XtSetArg(args[n], XmNselectColor, ss->selection_color); n++;
      FILTER_DB_BUTTON(sp) = make_togglebutton_widget("fltdB", CONTROLS(sp), args, n);
      XtAddEventHandler(FILTER_DB_BUTTON(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);
      XtAddCallback(FILTER_DB_BUTTON(sp), XmNvalueChangedCallback, filter_dB_callback, (XtPointer)sp);
      XmStringFree(s1);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNalignment, XmALIGNMENT_BEGINNING); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, FILTER_ORDER_DOWN(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_NONE); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNleftWidget, FILTER_ORDER_DOWN(sp)); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNrightWidget, FILTER_DB_BUTTON(sp)); n++;
      XtSetArg(args[n], XmNmarginHeight, CONTROLS_MARGIN); n++;
      FILTER_COEFFS_TEXT(sp) = make_textfield_widget("filter-text", CONTROLS(sp), args, n, ACTIVATABLE, add_completer_func(filename_completer, NULL));
      XtAddCallback(FILTER_COEFFS_TEXT(sp), XmNactivateCallback, filter_activate_callback, (XtPointer)sp);

      /* FILTER GRAPH */
      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
      XtSetArg(args[n], XmNtopWidget, FILTER_COEFFS_TEXT(sp)); n++;
      XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
      XtSetArg(args[n], XmNleftAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNleftPosition, 4); n++;
      XtSetArg(args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
      XtSetArg(args[n], XmNrightPosition, 98); n++;
      XtSetArg(args[n], XmNallowResize, true); n++;
      XtSetArg(args[n], XmNshadowType, XmSHADOW_ETCHED_IN); n++;
      XtSetArg(args[n], XmNshadowThickness, 4); n++;
      FILTER_FRAME(sp) = XtCreateManagedWidget("filter-frame", xmFrameWidgetClass, CONTROLS(sp), args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->highlight_color); n++;
      n = attach_all_sides(args, n);
      XtSetArg(args[n], XmNallowResize, true); n++;
      FILTER_GRAPH(sp) = XtCreateManagedWidget("filter-window", xmDrawingAreaWidgetClass, FILTER_FRAME(sp), args, n);
      XtAddCallback(FILTER_GRAPH(sp), XmNresizeCallback, filter_drawer_resize, (XtPointer)sp);
      XtAddCallback(FILTER_GRAPH(sp), XmNexposeCallback, filter_drawer_resize, (XtPointer)sp);

      sp->flt = new_env_editor();

      XtAddEventHandler(FILTER_GRAPH(sp), ButtonPressMask, false, filter_drawer_button_press, sp);
      XtAddEventHandler(FILTER_GRAPH(sp), ButtonMotionMask, false, filter_drawer_button_motion, sp);
      XtAddEventHandler(FILTER_GRAPH(sp), ButtonReleaseMask, false, filter_drawer_button_release, sp);
      XtAddEventHandler(FILTER_GRAPH(sp), KeyPressMask, false, graph_key_press, (XtPointer)sp);

      free(n1); free(n2); free(n3); free(n4); free(n5); free(n6);
      free(n7); free(n8); free(n9); free(n10); free(n11); free(n12);
      /* end if control-panel */
      if (sound_style(ss) == SOUNDS_IN_NOTEBOOK)
	{
	  char *name;
	  name = just_filename(sp->short_filename); /* copies */
	  if (strlen(name) > 8) name[8] = '\0';
	  n = 0;
	  XtSetArg(args[n], XmNbackground, ss->graph_color); n++;
	  XtSetArg(args[n], XmNnotebookChildType, XmMAJOR_TAB); n++;
	  XtSetArg(args[n], XmNuserData, sp->index); n++;
	  sp->tab = XtCreateManagedWidget(name, xmPushButtonWidgetClass, sound_pane(ss), args, n);
	  free(name);
	}


      if (sound_style(ss) != SOUNDS_IN_SEPARATE_WINDOWS)
	run_new_widget_hook(SND_PANE(sp));
      else run_new_widget_hook(sp->dialog);

#if WITH_RELATIVE_PANES
      if (sound_style(ss) == SOUNDS_VERTICAL)
	add_sash_watchers(sound_pane(ss)); /* add in any case since we might later change the sense of with_relative_panes */
#endif

    } /* new sound ss */
  else
    { /* re-manage currently inactive chan */
      int k;
      if (sound_style(ss) == SOUNDS_IN_SEPARATE_WINDOWS)
	{
	  title = (char *)calloc(PRINT_BUFFER_SIZE, sizeof(char));
	  snprintf(title, PRINT_BUFFER_SIZE, "%d: %s", snd_slot, sp->short_filename);
	  XtVaSetValues(sp->dialog, XmNtitle, title, NULL);
	  free(title);
	  if (!XtIsManaged(sp->dialog)) XtManageChild(sp->dialog);
	}

      for (i = 0; i < SP_NUM_WIDGETS - 1; i++)
	if ((sw[i]) && 
	    (!XtIsManaged(sw[i])) &&
	    (in_show_controls(ss) || (i != W_amp_form)))
	  XtManageChild(sw[i]);

      for (k = 0; k < nchans; k++) 
	add_channel_window(sp, k, chan_min_y, 0, NULL, WITH_FW_BUTTONS, WITH_EVENTS);

      set_button_label(NAME_LABEL(sp), shortname_indexed(sp));
      XtVaSetValues(SND_PANE(sp), XmNuserData, sp->index, NULL);

      if (sound_style(ss) == SOUNDS_IN_NOTEBOOK)
	{
	  char *name;
	  name = just_filename(sp->short_filename);
	  set_label(sp->tab, name);
	  free(name);
	}
    }

  map_over_children(sound_pane(ss), color_sashes);
  if (!(in_show_controls(ss)))
    hide_controls(sp);
  else show_controls(sp);

  if (sp->nchans == 1) 
    {
      XmToggleButtonSetState(UNITE_BUTTON(sp), false, false);
      XtUnmanageChild(UNITE_BUTTON(sp));

      if (ss->active_sounds == 0) /* ticked at the end in snd-file.c */
	{
	  XmToggleButtonSetState(SYNC_BUTTON(sp), false, false);
	  XtUnmanageChild(SYNC_BUTTON(sp));
	}
      else
	{
	  for_each_sound(manage_sync_button); 
	}
    }
  else
    {
      for_each_sound(manage_sync_button); 
    }
  attach_status_area(sp);

  add_sound_data(filename, sp, WITH_GRAPH);

  if (cant_write(sp->filename)) 
    sp->file_read_only = FILE_READ_ONLY;
  if ((sp->file_read_only == FILE_READ_ONLY) || 
      (sp->user_read_only == FILE_READ_ONLY)) 
    show_lock(sp);
  else hide_lock(sp);

  if (old_name)
    status_report(sp, "(translated %s)", old_name);

  if (sound_style(ss) != SOUNDS_IN_SEPARATE_WINDOWS)
    {
      reset_controls(sp);
    }
  else 
    {
      XtVaSetValues(sp->dialog,
		    XmNwidth, 100,
		    XmNheight, 100,
		    NULL);
      /* this is not redundant -- apparently they're trying to ignore size resets to the "current" */
      /* value, but forgot that unmanage/remanage does not return to the previous size */
      XtVaSetValues(sp->dialog,
		    XmNwidth, (Dimension)(widget_width(main_shell(ss))),
		    XmNheight, (Dimension)(chan_min_y * nchans), /* bugfix thanks to Paul @pobox */
		    NULL);
    }

  after_open(sp);
  if (free_filename) free(filename);
  return(sp);
}


void update_sound_label(snd_info *sp)
{
  if (has_widgets(sp))
    set_button_label(NAME_LABEL(sp), shortname_indexed(sp));  
}

void snd_info_cleanup(snd_info *sp)
{
  if (has_widgets(sp))
    {
      clear_status_area(sp);
      if (SYNC_BUTTON(sp))
	{
	  XtVaSetValues(SYNC_BUTTON(sp), XmNset, false, NULL);
	  XtVaSetValues(EXPAND_RIGHT_BUTTON(sp), XmNset, false, NULL);
	  XtVaSetValues(CONTRAST_RIGHT_BUTTON(sp), XmNset, false, NULL);
	  XtVaSetValues(SPEED_ARROW(sp), XmNset, false, NULL);
	  XtVaSetValues(FILTER_BUTTON(sp), XmNset, false, NULL);
	  XtVaSetValues(REVERB_BUTTON(sp), XmNset, false, NULL);
	  XmToggleButtonSetState(UNITE_BUTTON(sp), false, false);
	  sp->channel_style = CHANNELS_SEPARATE;
	  if (sound_style(ss) == SOUNDS_IN_NOTEBOOK)
	    {
	      set_label(sp->tab, "none");
	      XmChangeColor(sp->tab, ss->graph_color);
	    }
	  XtUnmanageChild(SND_PANE(sp));
	}
      if ((sp->dialog) && 
	  (XtIsManaged(sp->dialog))) 
	XtUnmanageChild(sp->dialog);
    }
}


static Xen reflect_file_close_in_sync(Xen hook_or_reason)
{
  int reason;
#if HAVE_SCHEME
  reason = Xen_integer_to_C_int(s7_let_ref(s7, hook_or_reason, s7_make_symbol(s7, "reason")));
#else
  reason = Xen_integer_to_C_int(hook_or_reason);
#endif
  if ((reason == FILE_CLOSED) && /* snd-file.c */
      (ss->active_sounds == 1))
    {
      snd_info *sp;
      sp = any_selected_sound();
      if ((sp) && (sp->nchans == 1))
	{
	  XtUnmanageChild(SYNC_BUTTON(sp));
	  attach_status_area(sp);
	}
    }
  return(Xen_false);
}

void set_sound_pane_file_label(snd_info *sp, const char *str)
{
  if (!(mus_strcmp(sp->name_string, str)))
    {
      if (sp->name_string) free(sp->name_string);
      sp->name_string = mus_strdup(str);
      set_button_label(SND_NAME(sp), str); /* this causes an expose event, so it's worth minimizing */
    }
}


void color_filter_waveform(Pixel color)
{
  int i;
  XSetForeground(main_display(ss), ss->fltenv_data_gc, color);
  for (i = 0; i < ss->max_sounds; i++)
    {
      snd_info *sp;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	display_filter_env(sp);
    }
}


void show_controls(snd_info *sp)
{
  Dimension hgt;
  XtVaGetValues(FILTER_LABEL(sp),
		XmNy, &hgt,
		NULL);
  if (XtIsManaged(CONTROLS(sp)))
    XtUnmanageChild(CONTROLS(sp));
  XtVaSetValues(CONTROLS(sp),
		XmNpaneMinimum, hgt,
		XmNpaneMaximum, hgt,
		NULL);
  XtManageChild(CONTROLS(sp));
  XtVaSetValues(CONTROLS(sp),
		XmNpaneMinimum, 1,
		XmNpaneMaximum, LOTSA_PIXELS,
		NULL);
}


void hide_controls(snd_info *sp)
{
  XtUnmanageChild(CONTROLS(sp));
}


bool showing_controls(snd_info *sp)
{
  return((bool)(XtIsManaged(CONTROLS(sp))));
}


void show_all_controls(void)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      snd_info *sp;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	show_controls(sp);
    }
}


void hide_all_controls(void)
{
  int i;
  for (i = 0; i < ss->max_sounds; i++)
    {
      snd_info *sp;
      sp = ss->sounds[i];
      if ((sp) && (sp->inuse == SOUND_NORMAL))
	hide_controls(sp);
    }
}


int control_panel_height(snd_info *sp)
{
  return(widget_height(CONTROLS(sp)));
}


/* -------- PROGRESS REPORT -------- */

void progress_report(chan_info *cp, mus_float_t pct)
{
  int which;
  snd_info *sp;
  sp = cp->sound;

  if ((!has_widgets(sp)) || (sp->inuse != SOUND_NORMAL)) return;

  which = (int)(pct * NUM_HOURGLASSES);
  if (which >= NUM_HOURGLASSES) which = NUM_HOURGLASSES - 1;
  if (which < 0) which = 0;

  if ((hourglasses[which]) &&
      (cp->chan < sp->num_progress_widgets) &&
      ((cp->chan == 0) ||
       (sp->channel_style != CHANNELS_SUPERIMPOSED)))
    {
      XtVaSetValues(PROGRESS_ICON(cp), XmNlabelPixmap, hourglasses[which], NULL);
      XmUpdateDisplay(PROGRESS_ICON(cp));
    }

  check_for_event();
}

void finish_progress_report(chan_info *cp)
{
  snd_info *sp;
  sp = cp->sound;

  if ((!has_widgets(sp)) || (sp->inuse != SOUND_NORMAL)) return;

  if ((cp->chan < sp->num_progress_widgets) &&
      ((cp->chan == 0) ||
       (sp->channel_style != CHANNELS_SUPERIMPOSED)))
    {
      XtVaSetValues(PROGRESS_ICON(cp), XmNlabelPixmap, blank_pixmap, NULL);
      XmUpdateDisplay(PROGRESS_ICON(cp));
      hide_stop_sign(sp);
    }
}


void start_progress_report(chan_info *cp)
{
  snd_info *sp;
  sp = cp->sound;

  if ((!has_widgets(sp)) || (sp->inuse != SOUND_NORMAL)) return;

  if ((cp->chan < sp->num_progress_widgets) &&
      ((cp->chan == 0) ||
       (sp->channel_style != CHANNELS_SUPERIMPOSED)))
    {
      XtVaSetValues(PROGRESS_ICON(cp), XmNlabelPixmap, hourglasses[0], NULL);
      XmUpdateDisplay(PROGRESS_ICON(cp));
      show_stop_sign(sp);
    }
}



void reflect_sound_selection(snd_info *sp)
{
  /* sp is the newly selected sound, ss->selected_sound is the previous one */
  snd_info *osp = NULL;

  if (ss->selected_sound != NO_SELECTION) 
    osp = ss->sounds[ss->selected_sound];

  if ((osp) && 
      (sp != osp) && 
      (osp->inuse == SOUND_NORMAL)) 
    {
      XmChangeColor(SND_NAME(osp), ss->highlight_color);
      if (sound_style(ss) == SOUNDS_IN_NOTEBOOK) 
	XmChangeColor(osp->tab, ss->graph_color);
    }

  if (sp->selected_channel != NO_SELECTION) 
    {
      XmChangeColor(SND_NAME(sp), ss->white);
      if (sound_style(ss) == SOUNDS_IN_NOTEBOOK) 
	{
	  int page, current_page;
	  XmNotebookPageStatus status;
	  XmNotebookPageInfo info;
	  XmChangeColor(sp->tab, ss->selected_graph_color);
	  XtVaGetValues(sound_pane(ss), XmNcurrentPageNumber, &current_page, NULL);
	  XtVaGetValues(sp->tab, XmNpageNumber, &page, NULL);
	  if (page != current_page)
	    {
	      status = XmNotebookGetPageInfo(sound_pane(ss), page, &info);
	      if (status == XmPAGE_FOUND)
		{
		  XtVaSetValues(sound_pane(ss), XmNcurrentPageNumber, page, NULL);
		}
	    }
	}
    }
}


/* -------- controls dialog -------- */

static Widget controls_dialog = NULL;
enum {EXPAND_HOP, EXPAND_LENGTH, EXPAND_RAMP, EXPAND_JITTER, CONTRAST_AMP, REVERB_LOWPASS, REVERB_FEEDBACK};
static Widget controls[7];

static void reset_all_sliders(void)
{
  expand_control_set_hop(DEFAULT_EXPAND_CONTROL_HOP);
  expand_control_set_length(DEFAULT_EXPAND_CONTROL_LENGTH);
  expand_control_set_ramp(DEFAULT_EXPAND_CONTROL_RAMP);
  expand_control_set_jitter(DEFAULT_EXPAND_CONTROL_JITTER);
  contrast_control_set_amp(DEFAULT_CONTRAST_CONTROL_AMP);
  reverb_control_set_lowpass(DEFAULT_REVERB_CONTROL_LOWPASS);
  reverb_control_set_feedback(DEFAULT_REVERB_CONTROL_FEEDBACK);

  XtVaSetValues(controls[EXPAND_HOP], XmNvalue, (int)(expand_control_hop(ss) * 1000), NULL);
  XtVaSetValues(controls[EXPAND_LENGTH], XmNvalue, (int)(expand_control_length(ss) * 1000), NULL);
  XtVaSetValues(controls[EXPAND_RAMP], XmNvalue, (int)(expand_control_ramp(ss) * 1000), NULL);
  XtVaSetValues(controls[EXPAND_JITTER], XmNvalue, (int)(expand_control_jitter(ss) * 1000), NULL);
  XtVaSetValues(controls[CONTRAST_AMP], XmNvalue, (int)(contrast_control_amp(ss) * 1000), NULL);
  XtVaSetValues(controls[REVERB_LOWPASS], XmNvalue, (int)(reverb_control_lowpass(ss) * 1000), NULL);
  XtVaSetValues(controls[REVERB_FEEDBACK], XmNvalue, (int)(reverb_control_feedback(ss) * 1000), NULL);
}

static void controls_reset_callback(Widget w, XtPointer context, XtPointer info) 
{
  if (XmGetFocusWidget(controls_dialog) == XmMessageBoxGetChild(controls_dialog, XmDIALOG_OK_BUTTON))
    reset_all_sliders();
}

static void controls_help_callback(Widget w, XtPointer context, XtPointer info) 
{
  snd_help("More controls", 
"This dialog controls all the otherwise hidden control-panel variables.\n\
Expand-hop sets the time in seconds between successive grains.\n\
Expand-length sets the length of each grain.\n\
Expand-ramp sets the ramp-time in the grain envelope.\n\
Expand-jitter sets the grain timing jitter.\n\
Contrast-amp sets the prescaler for contrast-enhancement.\n\
Reverb-lowpass sets the feedback lowpass filter coeficient.\n\
Reverb-feedback sets the scaler on the feedback.",
	   WITHOUT_WORD_WRAP);
}

static void controls_quit_callback(Widget w, XtPointer context, XtPointer info) 
{
  XtUnmanageChild(controls_dialog);
}

static void expand_hop_callback(Widget w, XtPointer context, XtPointer info)
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  expand_control_set_hop(cbs->value * 0.001);
}

static void expand_length_callback(Widget w, XtPointer context, XtPointer info)
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  expand_control_set_length(cbs->value * 0.001);
}

static void expand_ramp_callback(Widget w, XtPointer context, XtPointer info)
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  expand_control_set_ramp(cbs->value * 0.001);
}

static void expand_jitter_callback(Widget w, XtPointer context, XtPointer info)
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  expand_control_set_jitter(cbs->value * 0.001);
}

static void contrast_amp_callback(Widget w, XtPointer context, XtPointer info)
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  contrast_control_set_amp(cbs->value * 0.001);
}

static void reverb_lowpass_callback(Widget w, XtPointer context, XtPointer info)
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  reverb_control_set_lowpass(cbs->value * 0.001);
}

static void reverb_feedback_callback(Widget w, XtPointer context, XtPointer info)
{
  XmScaleCallbackStruct *cbs = (XmScaleCallbackStruct *)info;
  reverb_control_set_feedback(cbs->value * 0.001);
}

void make_controls_dialog(void)
{
  #define MSG_BOX(Dialog, Child) XmMessageBoxGetChild(Dialog, Child)
  if (!controls_dialog)
    {
      int n;
      Arg args[32];
      XmString go_away, xhelp, titlestr, xreset;
      Widget mainform, slider, reset_button;

      go_away = XmStringCreateLocalized((char *)I_GO_AWAY);
      xhelp = XmStringCreateLocalized((char *)I_HELP);
      titlestr = XmStringCreateLocalized((char *)"More controls");
      xreset = XmStringCreateLocalized((char *)"Reset");

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNhelpLabelString, xhelp); n++;
      XtSetArg(args[n], XmNokLabelString, xreset); n++;
      XtSetArg(args[n], XmNcancelLabelString, go_away); n++;
      XtSetArg(args[n], XmNautoUnmanage, false); n++;
      XtSetArg(args[n], XmNdialogTitle, titlestr); n++;
      XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
      XtSetArg(args[n], XmNnoResize, false); n++;
      XtSetArg(args[n], XmNtransient, false); n++;
      XtSetArg(args[n], XmNwidth, 400); n++;
      controls_dialog = XmCreateTemplateDialog(main_shell(ss), (char *)"More controls", args, n);
      reset_button = MSG_BOX(controls_dialog, XmDIALOG_OK_BUTTON);

      XtAddCallback(controls_dialog, XmNhelpCallback,   controls_help_callback,  NULL);
      /* XtAddCallback(controls_dialog, XmNokCallback,  controls_reset_callback, NULL); */
      XtAddCallback(reset_button, XmNactivateCallback,  controls_reset_callback, NULL);
      XtAddCallback(controls_dialog, XmNcancelCallback, controls_quit_callback,  NULL);

      XmStringFree(xhelp);
      XmStringFree(go_away);
      XmStringFree(titlestr);
      XmStringFree(xreset);

      XtVaSetValues(MSG_BOX(controls_dialog, XmDIALOG_OK_BUTTON),     XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(controls_dialog, XmDIALOG_HELP_BUTTON),   XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(controls_dialog, XmDIALOG_CANCEL_BUTTON), XmNarmColor,   ss->selection_color, NULL);
      XtVaSetValues(MSG_BOX(controls_dialog, XmDIALOG_OK_BUTTON),     XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(MSG_BOX(controls_dialog, XmDIALOG_HELP_BUTTON),   XmNbackground, ss->highlight_color, NULL);
      XtVaSetValues(MSG_BOX(controls_dialog, XmDIALOG_CANCEL_BUTTON), XmNbackground, ss->highlight_color, NULL);

      mainform = XtVaCreateManagedWidget("formd", xmRowColumnWidgetClass, controls_dialog,
					 XmNleftAttachment,   XmATTACH_FORM,
					 XmNrightAttachment,  XmATTACH_FORM,
					 XmNtopAttachment,    XmATTACH_FORM,
					 XmNbottomAttachment, XmATTACH_WIDGET,
					 XmNbottomWidget,     XmMessageBoxGetChild(controls_dialog, XmDIALOG_SEPARATOR),
					 XmNorientation,      XmVERTICAL, 
					 NULL);

      titlestr = XmStringCreateLocalized((char *)"expand hop");
      slider = XtVaCreateManagedWidget("expand-hop", xmScaleWidgetClass, mainform,
				       XmNorientation,   XmHORIZONTAL,
				       XmNshowValue,     true,
				       XmNminimum,       1,
				       XmNmaximum,       300,
				       XmNvalue,         (int)(expand_control_hop(ss) * 1000),
				       XmNdecimalPoints, 3,
				       XmNtitleString,   titlestr,
				       XmNborderWidth,   1,
				       XmNbackground,    ss->basic_color,
				       NULL);
      XmStringFree(titlestr);
      XtAddCallback(slider, XmNvalueChangedCallback, expand_hop_callback, NULL);
      XtAddCallback(slider, XmNdragCallback, expand_hop_callback, NULL);
      controls[EXPAND_HOP] = slider;

      titlestr = XmStringCreateLocalized((char *)"expand length");
      slider = XtVaCreateManagedWidget("expand-length", xmScaleWidgetClass, mainform,
				       XmNorientation,   XmHORIZONTAL,
				       XmNshowValue,     true,
				       XmNminimum,       10,
				       XmNmaximum,       500,
				       XmNvalue,         (int)(expand_control_length(ss) * 1000),
				       XmNdecimalPoints, 3,
				       XmNtitleString,   titlestr,
				       XmNborderWidth,   1,
				       XmNbackground,    ss->basic_color,
				       NULL);
      XmStringFree(titlestr);
      XtAddCallback(slider, XmNvalueChangedCallback, expand_length_callback, NULL);
      XtAddCallback(slider, XmNdragCallback, expand_length_callback, NULL);
      controls[EXPAND_LENGTH] = slider;

      titlestr = XmStringCreateLocalized((char *)"expand ramp");
      slider = XtVaCreateManagedWidget("expand-ramp", xmScaleWidgetClass, mainform,
				       XmNorientation,   XmHORIZONTAL,
				       XmNshowValue,     true,
				       XmNminimum,       10,
				       XmNmaximum,       500,
				       XmNvalue,         (int)(expand_control_ramp(ss) * 1000),
				       XmNdecimalPoints, 3,
				       XmNtitleString,   titlestr,
				       XmNborderWidth,   1,
				       XmNbackground,    ss->basic_color,
				       NULL);
      XmStringFree(titlestr);
      XtAddCallback(slider, XmNvalueChangedCallback, expand_ramp_callback, NULL);
      XtAddCallback(slider, XmNdragCallback, expand_ramp_callback, NULL);
      controls[EXPAND_RAMP] = slider;

      titlestr = XmStringCreateLocalized((char *)"expand jitter");
      slider = XtVaCreateManagedWidget("expand-hop", xmScaleWidgetClass, mainform,
				       XmNorientation,   XmHORIZONTAL,
				       XmNshowValue,     true,
				       XmNminimum,       0,
				       XmNmaximum,       200,
				       XmNvalue,         (int)(expand_control_jitter(ss) * 1000),
				       XmNdecimalPoints, 3,
				       XmNtitleString,   titlestr,
				       XmNborderWidth,   1,
				       XmNbackground,    ss->basic_color,
				       NULL);
      XmStringFree(titlestr);
      XtAddCallback(slider, XmNvalueChangedCallback, expand_jitter_callback, NULL);
      XtAddCallback(slider, XmNdragCallback, expand_jitter_callback, NULL);
      controls[EXPAND_JITTER] = slider;

      titlestr = XmStringCreateLocalized((char *)"contrast amp");
      slider = XtVaCreateManagedWidget("contrast-amp", xmScaleWidgetClass, mainform,
				       XmNorientation,   XmHORIZONTAL,
				       XmNshowValue,     true,
				       XmNminimum,       0,
				       XmNmaximum,       2000,
				       XmNvalue,         (int)(contrast_control_amp(ss) * 1000),
				       XmNdecimalPoints, 3,
				       XmNtitleString,   titlestr,
				       XmNborderWidth,   1,
				       XmNbackground,    ss->basic_color,
				       NULL);
      XmStringFree(titlestr);
      XtAddCallback(slider, XmNvalueChangedCallback, contrast_amp_callback, NULL);
      XtAddCallback(slider, XmNdragCallback, contrast_amp_callback, NULL);
      controls[CONTRAST_AMP] = slider;

      titlestr = XmStringCreateLocalized((char *)"reverb lowpass");
      slider = XtVaCreateManagedWidget("reverb-lowpass", xmScaleWidgetClass, mainform,
				       XmNorientation,   XmHORIZONTAL,
				       XmNshowValue,     true,
				       XmNminimum,       0,
				       XmNmaximum,       1000,
				       XmNvalue,         (int)(reverb_control_lowpass(ss) * 1000),
				       XmNdecimalPoints, 3,
				       XmNtitleString,   titlestr,
				       XmNborderWidth,   1,
				       XmNbackground,    ss->basic_color,
				       NULL);
      XmStringFree(titlestr);
      XtAddCallback(slider, XmNvalueChangedCallback, reverb_lowpass_callback, NULL);
      XtAddCallback(slider, XmNdragCallback, reverb_lowpass_callback, NULL);
      controls[REVERB_LOWPASS] = slider;

      titlestr = XmStringCreateLocalized((char *)"reverb feedback");
      slider = XtVaCreateManagedWidget("reverb-feedback", xmScaleWidgetClass, mainform,
				       XmNorientation,   XmHORIZONTAL,
				       XmNshowValue,     true,
				       XmNminimum,       0,
				       XmNmaximum,       1250,
				       XmNvalue,         (int)(reverb_control_feedback(ss) * 1000),
				       XmNdecimalPoints, 3,
				       XmNtitleString,   titlestr,
				       XmNborderWidth,   1,
				       XmNbackground,    ss->basic_color,
				       NULL);
      XmStringFree(titlestr);
      XtAddCallback(slider, XmNvalueChangedCallback, reverb_feedback_callback, NULL);
      XtAddCallback(slider, XmNdragCallback, reverb_feedback_callback, NULL);
      controls[REVERB_FEEDBACK] = slider;

      set_dialog_widget(CONTROLS_DIALOG, controls_dialog);
    }
  
  if (!XtIsManaged(controls_dialog))
    XtManageChild(controls_dialog);
}


/* ---------------------------------------- */

static Xen g_sound_widgets(Xen snd)
{
  #define H_sound_widgets "(" S_sound_widgets " :optional snd): a list of \
widgets: (0)pane (1)name (2)control-panel (3)status area (4)play-button (5)filter-env (6)unite-button (7)name-label (8)name-icon (9)sync-button"

  snd_info *sp;

  Snd_assert_sound(S_sound_widgets, snd, 1);

  sp = get_sp(snd);
  if (!sp)
    return(snd_no_such_sound_error(S_sound_widgets, snd));
  if (!has_widgets(sp))
    return(Xen_empty_list);

  return(Xen_cons(Xen_wrap_widget(SND_PANE(sp)),
	  Xen_cons(Xen_wrap_widget(SND_NAME(sp)),
           Xen_cons(Xen_wrap_widget(CONTROLS(sp)),
	    Xen_cons(Xen_wrap_widget(STATUS_AREA(sp)),
#if WITH_AUDIO
	     Xen_cons(Xen_wrap_widget(PLAY_BUTTON(sp)),
#else
	     Xen_cons(Xen_false,
#endif
	      Xen_cons(Xen_wrap_widget(FILTER_GRAPH(sp)), /* this is the drawingarea widget */
	       Xen_cons(Xen_wrap_widget(UNITE_BUTTON(sp)),
		Xen_cons(Xen_false,
	         Xen_cons(Xen_wrap_widget(LOCK_OR_BOMB(sp)),
	          Xen_cons(Xen_wrap_widget(SYNC_BUTTON(sp)),
	           Xen_empty_list)))))))))));
}


#define FALLBACK_FONT        "fixed"
#define DEFAULT_LISTENER_FONT "9x15"
#define DEFAULT_FONTLIST     "9x15"

#define HIGHLIGHT_COLOR      "ivory1"
#define BASIC_COLOR          "ivory2"
#define POSITION_COLOR       "ivory3"
#define ZOOM_COLOR           "ivory4"
#define CURSOR_COLOR         "red"
#define SELECTION_COLOR      "lightsteelblue1"
#define ENVED_WAVEFORM_COLOR "blue"
#define MIX_COLOR            "darkgray"
#define GRAPH_COLOR          "white"
#define SELECTED_GRAPH_COLOR "white"
#define DATA_COLOR           "black"
#define SELECTED_DATA_COLOR  "black"
#define MARK_COLOR           "red"
#define LISTENER_COLOR       "AliceBlue"
#define LISTENER_TEXT_COLOR  "black"
#define LIGHT_BLUE_COLOR     "lightsteelblue1"
#define LIGHTER_BLUE_COLOR   "AliceBlue"
#define WHITE_COLOR          "white"
#define BLACK_COLOR          "black"
#define GREEN_COLOR          "green2"
#define RED_COLOR            "red"
#define YELLOW_COLOR         "yellow"
#define TEXT_FOCUS_COLOR     "white"
#define FILTER_CONTROL_WAVEFORM_COLOR "blue"
#define SASH_COLOR           "lightgreen"
#define CHANNEL_SASH_INDENT  -10
#define CHANNEL_SASH_SIZE    0
/* 0 means: use Motif default size */

#define POSITION_SLIDER_WIDTH 13
#define ZOOM_SLIDER_WIDTH 10
#if (!__linux__) && (!_LINUX__)
  #define TOGGLE_SIZE 0
#else
  #define TOGGLE_SIZE 15
#endif
#define CHANNEL_MIN_HEIGHT 150  /* open size (set to 5 immediately thereafter) */
                                /* paned window default setup is not very smart, so we force these to be this size to begin with */
                                /* this number is only a first approximation -- we try not to expand below the screen */
                                /* if too small (i.e. 100), the scrollbars are sometimes messed up on the initial layout */

#define SASH_SIZE 16
#define SASH_INDENT -20
#define AUTO_RESIZE_DEFAULT 1

#define INITIAL_WINDOW_WIDTH 700
#define INITIAL_WINDOW_HEIGHT 300


static void window_close(Widget w, XtPointer context, XtPointer info)
{
  /* this is called from the window manager close event, not (exit) or the File:Exit item */
  snd_exit_cleanly(EXIT_FORCED);
}


static XtIntervalId auto_update_proc = 0;

static void auto_update_check(XtPointer context, XtIntervalId *id)
{
  if (auto_update_interval(ss) > 0.0)
    {
      if (!(play_in_progress()))
	for_each_sound(sound_not_current);
      auto_update_proc = XtAppAddTimeOut(main_app(ss),
					 (unsigned long)(auto_update_interval(ss) * 1000),
					 (XtTimerCallbackProc)auto_update_check,
					 context);
    }
  else auto_update_proc = 0;
}


void auto_update_restart(void)
{
  if (auto_update_proc == 0)
    auto_update_proc = XtAppAddTimeOut(main_app(ss),
				       (unsigned long)(auto_update_interval(ss) * 1000),
				       (XtTimerCallbackProc)auto_update_check,
				       (XtPointer)NULL);
}



/* handle iconification */
static Widget *iconify_active_dialogs = NULL;

static void minify_maxify_window(Widget w, XtPointer context, XEvent *event, Boolean *cont) 
{
  XMapEvent *ev = (XMapEvent *)event;
  if ((!ss) || (!(ss->dialogs)))
    return;

  /* ev->type can be several things, but the ones we care about here are
   * MapNotify and UnmapNotify.  Snd dialogs are "windows" in X-jargon, so
   * when the main window is minimized (iconified), other active dialogs
   * aren't also closed unless we mess with them explicitly here.  We keep
   * a list of created dialogs, and depend on XtIsManaged to tell us which
   * ones need to be unmanaged. But, if the user has several "desks", a change
   * of desk can also generate an unmap event, and if we dismiss the dialogs,
   * they don't come back upon remap; if we save a list of live dialogs, they
   * don't return to their previous location upon being re-managed.  We
   * need to see just iconfication events here, but there's no way I can
   * see to distinguish an iconify event from a desk event (WM_STATE atom state
   * of property changed event is identical etc), so I'll do what I can...
   * This problem may be a side-effect of using non-transient dialogs:
   * perhaps XSetTransientFor would handle this more cleanly?
   *
   * Also, ideally we'd equalize/relative-panes upon maxify, but ICCC thinks maxify
   *   is the same as map (i.e. from iconified state), and the only difference
   *   I can see in the mwm code is the window size.
   *
   * a rumor in the air that what we need is to catch StructureNotify event, then
   *   check in that for UnmapNotify
   */
  if (ev->type == UnmapNotify) 
    {
      Atom _NET_WM_STATE, _NET_WM_STATE_HIDDEN, actual_type;
      int actual_format;
      unsigned long nitems, bytes_after;
      unsigned char *prop = NULL;
      
      /* this code thanks to Tito Latini */
      _NET_WM_STATE = XInternAtom(main_display(ss), "_NET_WM_STATE", false);
      _NET_WM_STATE_HIDDEN = XInternAtom(main_display(ss), "_NET_WM_STATE_HIDDEN", false);

      if (XGetWindowProperty(main_display(ss), XtWindow(w), _NET_WM_STATE, 0, 1024,
                             false, XA_ATOM, &actual_type, &actual_format, &nitems,
                             &bytes_after, &prop) == Success)
        {
          Atom *atoms = (Atom *)prop;
          bool iconified = false;
	  unsigned long i;
          for (i = 0; i < nitems; i++)
            {
              if (atoms[i] == _NET_WM_STATE_HIDDEN)
                {
                  iconified = true;
                  break;
                }
            }
          XFree(prop);
          if (!iconified) return;
        }

      if (iconify_active_dialogs) free(iconify_active_dialogs);
      iconify_active_dialogs = (Widget *)calloc(ss->num_dialogs, sizeof(Widget));
      
      {
	int i;
	for (i = 0; i < ss->num_dialogs; i++)
	  if (ss->dialogs[i])
	    {
	      if (XtIsManaged(ss->dialogs[i]))
		iconify_active_dialogs[i] = ss->dialogs[i];
	      XtUnmanageChild(ss->dialogs[i]);
	    }
      }
    }
  else
    {
      if (ev->type == MapNotify)
	{
	  if (iconify_active_dialogs) 
	    {
	      int i;
	      for (i = 0; i < ss->num_dialogs; i++)
		if (iconify_active_dialogs[i])
		  XtManageChild(iconify_active_dialogs[i]);

	      free(iconify_active_dialogs);
	      iconify_active_dialogs = NULL;
	    }
	}
    }
}


#ifndef _MSC_VER
#include <setjmp.h>

static jmp_buf top_level_jump;

void top_level_catch(int ignore);
void top_level_catch(int ignore)
{
  siglongjmp(top_level_jump, 1);
}
#endif


static char **auto_open_file_names = NULL;
static int auto_open_files = 0;
static bool noglob = false, noinit = false, batch = false, nostdin = false;

#if HAVE_EXTENSION_LANGUAGE
static XtInputId stdin_id = 0;

static void get_stdin_string(XtPointer context, int *fd, XtInputId *id)
{
  int size;
  ssize_t bytes;
  char *buf;
  buf = (char *)calloc(1024, sizeof(char));
  size = 1024;
  bytes = read(*fd, buf, 1024);
  if (bytes <= 0) 
    {
      /* redirected to /dev/null?? -- apparently in kde/kfm the process is started without stdin? */
      XtRemoveInput(stdin_id);
      stdin_id = 0;
    }
  else
    {
      while (bytes == 1024)
	{
	  size += 1024;
	  buf = (char *)realloc(buf, size);
	  bytes = read(*fd, (char *)(buf + size - 1024), 1024);
	}
      snd_eval_stdin_str(buf);
    }
  free(buf);
}
#endif


static void startup_funcs(void)
{
  Atom wm_delete_window;
  Display *dpy;
  Widget shell;
  static int auto_open_ctr = 0;

  ss->file_monitor_ok = initialize_file_monitor();

  shell = ss->mainshell;
  dpy = main_display(ss);

#ifndef __alpha__
  add_menu_drop();
#endif

  /* trap outer-level Close for cleanup check */
  wm_delete_window = XmInternAtom(dpy, (char *)"WM_DELETE_WINDOW", false);
  XmAddWMProtocolCallback(shell, wm_delete_window, window_close, NULL);
  XtAddEventHandler(shell, StructureNotifyMask, false, minify_maxify_window, NULL);

  ss->graph_cursor = XCreateFontCursor(XtDisplay(main_shell(ss)), in_graph_cursor(ss));
  ss->wait_cursor = XCreateFontCursor(XtDisplay(main_shell(ss)), XC_watch);
  ss->bounds_cursor = XCreateFontCursor(XtDisplay(main_shell(ss)), XC_sb_h_double_arrow);
  ss->yaxis_cursor = XCreateFontCursor(XtDisplay(main_shell(ss)), XC_sb_v_double_arrow);
  ss->play_cursor = XCreateFontCursor(XtDisplay(main_shell(ss)), XC_sb_right_arrow);
  ss->loop_play_cursor = XCreateFontCursor(XtDisplay(main_shell(ss)), XC_sb_left_arrow);

#if HAVE_EXTENSION_LANGUAGE
  snd_load_init_file(noglob, noinit);
#endif

#if (!_MSC_VER) && HAVE_EXTENSION_LANGUAGE && !__MINGW32__
  if (!nostdin)
    {
      signal(SIGTTIN, SIG_IGN);
      signal(SIGTTOU, SIG_IGN);
      /* these signals are sent by a shell if we start Snd as a background process,
       * but try to read stdin (needed to support the emacs subjob connection).  If
       * we don't do this, the background job is suspended when the shell sends SIGTTIN.
       */
      stdin_id = XtAppAddInput(main_app(ss), 
			       STDIN_FILENO, 
			       (XtPointer)XtInputReadMask, 
			       get_stdin_string, 
			       NULL);
    }
#endif

  while (auto_open_ctr < auto_open_files)
    auto_open_ctr = handle_next_startup_arg(auto_open_ctr, auto_open_file_names, false, auto_open_files);

  if (ss->init_window_width > 0) set_widget_width(main_shell(ss), ss->init_window_width);
  if (ss->init_window_height > 0) set_widget_height(main_shell(ss), ss->init_window_height);
  if (ss->init_window_x != DEFAULT_INIT_WINDOW_X) set_widget_x(main_shell(ss), ss->init_window_x);
  if (ss->init_window_y != DEFAULT_INIT_WINDOW_Y) set_widget_y(main_shell(ss), ss->init_window_y);

  if (!ss->file_monitor_ok)
    {
      if (auto_update_interval(ss) > 0.0)
	XtAppAddTimeOut(main_app(ss), 
			(unsigned long)(auto_update_interval(ss) * 1000), 
			auto_update_check, 
			NULL);
    }


  if ((ss->sounds) &&
      (ss->selected_sound == NO_SELECTION))
    {
      snd_info *sp;
      sp = ss->sounds[0];
      if ((sp) && 
	  (sp->inuse == SOUND_NORMAL) &&
	  (sp->selected_channel == NO_SELECTION)) /* don't clobber possible select-channel in loaded startup files */
	select_channel(sp, 0);
    }

  if ((ss->init_window_height == 0) && (sound_style(ss) == SOUNDS_HORIZONTAL))
    set_widget_height(main_shell(ss), 200); /* otherwise it's just a title bar! */
}


static void SetupIcon(Widget shell)
{
  Display *dpy;
  Window root;
  int scr;
  Pixmap pix, mask;
  XpmAttributes attributes;
  dpy = XtDisplay(shell);
  root = DefaultRootWindow(dpy);
  scr = DefaultScreen(dpy);
  XtVaGetValues(shell, XmNdepth, &attributes.depth, XmNcolormap, &attributes.colormap, NULL);
  attributes.visual = DefaultVisual(dpy, scr);
  attributes.valuemask = XpmDepth | XpmColormap | XpmVisual;
  XpmCreatePixmapFromData(dpy, root, (char **)snd_icon_bits(), &pix, &mask, &attributes);
  if (mask) XFreePixmap(dpy, mask);
  XtVaSetValues(shell, XmNiconPixmap, pix, NULL);
}


static void muffle_warning(char *name, char *type, char *klass, char *defaultp, char **params, uint32_t *num_params)
{
}


static void notebook_page_changed_callback(Widget w, XtPointer context, XtPointer info)
{
  /* if page chosen via major tab click, select that sound */
  XmNotebookCallbackStruct *nb = (XmNotebookCallbackStruct *)info;
  Widget page;
  if ((nb->reason == XmCR_MAJOR_TAB) && (nb->page_widget))
    {
      page = nb->page_widget;
      if (page)
	{
	  int index;
	  intptr_t data;
	  XtVaGetValues(page, XmNuserData, &data, NULL);
	  index = (int)data;
	  if ((index < ss->max_sounds) && 
	      (snd_ok(ss->sounds[index])))
	    {
	      snd_info *sp;
	      sp = ss->sounds[index];
	      if (sp->selected_channel == NO_SELECTION)
		select_channel(ss->sounds[index], 0);
	      else select_channel(ss->sounds[index], sp->selected_channel);
	    }
	}
    }
}


color_t get_in_between_color(color_t fg, color_t bg)
{
  Colormap cmap;
  Display *dpy;
  int scr;
  XColor fg_color, bg_color, new_color;
  dpy = main_display(ss);
  scr = DefaultScreen(dpy);
  cmap = DefaultColormap(dpy, scr);
  fg_color.flags = DoRed | DoGreen | DoBlue;
  fg_color.pixel = fg;
  XQueryColor(dpy, cmap, &fg_color);
  bg_color.flags = DoRed | DoGreen | DoBlue;
  bg_color.pixel = bg;
  XQueryColor(dpy, cmap, &bg_color);
  new_color.flags = DoRed | DoGreen | DoBlue;
  new_color.red = (rgb_t)((fg_color.red + (2 * bg_color.red)) / 3);
  new_color.green = (rgb_t)((fg_color.green + (2 * bg_color.green)) / 3);
  new_color.blue = (rgb_t)((fg_color.blue + (2 * bg_color.blue)) / 3);
  if ((XAllocColor(dpy, cmap, &new_color)) == 0)
    return(fg);
  return(new_color.pixel);
}


static Pixel get_color(Widget shell, const char *defined_color, 
		       const char *fallback_color, const char *second_fallback_color, bool use_white)
{
  Colormap cmap;
  Display *dpy;
  int scr;
  XColor tmp_color, ignore;

  dpy = XtDisplay(shell);
  scr = DefaultScreen(dpy);
  cmap = DefaultColormap(dpy, scr);
  /* I suppose we could use XQueryColors and search for the closest available, or try yet again to get XCreateColormap to behave itself */

  if ((!XAllocNamedColor(dpy, cmap, defined_color, &tmp_color, &ignore)) &&
      ((!fallback_color) || 
       (!XAllocNamedColor(dpy, cmap, fallback_color, &tmp_color, &ignore))) &&
      ((!second_fallback_color) || 
       (!XAllocNamedColor(dpy, cmap, second_fallback_color, &tmp_color, &ignore))))
    {
      /* snd_error here causes a seg fault (it builds on mainpane which has not yet been created) */
      if (use_white)
	{
	  fprintf(stderr, "can't get color %s -- will use white\n", defined_color);
	  return(WhitePixel(dpy, scr));
	}
      else
	{
	  fprintf(stderr, "can't get color %s -- will use black\n", defined_color);
	  return(BlackPixel(dpy, scr));
	}
    }
  return(tmp_color.pixel);
}


static void save_a_color(FILE *Fp, Display *dpy, Colormap cmap, const char *name, Pixel pix, const char *ext_name)
{
#if HAVE_EXTENSION_LANGUAGE
  Status lookup_ok;
  XColor default_color, ignore;

  lookup_ok = XLookupColor(dpy, cmap, name, &default_color, &ignore);
  if (lookup_ok)
    {
      XColor current_color;
      current_color.flags = DoRed | DoGreen | DoBlue;
      current_color.pixel = pix;
      XQueryColor(dpy, cmap, &current_color);
      if ((current_color.red != default_color.red) ||
	  (current_color.green != default_color.green) ||
	  (current_color.blue != default_color.blue))

#if HAVE_FORTH
	fprintf(Fp, "%.3f %.3f %.3f %s set-%s drop\n", 
		rgb_to_float(current_color.red),
		rgb_to_float(current_color.green),
		rgb_to_float(current_color.blue),
		S_make_color,
		ext_name);
#else

#if HAVE_SCHEME
	fprintf(Fp, "(set! (%s) (%s %.3f %.3f %.3f))\n", 
#endif

#if HAVE_RUBY
	fprintf(Fp, "set_%s(%s(%.3f, %.3f, %.3f))\n", 
#endif
		to_proc_name(ext_name), 
		to_proc_name(S_make_color),
		rgb_to_float(current_color.red),
		rgb_to_float(current_color.green),
		rgb_to_float(current_color.blue));
#endif
    }
#endif
}


void save_colors(FILE *Fp)
{
  Colormap cmap;
  Display *dpy;
  int scr;

  dpy = XtDisplay(ss->mainshell);
  scr = DefaultScreen(dpy);
  cmap = DefaultColormap(dpy, scr);

  save_a_color(Fp, dpy, cmap, BASIC_COLOR,          ss->basic_color,          S_basic_color);
  save_a_color(Fp, dpy, cmap, CURSOR_COLOR,         ss->cursor_color,         S_cursor_color);
  save_a_color(Fp, dpy, cmap, DATA_COLOR,           ss->data_color,           S_data_color);
  save_a_color(Fp, dpy, cmap, SELECTED_DATA_COLOR,  ss->selected_data_color,  S_selected_data_color);
  save_a_color(Fp, dpy, cmap, HIGHLIGHT_COLOR,      ss->highlight_color,      S_highlight_color);
  save_a_color(Fp, dpy, cmap, POSITION_COLOR,       ss->position_color,       S_position_color);
  save_a_color(Fp, dpy, cmap, ZOOM_COLOR,           ss->zoom_color,           S_zoom_color);
  save_a_color(Fp, dpy, cmap, SELECTION_COLOR,      ss->selection_color,      S_selection_color);
  save_a_color(Fp, dpy, cmap, MIX_COLOR,            ss->mix_color,            S_mix_color);
  save_a_color(Fp, dpy, cmap, ENVED_WAVEFORM_COLOR, ss->enved_waveform_color, S_enved_waveform_color);
  save_a_color(Fp, dpy, cmap, LISTENER_COLOR,       ss->listener_color,       S_listener_color);
  save_a_color(Fp, dpy, cmap, LISTENER_TEXT_COLOR,  ss->listener_text_color,  S_listener_text_color);
  save_a_color(Fp, dpy, cmap, GRAPH_COLOR,          ss->graph_color,          S_graph_color);
  save_a_color(Fp, dpy, cmap, SELECTED_GRAPH_COLOR, ss->selected_graph_color, S_selected_graph_color);
  save_a_color(Fp, dpy, cmap, MARK_COLOR,           ss->mark_color,           S_mark_color);
  save_a_color(Fp, dpy, cmap, SASH_COLOR,           ss->sash_color,           S_sash_color);
  save_a_color(Fp, dpy, cmap, TEXT_FOCUS_COLOR,     ss->text_focus_color,     S_text_focus_color);
  save_a_color(Fp, dpy, cmap, FILTER_CONTROL_WAVEFORM_COLOR, ss->filter_control_waveform_color, S_filter_control_waveform_color);
}


static char *fallbacks[] = {
  (char *)"*fontList: " DEFAULT_FONTLIST,
  (char *)"*enableEtchedInMenu: True",
  (char *)"*enableThinThickness: True",
  (char *)"*enableToggleColor: True",
  (char *)"*enableToggleVisual: True",
 NULL
};


void snd_doit(int argc, char **argv)
{
  XtAppContext app;     
  Widget shell;
  Display *dpy;
  Drawable wn;
  Arg args[32];
  int i, n;
  Widget menu;
  XGCValues gv;
  char *app_title = NULL;

  int err = 0;
  XtSetLanguageProc(NULL, NULL, NULL);

  ss->channel_min_height = CHANNEL_MIN_HEIGHT;
  ss->Graph_Cursor = DEFAULT_GRAPH_CURSOR;

#ifndef __alpha__
  XtSetArg(args[0], XtNwidth, INITIAL_WINDOW_WIDTH);
  XtSetArg(args[1], XtNheight, INITIAL_WINDOW_HEIGHT);
  shell = XtAppInitialize(&app, "Snd", NULL, 0, &argc, argv, fallbacks, args, 2);
#else
  shell = XtVaOpenApplication(&app, "Snd", NULL, 0, &argc, argv, fallbacks, applicationShellWidgetClass,
			      XmNallowShellResize, AUTO_RESIZE_DEFAULT,
			      NULL);
#endif

#ifndef _MSC_VER
  setlocale(LC_NUMERIC, "C");
#endif

  /* if user has *keyboardFocusPolicy: Pointer in .Xdefaults, it can screw up Snd's attempt to
   * keep the channel graphics window as the active widget in case of keyboard input.  So,
   * we try to force Snd's focus policy to be XmEXPLICIT
   */
  XtVaGetValues(shell, XmNkeyboardFocusPolicy, &err, NULL);
  if (err != XmEXPLICIT)
    XtVaSetValues(shell, XmNkeyboardFocusPolicy, XmEXPLICIT, NULL);

  auto_open_files = argc - 1;
  if (argc > 1) auto_open_file_names = (char **)(argv + 1);

  dpy = XtDisplay(shell);

  XtVaGetValues(shell, XmNtitle, &app_title, NULL);  /* perhaps caller included -title arg */
  if (app_title) 
    ss->startup_title = mus_strdup(app_title); 
  else ss->startup_title = mus_strdup("snd");

  for (i = 1; i < argc; i++)
    if ((mus_strcmp(argv[i], "-h")) || 
	(mus_strcmp(argv[i], "-horizontal")) ||
	(mus_strcmp(argv[i], "--horizontal")))
      set_sound_style(SOUNDS_HORIZONTAL);
    else
      if ((mus_strcmp(argv[i], "-v")) || 
	  (mus_strcmp(argv[i], "-vertical")) ||
	  (mus_strcmp(argv[i], "--vertical")))
	set_sound_style(SOUNDS_VERTICAL);
      else
	if ((mus_strcmp(argv[i], "-notebook")) ||
	    (mus_strcmp(argv[i], "--notebook")))
	  {
	    set_sound_style(SOUNDS_IN_NOTEBOOK);
	    set_auto_resize(false);
	  }
	else
	  if ((mus_strcmp(argv[i], "-separate")) ||
	      (mus_strcmp(argv[i], "--separate")))
	    set_sound_style(SOUNDS_IN_SEPARATE_WINDOWS);
	  else
	    if ((mus_strcmp(argv[i], "-noglob")) ||
		(mus_strcmp(argv[i], "--noglob")))
	      noglob = true;
	    else
	      if ((mus_strcmp(argv[i], "-noinit")) ||
		  (mus_strcmp(argv[i], "--noinit")))
		noinit = true;
	      else
		if (mus_strcmp(argv[i], "-nostdin"))
		  nostdin = true;
		else
		  if ((mus_strcmp(argv[i], "-b")) || 
		      (mus_strcmp(argv[i], "-batch")) ||
		      (mus_strcmp(argv[i], "--batch")))
		    batch = true;
		  else
		    if (mus_strcmp(argv[i], "--features")) /* testing (compsnd) */
		      check_features_list(argv[i + 1]);

  ss->batch_mode = batch;
  if (batch) XtSetMappedWhenManaged(shell, 0);

  ss->zoom_slider_width = ZOOM_SLIDER_WIDTH;
  ss->position_slider_width = POSITION_SLIDER_WIDTH;
  ss->channel_sash_indent = CHANNEL_SASH_INDENT;
  ss->channel_sash_size = CHANNEL_SASH_SIZE;
  ss->sash_size = SASH_SIZE;
  ss->sash_indent = SASH_INDENT;
  ss->toggle_size = TOGGLE_SIZE;
  ss->click_time = (oclock_t)XtGetMultiClickTime(dpy);

#if HAVE_GL
  {
    /* this taken from glxmotif.c from xjournal/sgi */
    XVisualInfo *vi = NULL;
    Colormap cmap;
    GLXContext cx;
    int dblBuf[] = {GLX_RGBA, GLX_DEPTH_SIZE, 16, GLX_DOUBLEBUFFER, None};
    vi = glXChooseVisual(dpy, DefaultScreen(dpy), dblBuf);
    if (vi) 
      ss->gl_has_double_buffer = true;
    else
      {
	int snglBuf[] = {GLX_RGBA, GLX_DEPTH_SIZE, 16, None};
	ss->gl_has_double_buffer = false;
	vi = glXChooseVisual(dpy, DefaultScreen(dpy), snglBuf);
      }
    if (!vi) 
      fprintf(stderr, "%s", "no RGB visual with desired depth\n"); /* not snd_error -- shell not ready yet */
    else
      {
	/* create an OpenGL rendering context */
	cx = glXCreateContext(dpy, vi, /* no display list sharing */ None, /* favor direct */ GL_TRUE);
	if (!cx) 
	  fprintf(stderr, "%s", "could not create rendering context\n");
	else
	  {
	    /* create an X colormap since probably not using default visual */
	    cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen), vi->visual, AllocNone);
	    XtVaSetValues(shell, XtNvisual, vi->visual, XtNdepth, vi->depth, XtNcolormap, cmap, NULL);
	    ss->cx = cx;
	  }
	XFree(vi);
      }
  }
#endif

  XtAppSetWarningMsgHandler(app, muffle_warning);

  ss->mainapp = app;
  ss->mainshell = shell;
  ss->mdpy = dpy;
  ss->toolbar = NULL;

  ss->white =                   get_color(shell, WHITE_COLOR,             NULL, NULL, true);
  ss->black =                   get_color(shell, BLACK_COLOR,             NULL, NULL, false);
  ss->light_blue =              get_color(shell, LIGHT_BLUE_COLOR,        "blue", NULL, true);
  ss->lighter_blue =            get_color(shell, LIGHTER_BLUE_COLOR,      "blue", NULL, true);
  ss->red =                     get_color(shell, RED_COLOR,               NULL, NULL, false);
  ss->green =                   get_color(shell, GREEN_COLOR,             NULL, NULL, false);
  ss->blue =                    get_color(shell, "blue",                  NULL, NULL, false);
  ss->yellow =                  get_color(shell, YELLOW_COLOR,            NULL, NULL, true);
  ss->highlight_color =         get_color(shell, HIGHLIGHT_COLOR,         "gray90", NULL, true);
  ss->basic_color =             get_color(shell, BASIC_COLOR,             "gray80", "gray", true);
  ss->position_color =          get_color(shell, POSITION_COLOR,          "gray60", "blue", false);
  ss->zoom_color =              get_color(shell, ZOOM_COLOR,              "gray20", "gray", false);
  ss->cursor_color =            get_color(shell, CURSOR_COLOR,            NULL, NULL, false);
  ss->selection_color =         get_color(shell, SELECTION_COLOR,         "gray80", NULL, false);
  ss->mix_color =               get_color(shell, MIX_COLOR,               "red", NULL, false);
  ss->enved_waveform_color =    get_color(shell, ENVED_WAVEFORM_COLOR,    "red", NULL, false);
  ss->filter_control_waveform_color = get_color(shell, FILTER_CONTROL_WAVEFORM_COLOR, "blue", NULL, false);
  ss->listener_color =          get_color(shell, LISTENER_COLOR,          NULL, NULL, true);
  ss->listener_text_color =     get_color(shell, LISTENER_TEXT_COLOR,     NULL, NULL, false);
  ss->graph_color =             get_color(shell, GRAPH_COLOR,             NULL, NULL, true);
  ss->selected_graph_color =    get_color(shell, SELECTED_GRAPH_COLOR,    NULL, NULL, true);
  ss->data_color =              get_color(shell, DATA_COLOR,              NULL, NULL, false);
  ss->selected_data_color =     get_color(shell, SELECTED_DATA_COLOR,     NULL, NULL, false);
  ss->mark_color =              get_color(shell, MARK_COLOR,              "red", NULL, false);
  ss->sash_color =              get_color(shell, SASH_COLOR,              NULL, NULL, false);
  ss->text_focus_color =        get_color(shell, TEXT_FOCUS_COLOR,        NULL, NULL, true);
  ss->grid_color = get_in_between_color(ss->data_color, ss->graph_color);
  ss->selected_grid_color = get_in_between_color(ss->selected_data_color, ss->selected_graph_color);

#if HAVE_SCHEME
  s7_symbol_set_value(s7, ss->highlight_color_symbol,      Xen_wrap_pixel(ss->highlight_color));
  s7_symbol_set_value(s7, ss->basic_color_symbol,          Xen_wrap_pixel(ss->basic_color));
  s7_symbol_set_value(s7, ss->position_color_symbol,       Xen_wrap_pixel(ss->position_color));
  s7_symbol_set_value(s7, ss->zoom_color_symbol,           Xen_wrap_pixel(ss->zoom_color));
  s7_symbol_set_value(s7, ss->cursor_color_symbol,         Xen_wrap_pixel(ss->cursor_color));
  s7_symbol_set_value(s7, ss->selection_color_symbol,      Xen_wrap_pixel(ss->selection_color));
  s7_symbol_set_value(s7, ss->mix_color_symbol,            Xen_wrap_pixel(ss->mix_color));
  s7_symbol_set_value(s7, ss->enved_waveform_color_symbol, Xen_wrap_pixel(ss->enved_waveform_color));
  s7_symbol_set_value(s7, ss->filter_control_waveform_color_symbol, Xen_wrap_pixel(ss->filter_control_waveform_color));
  s7_symbol_set_value(s7, ss->listener_color_symbol,       Xen_wrap_pixel(ss->listener_color));
  s7_symbol_set_value(s7, ss->listener_text_color_symbol,  Xen_wrap_pixel(ss->listener_text_color));
  s7_symbol_set_value(s7, ss->graph_color_symbol,          Xen_wrap_pixel(ss->graph_color));
  s7_symbol_set_value(s7, ss->selected_graph_color_symbol, Xen_wrap_pixel(ss->selected_graph_color));
  s7_symbol_set_value(s7, ss->data_color_symbol,           Xen_wrap_pixel(ss->data_color));
  s7_symbol_set_value(s7, ss->selected_data_color_symbol,  Xen_wrap_pixel(ss->selected_data_color));
  s7_symbol_set_value(s7, ss->mark_color_symbol,           Xen_wrap_pixel(ss->mark_color));
  s7_symbol_set_value(s7, ss->sash_color_symbol,           Xen_wrap_pixel(ss->sash_color));
  s7_symbol_set_value(s7, ss->text_focus_color_symbol,     Xen_wrap_pixel(ss->text_focus_color));
#endif

  ss->axis_color_set = false;

  ss->orig_data_color = ss->data_color;
  ss->orig_selected_data_color = ss->selected_data_color;
  ss->orig_mark_color = ss->mark_color;
  ss->orig_mix_color = ss->mix_color;
  ss->orig_graph_color = ss->graph_color;
  ss->orig_selected_graph_color = ss->selected_graph_color;
  ss->orig_listener_color = ss->listener_color;
  ss->orig_listener_text_color = ss->listener_text_color;
  ss->orig_cursor_color = ss->cursor_color;
  ss->orig_basic_color = ss->basic_color;
  ss->orig_selection_color = ss->selection_color;
  ss->orig_zoom_color = ss->zoom_color;
  ss->orig_position_color = ss->position_color;
  ss->orig_highlight_color = ss->highlight_color;

  if ((!(set_peaks_font(DEFAULT_PEAKS_FONT))) &&
      (!(set_peaks_font(FALLBACK_FONT))))
    fprintf(stderr, "can't find peaks font %s", DEFAULT_PEAKS_FONT);

  if ((!(set_tiny_font(DEFAULT_TINY_FONT))) &&
      (!(set_tiny_font(FALLBACK_FONT))))
    fprintf(stderr, "can't find tiny font %s", DEFAULT_TINY_FONT);

  if ((!(set_bold_peaks_font(DEFAULT_BOLD_PEAKS_FONT))) &&
      (!(set_bold_peaks_font(FALLBACK_FONT))))
    fprintf(stderr, "can't find bold peaks font %s", DEFAULT_BOLD_PEAKS_FONT);

  if ((!(set_axis_label_font(DEFAULT_AXIS_LABEL_FONT))) &&
      (!(set_axis_label_font(FALLBACK_FONT))))
    fprintf(stderr, "can't find axis label font %s", DEFAULT_AXIS_LABEL_FONT);

  if ((!(set_axis_numbers_font(DEFAULT_AXIS_NUMBERS_FONT))) &&
      (!(set_axis_numbers_font(FALLBACK_FONT))))
    fprintf(stderr, "can't find axis numbers font %s", DEFAULT_AXIS_NUMBERS_FONT);

  set_listener_font(DEFAULT_LISTENER_FONT); /* we need some sort of font here! */

  ss->orig_axis_label_font = mus_strdup(axis_label_font(ss));
  ss->orig_axis_numbers_font = mus_strdup(axis_numbers_font(ss));
  ss->orig_peaks_font = mus_strdup(peaks_font(ss));
  ss->orig_bold_peaks_font = mus_strdup(bold_peaks_font(ss));
  ss->orig_listener_font = mus_strdup(listener_font(ss));
  ss->orig_tiny_font = mus_strdup(tiny_font(ss));

  XtVaSetValues(shell, XmNbackground, ss->basic_color, NULL);

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  n = attach_all_sides(args, n);
  XtSetArg(args[n], XmNallowResize, true); n++;
  ss->mainpane = XtCreateManagedWidget("mainpane", xmFormWidgetClass, shell, args, n);

  menu = add_menu();

  n = 0;
  XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
  XtSetArg(args[n], XmNtopAttachment, XmATTACH_WIDGET); n++;
  XtSetArg(args[n], XmNtopWidget, menu); n++;
  XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM); n++;
  XtSetArg(args[n], XmNallowResize, true); n++;
  XtSetArg(args[n], XmNpaneMaximum, LOTSA_PIXELS); n++; 
  switch (sound_style(ss))
    {
    case SOUNDS_IN_SEPARATE_WINDOWS:
      ss->soundpane = XtCreateManagedWidget("soundpane", xmFormWidgetClass, ss->mainpane, args, n);
      break;

    case SOUNDS_HORIZONTAL:
      XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
      ss->soundpanebox = XtCreateManagedWidget("soundpane", xmPanedWindowWidgetClass, ss->mainpane, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNsashHeight, ss->sash_size); n++;
      XtSetArg(args[n], XmNsashWidth, ss->sash_size); n++;
      XtSetArg(args[n], XmNorientation, XmHORIZONTAL); n++;
      XtSetArg(args[n], XmNsashIndent, ss->sash_indent); n++;
      ss->soundpane = XtCreateManagedWidget("soundpane", xmPanedWindowWidgetClass, ss->soundpanebox, args, n);
      break;

    case SOUNDS_VERTICAL:
      XtSetArg(args[n], XmNsashHeight, ss->sash_size); n++;
      XtSetArg(args[n], XmNsashWidth, ss->sash_size); n++;
      XtSetArg(args[n], XmNsashIndent, ss->sash_indent); n++;
      ss->soundpane = XtCreateManagedWidget("soundpane", xmPanedWindowWidgetClass, ss->mainpane, args, n);
      break;

    case SOUNDS_IN_NOTEBOOK:
      XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
      ss->soundpanebox = XtCreateManagedWidget("soundpane", xmPanedWindowWidgetClass, ss->mainpane, args, n);

      n = 0;
      XtSetArg(args[n], XmNbackground, ss->basic_color); n++;
      XtSetArg(args[n], XmNframeBackground, ss->zoom_color); n++;
      XtSetArg(args[n], XmNbindingType, XmNONE); n++;
      XtSetArg(args[n], XmNbackPagePlacement, XmTOP_RIGHT); n++;
      XtSetArg(args[n], XmNorientation, XmVERTICAL); n++;
      ss->soundpane = XtCreateWidget("nb", xmNotebookWidgetClass, ss->soundpanebox, args, n);

      {
	/* get rid of the useless spinbox */
	n = 0;
	XtSetArg(args[n], XmNnotebookChildType, XmPAGE_SCROLLER); n++;
	XtCreateWidget("scroller", xmScrollBarWidgetClass, ss->soundpane, NULL, 0);
      }
      XtManageChild(ss->soundpane);
      XtAddCallback(ss->soundpane, XmNpageChangedCallback, notebook_page_changed_callback, NULL);
      map_over_children(ss->soundpane, set_main_color_of_widget); /* appears to be a no-op */
      break;
    }

  SetupIcon(shell);
  XtRealizeWidget(shell);
  if (auto_resize(ss) != AUTO_RESIZE_DEFAULT) 
    XtVaSetValues(shell, XmNallowShellResize, auto_resize(ss), NULL);

  wn = XtWindow(shell);
  gv.background = ss->graph_color;
  gv.foreground = ss->data_color;
  ss->basic_gc = XCreateGC(dpy, wn, GCForeground | GCBackground, &gv);

  gv.background = ss->graph_color;
  gv.foreground = ss->data_color;
  ss->combined_basic_gc = XCreateGC(dpy, wn, GCForeground | GCBackground, &gv);

  gv.background = ss->graph_color;
  gv.foreground = ss->mix_color;
  ss->mix_gc = XCreateGC(dpy, wn, GCForeground | GCBackground, &gv);

  gv.function = GXxor;
  gv.background = ss->graph_color;
  gv.foreground = (Pixel)(XOR(ss->cursor_color, gv.background));
  ss->cursor_gc = XCreateGC(dpy, wn, GCForeground | GCFunction, &gv);

  gv.function = GXxor;
  gv.background = ss->graph_color;
  gv.foreground = (Pixel)(XOR(ss->selection_color, gv.background));
  ss->selection_gc = XCreateGC(dpy, wn, GCForeground | GCFunction, &gv);

  gv.function = GXxor;
  gv.background = ss->graph_color;
  gv.foreground = (Pixel)(XOR(ss->mark_color, gv.background));
  ss->mark_gc = XCreateGC(dpy, wn, GCForeground | GCFunction, &gv);

  gv.function = GXcopy;
  gv.background = ss->data_color;
  gv.foreground = ss->graph_color;
  ss->erase_gc = XCreateGC(dpy, wn, GCForeground | GCBackground | GCFunction, &gv);

  gv.background = ss->selected_graph_color;
  gv.foreground = ss->selected_data_color;
  ss->selected_basic_gc = XCreateGC(dpy, wn, GCForeground | GCBackground, &gv);

  gv.function = GXxor;
  gv.background = ss->selected_graph_color;
  gv.foreground = (Pixel)(XOR(ss->cursor_color, gv.background));
  ss->selected_cursor_gc = XCreateGC(dpy, wn, GCForeground | GCFunction, &gv);

  gv.function = GXxor;
  gv.background = ss->selected_graph_color;
  gv.foreground = (Pixel)(XOR(ss->selection_color, gv.background));
  ss->selected_selection_gc = XCreateGC(dpy, wn, GCForeground | GCFunction, &gv);

  gv.function = GXxor;
  gv.background = ss->selected_graph_color;
  gv.foreground = (Pixel)(XOR(ss->mark_color, gv.background));
  ss->selected_mark_gc = XCreateGC(dpy, wn, GCForeground | GCFunction, &gv);

  gv.function = GXcopy;
  gv.background = ss->selected_data_color;
  gv.foreground = ss->selected_graph_color;
  ss->selected_erase_gc = XCreateGC(dpy, wn, GCForeground | GCBackground | GCFunction, &gv);

  gv.function = GXcopy;
  gv.background = ss->basic_color;
  gv.foreground = ss->black;
  ss->fltenv_basic_gc = XCreateGC(dpy, wn, GCBackground | GCForeground | GCFunction, &gv);

  gv.function = GXcopy;
  gv.background = ss->basic_color;
  gv.foreground = ss->filter_control_waveform_color;
  ss->fltenv_data_gc = XCreateGC(dpy, wn, GCBackground | GCForeground | GCFunction, &gv);

  initialize_colormap(); /* X11 not ours */
  make_icons_transparent(BASIC_COLOR);

  if (with_toolbar(ss)) show_toolbar();

#ifndef _MSC_VER
  if (sigsetjmp(top_level_jump, 1))
    {
      if (!(ss->jump_ok))
	snd_error_without_format("Caught top level error (will try to continue):\n");
      else ss->jump_ok = false;
    }
  else
#endif
    startup_funcs(); /* snd /tmp -> segfault if this happens before the setjmp(top_level_jump)  */

  if (ss->startup_errors)
    {
      post_it("Error in initialization", ss->startup_errors);
      free(ss->startup_errors);
      ss->startup_errors = NULL;
    }

  XtAppMainLoop(app);
}


/* -------------------------------------------------------------------------------- */

Xen_wrap_2_optional_args(g_find_dialog_w, g_find_dialog)
Xen_wrap_no_args(g_find_dialog_widgets_w, g_find_dialog_widgets)
Xen_wrap_1_arg(reflect_file_in_enved_w, reflect_file_in_enved)
Xen_wrap_no_args(g_enved_filter_w, g_enved_filter)
Xen_wrap_1_arg(g_set_enved_filter_w, g_set_enved_filter)
Xen_wrap_no_args(g_enved_envelope_w, g_enved_envelope)
Xen_wrap_1_arg(g_set_enved_envelope_w, g_set_enved_envelope)
Xen_wrap_1_arg(reflect_file_in_region_browser_w, reflect_file_in_region_browser)
Xen_wrap_no_args(g_view_regions_dialog_w, g_view_regions_dialog)
Xen_wrap_1_arg(mix_open_file_watcher_w, mix_open_file_watcher)
Xen_wrap_1_optional_arg(insert_open_file_watcher_w, insert_open_file_watcher)
Xen_wrap_1_arg(vf_open_file_watcher_w, vf_open_file_watcher)
Xen_wrap_1_optional_arg(g_view_files_sort_w, g_view_files_sort)
Xen_wrap_2_optional_args(g_set_view_files_sort_w, g_set_view_files_sort)
Xen_wrap_2_optional_args(g_add_directory_to_view_files_list_w, g_add_directory_to_view_files_list)
Xen_wrap_2_optional_args(g_add_file_to_view_files_list_w, g_add_file_to_view_files_list)
Xen_wrap_2_optional_args(g_view_files_dialog_w, g_view_files_dialog)
Xen_wrap_1_arg(g_view_files_amp_w, g_view_files_amp)
Xen_wrap_2_args(g_view_files_set_amp_w, g_view_files_set_amp)
Xen_wrap_1_arg(g_view_files_speed_w, g_view_files_speed)
Xen_wrap_2_args(g_view_files_set_speed_w, g_view_files_set_speed)
Xen_wrap_1_arg(g_view_files_amp_env_w, g_view_files_amp_env)
Xen_wrap_2_args(g_view_files_set_amp_env_w, g_view_files_set_amp_env)
Xen_wrap_1_arg(g_view_files_speed_style_w, g_view_files_speed_style)
Xen_wrap_2_args(g_view_files_set_speed_style_w, g_view_files_set_speed_style)
Xen_wrap_1_arg(g_view_files_selected_files_w, g_view_files_selected_files)
Xen_wrap_1_arg(g_view_files_files_w, g_view_files_files)
Xen_wrap_2_args(g_view_files_set_selected_files_w, g_view_files_set_selected_files)
Xen_wrap_2_args(g_view_files_set_files_w, g_view_files_set_files)
Xen_wrap_1_arg(g_delete_file_sorter_w, g_delete_file_sorter)
Xen_wrap_2_args(g_add_file_sorter_w, g_add_file_sorter)
Xen_wrap_no_args(g_menu_widgets_w, g_menu_widgets)
Xen_wrap_no_args(g_listener_selection_w, g_listener_selection)
Xen_wrap_no_args(g_reset_listener_cursor_w, g_reset_listener_cursor)
Xen_wrap_no_args(g_goto_listener_end_w, g_goto_listener_end)
Xen_wrap_2_args(g_in_w, g_in)
Xen_wrap_no_args(g_graph_cursor_w, g_graph_cursor)
Xen_wrap_1_arg(g_set_graph_cursor_w, g_set_graph_cursor)
Xen_wrap_2_optional_args(g_channel_widgets_w, g_channel_widgets)
Xen_wrap_1_arg(reflect_file_close_in_sync_w, reflect_file_close_in_sync)
Xen_wrap_1_optional_arg(g_sound_widgets_w, g_sound_widgets)

static void add_reflect_enved_hook(void)
{
  Xen_add_to_hook_list(ss->snd_open_file_hook, reflect_file_in_enved_w, "enved-file-open-handler", "enved dialog's file-open-hook handler");
}

static void add_reflect_region_hook(void)
{
  Xen_add_to_hook_list(ss->snd_open_file_hook, reflect_file_in_region_browser_w, "region-dialog-open-file-watcher", "region dialog open-file-hook handler");
}

static void add_reflect_mix_hook(void)
{
  Xen_add_to_hook_list(ss->snd_open_file_hook, mix_open_file_watcher_w, "mix-dialog-open-file-watcher", "mix dialog's open-file-hook handler");
}

static void add_reflect_insert_hook(void)
{
  Xen_add_to_hook_list(ss->snd_open_file_hook, insert_open_file_watcher_w, "insert-dialog-open-file-watcher", "insert dialog's open-file-hook handler");
}


#if HAVE_SCHEME
static s7_pointer acc_view_files_sort(s7_scheme *sc, s7_pointer args) {return(g_set_view_files_sort(s7_cadr(args), s7_undefined(sc)));}
static s7_pointer acc_graph_cursor(s7_scheme *sc, s7_pointer args) {return(g_set_graph_cursor(s7_cadr(args)));}
#endif


  #define H_orientation_hook S_orientation_hook " (): called whenever one of the variables associated with the orientation dialog changes"
  #define H_color_hook S_color_hook " (): called whenever one of the variables associated with the color dialog changes"
  #define H_drop_hook S_drop_hook " (name): called whenever Snd receives a drag-and-drop \
event. If it returns " PROC_TRUE ", the file is not opened or mixed by Snd."
  #define H_view_files_select_hook S_view_files_select_hook "(dialog name): called when a file is selected in the \
files list of the View Files dialog.  If it returns " PROC_TRUE ", the default action, opening the file, is omitted."

  #define H_mouse_leave_label_hook S_mouse_leave_label_hook " (type position label): called when the mouse leaves a file viewer or region label"
  #define H_mouse_leave_text_hook S_mouse_leave_text_hook " (widget): called when the mouse leaves a text widget"
  #define H_listener_click_hook S_listener_click_hook " (position): called when listener clicked; position is text pos of click in listener"
  #define H_mouse_leave_listener_hook S_mouse_leave_listener_hook " (widget): called when the mouse leaves the lisp listener pane"
  
#if HAVE_SCHEME
  #define H_mouse_enter_label_hook S_mouse_enter_label_hook " (type position label): called when the mouse enters a file viewer or region label. \
The 'type' is 1 for view-files, and 2 for regions. The 'position' \
is the scrolled list position of the label. The label itself is 'label'. We could use the 'finfo' procedure in examp.scm \
to popup file info as follows: \n\
(hook-push " S_mouse_enter_label_hook "\n\
  (lambda (type position name)\n\
    (if (not (= type 2))\n\
        (" S_info_dialog " name (finfo name)))))\n\
See also nb.scm."
#endif
#if HAVE_RUBY
  #define H_mouse_enter_label_hook S_mouse_enter_label_hook " (type position label): called when the mouse enters a file viewer or region label. \
The 'type' is 1 for view-files, and 2 for regions. The 'position' \
is the scrolled list position of the label. The label itself is 'label'. We could use the 'finfo' procedure in examp.rb \
to popup file info as follows: \n\
$mouse_enter_label_hook.add_hook!(\"finfo\") do |type, position, name|\n\
  if type != 2\n\
    " S_info_dialog "(name, finfo(name))\n\
  end\n\
end\n\
See also nb.rb."
#endif
#if HAVE_FORTH
  #define H_mouse_enter_label_hook S_mouse_enter_label_hook " (type position label): called when the mouse enters a file viewer or region label. \
The 'type' is 1 for view-files, and 2 for regions. The 'position' \
is the scrolled list position of the label. The label itself is 'label'. We could use the 'finfo' procedure in examp.fs \
to popup file info as follows: \n\
" S_mouse_enter_label_hook " lambda: <{ type position name }>\n\
  type 2 <> if\n\
    name name finfo info-dialog\n\
  else\n\
    #f\n\
  then\n\
; add-hook!"
#endif

#if HAVE_SCHEME
  #define H_mouse_enter_graph_hook S_mouse_enter_graph_hook " (snd chn): called when the mouse \
enters the drawing area (graph pane) of the given channel.\n\
  (hook-push " S_mouse_enter_graph_hook "\n\
    (lambda (hook)\n\
      (" S_focus_widget " (car (" S_channel_widgets " (hook 'snd) (hook 'chn))))))"

  #define H_mouse_leave_graph_hook S_mouse_leave_graph_hook " (snd chn): is called when the mouse \
leaves the drawing area (graph pane) of the given channel."
#endif
#if HAVE_RUBY
  #define H_mouse_enter_graph_hook S_mouse_enter_graph_hook " (snd chn): called when the mouse \
enters the drawing area (graph pane) of the given channel.\n\
  $mouse_enter_graph_hook.add-hook!(\"focus\") do |snd chn|\n\
    focus_widget(channel_widgets(snd, chn)[0])\n\
    end"

  #define H_mouse_leave_graph_hook S_mouse_leave_graph_hook " (snd chn): called when the mouse \
leaves the drawing area (graph pane) of the given channel."
#endif
#if HAVE_FORTH
  #define H_mouse_enter_graph_hook S_mouse_enter_graph_hook " (snd chn): called when the mouse \
enters the drawing area (graph pane) of the given channel.\n\
" S_mouse_enter_graph_hook " lambda: <{ snd chn }>\n\
  snd chn " S_channel_widgets " car " S_focus_widget "\n\
; add-hook!"
  #define H_mouse_leave_graph_hook S_mouse_leave_graph_hook " (snd chn): is called when the mouse \
leaves the drawing area (graph pane) of the given channel."
#endif

#if HAVE_SCHEME
  #define H_mouse_enter_listener_hook S_mouse_enter_listener_hook " (widget): called when the mouse \
enters the lisp listener pane:\n\
  (hook-push " S_mouse_enter_listener_hook "\n\
    (lambda (hook)\n\
      (" S_focus_widget " (hook 'widget))))"
#endif
#if HAVE_RUBY
  #define H_mouse_enter_listener_hook S_mouse_enter_listener_hook " (listener): called when the mouse \
enters the lisp listener pane:\n\
  $mouse_enter_listener_hook.add-hook!(\"enter\") do |widget|\n\
    focus_widget(widget)\n\
  end"
#endif
#if HAVE_FORTH
  #define H_mouse_enter_listener_hook S_mouse_enter_listener_hook " (listener): called when the mouse \
enters the lisp listener pane:\n\
" S_mouse_enter_listener_hook " lambda: <{ wid }> wid " S_focus_widget " ; add-hook!"
#endif

#if HAVE_SCHEME
  #define H_mouse_enter_text_hook S_mouse_enter_text_hook " (widget): called when the mouse enters a text widget:\n\
(hook-push " S_mouse_enter_text_hook "\n\
  (lambda (w)\n\
    (" S_focus_widget " w)))"
#endif
#if HAVE_RUBY
  #define H_mouse_enter_text_hook S_mouse_enter_text_hook " (widget): called when the mouse enters a text widget:\n\
$mouse_enter_text_hook.add_hook!(\"enter\") do |w|\n\
    focus_widget(w)\n\
  end"
#endif
#if HAVE_FORTH
  #define H_mouse_enter_text_hook S_mouse_enter_text_hook " (widget): called when the mouse enters a text widget:\n\
" S_mouse_enter_text_hook " lambda: <{ wid }> wid " S_focus_widget " ; add-hook!"
#endif


void g_init_motif(void)
{
#if HAVE_SCHEME
  s7_pointer i, b, p, t, r, s, l, fnc;
  i = s7_make_symbol(s7, "integer?");
  b = s7_make_symbol(s7, "boolean?");
  p = s7_make_symbol(s7, "pair?");
  l = s7_make_symbol(s7, "list?");
  r = s7_make_symbol(s7, "real?");
  s = s7_make_symbol(s7, "string?");
  fnc = s7_make_symbol(s7, "procedure?");
  t = s7_t(s7);
#endif

  orientation_hook =          Xen_define_hook(S_orientation_hook,          "(make-hook)", 0,                        H_orientation_hook);
  color_hook =                Xen_define_hook(S_color_hook,                "(make-hook)", 0,                        H_color_hook);
  mouse_enter_label_hook =    Xen_define_hook(S_mouse_enter_label_hook,    "(make-hook 'type 'position 'label)", 3, H_mouse_enter_label_hook);
  mouse_leave_label_hook =    Xen_define_hook(S_mouse_leave_label_hook,    "(make-hook 'type 'position 'label)", 3, H_mouse_leave_label_hook);
  view_files_select_hook =    Xen_define_hook(S_view_files_select_hook,    "(make-hook 'dialog 'name)", 2,          H_view_files_select_hook);
  drop_hook =                 Xen_define_hook(S_drop_hook,                 "(make-hook 'name)", 1,                  H_drop_hook);
  mouse_enter_listener_hook = Xen_define_hook(S_mouse_enter_listener_hook, "(make-hook 'widget)", 1,                H_mouse_enter_listener_hook);
  mouse_leave_listener_hook = Xen_define_hook(S_mouse_leave_listener_hook, "(make-hook 'widget)", 1,                H_mouse_leave_listener_hook);
  mouse_enter_text_hook =     Xen_define_hook(S_mouse_enter_text_hook,     "(make-hook 'widget)", 1,                H_mouse_enter_text_hook);
  mouse_leave_text_hook =     Xen_define_hook(S_mouse_leave_text_hook,     "(make-hook 'widget)", 1,                H_mouse_leave_text_hook);
  listener_click_hook =       Xen_define_hook(S_listener_click_hook,       "(make-hook 'position)", 1,              H_listener_click_hook);
  mouse_enter_graph_hook =    Xen_define_hook(S_mouse_enter_graph_hook,    "(make-hook 'snd 'chn)", 2,              H_mouse_enter_graph_hook);
  mouse_leave_graph_hook =    Xen_define_hook(S_mouse_leave_graph_hook,    "(make-hook 'snd 'chn)", 2,              H_mouse_leave_graph_hook);


  Xen_define_typed_procedure(S_find_dialog,           g_find_dialog_w,            0, 2, 0, H_find_dialog,           s7_make_signature(s7, 3, p, b, s));
  Xen_define_typed_procedure("find-dialog-widgets",   g_find_dialog_widgets_w,    0, 0, 0, "test function",         s7_make_signature(s7, 1, l));
  Xen_define_typed_procedure(S_view_regions_dialog,   g_view_regions_dialog_w,    0, 0, 0, H_view_regions_dialog,   s7_make_signature(s7, 1, p));
  Xen_define_typed_procedure(S_view_files_dialog,     g_view_files_dialog_w,      0, 2, 0, H_view_files_dialog,     s7_make_signature(s7, 3, p, b, b));
  Xen_define_typed_procedure(S_add_file_sorter,       g_add_file_sorter_w,        2, 0, 0, H_add_file_sorter,       s7_make_signature(s7, 3, i, s, fnc));
  Xen_define_typed_procedure(S_delete_file_sorter,    g_delete_file_sorter_w,     1, 0, 0, H_delete_file_sorter,    s7_make_signature(s7, 2, i, i));
  Xen_define_typed_procedure(S_menu_widgets,          g_menu_widgets_w,           0, 0, 0, H_menu_widgets,          s7_make_signature(s7, 1, p));
  Xen_define_typed_procedure(S_listener_selection,    g_listener_selection_w,     0, 0, 0, H_listener_selection,    
			     s7_make_signature(s7, 1, s7_make_signature(s7, 2, b, s)));
  Xen_define_typed_procedure(S_reset_listener_cursor, g_reset_listener_cursor_w,  0, 0, 0, H_reset_listener_cursor, s7_make_signature(s7, 1, b));
  Xen_define_typed_procedure(S_goto_listener_end,     g_goto_listener_end_w,      0, 0, 0, H_goto_listener_end,     s7_make_signature(s7, 1, i));
  Xen_define_typed_procedure(S_channel_widgets,       g_channel_widgets_w,        0, 2, 0, H_channel_widgets,       s7_make_signature(s7, 3, p, t, t));
  Xen_define_typed_procedure(S_sound_widgets,         g_sound_widgets_w,          0, 1, 0, H_sound_widgets,         s7_make_signature(s7, 2, p, t));

  Xen_define_typed_procedure(S_add_directory_to_view_files_list, g_add_directory_to_view_files_list_w, 1, 1, 0, H_add_directory_to_view_files_list,
			     s7_make_signature(s7, 3, s, s, p));
  Xen_define_typed_procedure(S_add_file_to_view_files_list,      g_add_file_to_view_files_list_w,      1, 1, 0, H_add_file_to_view_files_list,
			     s7_make_signature(s7, 3, s, s, p));


  Xen_define_typed_dilambda(S_enved_filter, g_enved_filter_w, H_enved_filter,
			    S_set S_enved_filter, g_set_enved_filter_w, 0, 0, 1, 0, 
			    s7_make_signature(s7, 1, b), s7_make_signature(s7, 2, b, b));
  Xen_define_typed_dilambda(S_enved_envelope, g_enved_envelope_w, H_enved_envelope,
			    S_set S_enved_envelope, g_set_enved_envelope_w, 0, 0, 1, 0, 
			    s7_make_signature(s7, 1, p), s7_make_signature(s7, 2, t, t));

  Xen_define_typed_dilambda(S_view_files_amp, g_view_files_amp_w, H_view_files_amp,
			    S_set S_view_files_amp, g_view_files_set_amp_w,  1, 0, 2, 0, 
			    s7_make_signature(s7, 2, r, t), s7_make_signature(s7, 3, r, t, r));
  Xen_define_typed_dilambda(S_view_files_amp_env, g_view_files_amp_env_w, H_view_files_amp_env,
			    S_set S_view_files_amp_env, g_view_files_set_amp_env_w,  1, 0, 2, 0,
			    s7_make_signature(s7, 2, p, t), s7_make_signature(s7, 3, p, t, p));
  Xen_define_typed_dilambda(S_view_files_speed_style, g_view_files_speed_style_w, H_view_files_speed_style,
			    S_set S_view_files_speed_style, g_view_files_set_speed_style_w,  1, 0, 2, 0,
			    s7_make_signature(s7, 2, i, t), s7_make_signature(s7, 3, i, t, i));
  Xen_define_typed_dilambda(S_view_files_speed, g_view_files_speed_w, H_view_files_speed,
			    S_set S_view_files_speed, g_view_files_set_speed_w,  1, 0, 2, 0,
			    s7_make_signature(s7, 2, r, t), s7_make_signature(s7, 3, r, t, r));
  Xen_define_typed_dilambda(S_view_files_files, g_view_files_files_w, H_view_files_files,
			    S_set S_view_files_files, g_view_files_set_files_w,  1, 0, 2, 0,
			    s7_make_signature(s7, 2, l, t), s7_make_signature(s7, 3, l, t, l));
  Xen_define_typed_dilambda(S_view_files_selected_files, g_view_files_selected_files_w, H_view_files_selected_files,
			    S_set S_view_files_selected_files, g_view_files_set_selected_files_w,  1, 0, 2, 0,
			    s7_make_signature(s7, 2, l, t), s7_make_signature(s7, 3, l, t, l));
  Xen_define_typed_dilambda(S_view_files_sort, g_view_files_sort_w, H_view_files_sort,
			    S_set S_view_files_sort, g_set_view_files_sort_w,  0, 1, 1, 1,
			    s7_make_signature(s7, 2, i, t), s7_make_signature(s7, 3, i, t, i));

  Xen_define_typed_dilambda(S_graph_cursor, g_graph_cursor_w, H_graph_cursor,
			    S_set S_graph_cursor, g_set_graph_cursor_w,  0, 0, 1, 0,
			    s7_make_signature(s7, 1, i), s7_make_signature(s7, 2, i, i));


  Xen_add_to_hook_list(ss->snd_open_file_hook, vf_open_file_watcher_w, "view-files-dialog-open-file-handler", "view-files dialog open-file handler");
  Xen_add_to_hook_list(ss->snd_open_file_hook, reflect_file_close_in_sync_w, "sync-open-file-watcher", "sound sync open-file-hook handler");

  /* file-filters and file-sorters are lists from user's point of view, but I want to
   *   make sure they're gc-protected through add/delete/set, and want such code compatible
   *   with current Ruby xen macros, so I'll use an array internally.
   */
  ss->file_sorters_size = INITIAL_FILE_SORTERS_SIZE;
  ss->file_sorters = Xen_make_vector(ss->file_sorters_size, Xen_false);
  Xen_GC_protect(ss->file_sorters);


#if HAVE_SCHEME
  s7_set_setter(s7, ss->view_files_sort_symbol, s7_make_function(s7, "[acc-" S_view_files_sort "]", acc_view_files_sort, 2, 0, false, "accessor"));
  top_level_let = s7_nil(s7);
  s7_define_variable(s7, "top-level-let", 
                     s7_dilambda(s7, "top-level-let", g_top_level_let, 0, 0, g_set_top_level_let, 1, 0, "listener environment"));
  s7_set_setter(s7, ss->graph_cursor_symbol, s7_make_function(s7, "[acc-" S_graph_cursor "]", acc_graph_cursor, 2, 0, false, "accessor"));
  s7_set_documentation(s7, ss->graph_cursor_symbol, "*graph-cursor*: current graph cursor shape");
#endif

  preload_best_completions();
  Xen_define_procedure(S_in,            g_in_w,             2, 0, 0, H_in);
}
