#include "snd.h"

/* envelope editor and viewer
 *
 * TODO: 
 *    drag along x? (clipx button?)
 *    exp envs? (would need base widget -> scale?) (smoothing?) (an e button for apply as exp env?)
 *    log scale on y axis
 *    ymin/max/scale fixup during move to keep axes sensible (this is non-trivial)
 *    finish save state
 *    if click on 1st point, x axis starts at 2nd and there's no way to push back (except undo)
 *    clip should be to settable bounds
 *    load string representation into text field for edit?
 */

static Widget enved_dialog = NULL;
static Widget mainform,applyB,apply2B,cancelB,drawer,colB,colF,showB,saveB,revertB,undoB,redoB,printB,brkptL,graphB,fltB,ampB,srcB,rbrow,clipB;
static Widget nameL,textL,screnvlst,screnvname;
static GC gc,rgc,ggc;

#define AMPLITUDE_ENV 0
#define SPECTRUM_ENV 1
#define SRATE_ENV 2

static char *env_names[3] = {snd_string_amp_env_p,snd_string_flt_env_p,snd_string_src_env_p};

static int apply_to_amp = AMPLITUDE_ENV;
static int showing_all_envs = 0; /* edit one env (0), or view all currently defined envs (1) */
static int env_clipped = 0;      /* clip mouse movement at initial y bounds? */

static int env_list_size = 0;    /* current size of env edits list */
static int env_list_top = 0;     /* one past current active position in list */
static env **env_list = NULL;    /* env edits list (for local undo/redo/revert) */

static env **all_envs = NULL;    /* all envs, either loaded or created in editor */
static char **all_names = NULL;  /* parallel names */
static int all_envs_size = 0;    /* size of this array */
static int all_envs_top = 0;     /* one past pointer to last entry in this array */

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

static int *current_xs = NULL;
static int *current_ys = NULL;
static int current_size = 0;

static chan_info *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 void set_current_point(int pos, int x, int y)
{
  if (pos == current_size)
    {
      if (current_size == 0)
	{
	  current_size = 32;
	  current_xs = (int *)calloc(current_size,sizeof(int));
	  current_ys = (int *)calloc(current_size,sizeof(int));
	}
      else
	{
	  current_size += 32;
	  current_xs = (int *)realloc(current_xs,current_size * sizeof(int));
	  current_ys = (int *)realloc(current_ys,current_size * sizeof(int));
	}
    }
  current_xs[pos] = x;
  current_ys[pos] = y;
}

#define POINT_SIZE 10
#define HALF_POINT_SIZE 5
#define TINY_POINT_SIZE 4
#define HALF_TINY_POINT_SIZE 2

static int hit_point(int points, int x, int y)
{
  int i;
  for (i=0;i<points;i++)
    {
      if (((x>(current_xs[i]-POINT_SIZE)) && (x<(current_xs[i]+POINT_SIZE))) &&
	  ((y>(current_ys[i]-POINT_SIZE)) && (y<(current_ys[i]+POINT_SIZE))))
	return(i);
    }
  return(-1);
}

static int place_point(int points, int x)
{
  int i;
  for (i=0;i<points;i++)
    {
      if (x<current_xs[i]) return(i-1);
    }
  return(points);
}


static void add_point (env *e, int pos, float x, float y)
{
  int i,j;
  if (e->pts*2 == e->data_size)
    {
      e->data_size += 16;
      e->data = (float *)realloc(e->data,(e->data_size) * sizeof(float));
    }
  for (i=e->pts-1,j=(e->pts-1)*2;i>=pos;i--,j-=2)
    {
      e->data[j+2] = e->data[j];
      e->data[j+3] = e->data[j+1];
    }
  e->data[pos*2] = x;
  e->data[pos*2+1] = y;
  e->pts++;
}

static void move_point (env *e, int pos, float x, float y)
{
  e->data[pos*2] = x;
  e->data[pos*2 + 1] = y;
}

static void delete_point(env *e, int pos)
{
  int i,j;
  for (i=pos,j=pos*2;i<e->pts-1;i++,j+=2)
    {
      e->data[j] = e->data[j+2];
      e->data[j+1] = e->data[j+3];
    }
  e->pts--;
}

static chan_info *axis_cp = NULL;
static axis_info *gray_ap = NULL;

static void make_axis_cp(snd_state *ss, char *name, GC cur_gc, int ex0, int ey0, int width, int height, float xmin, float xmax, float ymin, float ymax)
{
  /* conjure up minimal context for axis drawer in snd-axis.c */
  axis_info *ap;
  axis_context *ax,*gray_ax;
  if (!axis_cp)
    {
      /* axis_cp is just a dummy chan_info struct to hold the axis_info pointer */
      axis_cp = (chan_info *)calloc(1,sizeof(chan_info));
      axis_cp->s_type = make_snd_pointer_type(CHAN_INFO);
      axis_cp->printing = 0;
      axis_cp->drawing = 1;
      axis_cp->state = ss;
      ap = (axis_info *)calloc(1,sizeof(axis_info));
      ap->s_type = make_snd_pointer_type(AXIS_INFO);
      axis_cp->axis = ap;
      ax = (axis_context *)calloc(1,sizeof(axis_context));
      ap->ax = ax;
      ap->ss = ss;
      ap->cp = axis_cp;
      ax->label_font = axis_label_FONTSTRUCT(ap);
      ax->numbers_font = axis_numbers_FONTSTRUCT(ap);
      ax->dp = XtDisplay(drawer);
      ax->wn = XtWindow(drawer);

      gray_ap = (axis_info *)calloc(1,sizeof(axis_info));
      gray_ap->s_type = make_snd_pointer_type(AXIS_INFO);
      gray_ax = (axis_context *)calloc(1,sizeof(axis_context));
      gray_ap->ax = gray_ax;
      gray_ap->ss = ss;
      gray_ax->label_font = axis_label_FONTSTRUCT(ap);
      gray_ax->numbers_font = axis_numbers_FONTSTRUCT(ap);
      gray_ax->dp = XtDisplay(drawer);
      gray_ax->wn = XtWindow(drawer);
      gray_ax->gc = ggc;
    }
  else
    {
      ap = axis_cp->axis;
      ax = ap->ax;
      if (ap->xlabel) free(ap->xlabel);
    }
  ax->gc = cur_gc;
  ap->xmin = xmin;
  ap->xmax = xmax;
  ap->ymin = ymin;
  ap->ymax = ymax;
  ap->xlabel = copy_string(name);
  ap->ylabel = NULL;
  ap->x0 = xmin;
  ap->x1 = xmax;
  ap->y0 = ymin;
  ap->y1 = ymax;
  ap->width = width;
  ap->window_width = width;
  ap->y_offset = ey0;
  ap->height = height;
  ap->graph_x0 = ex0;
  make_axes_1(axis_cp, ap, X_IN_SECONDS, 1);
  /* if this is too small for an axis, it still sets up the fields needed for grf_x|y, so tiny envelope graphs will work */
}

static void display_env(snd_state *ss, env *e, char *name, GC cur_gc, int x0, int y0, int width, int height, int dots)
{
  int i,j;
  float ex0,ey0,ex1,ey1,val;
  int ix0,ix1,iy0,iy1,size,half;
  Display *dp;
  Drawable wn;
  dp = XtDisplay(drawer);
  wn = XtWindow(drawer);
  if (e)
    {
      ex0 = e->data[0];
      ey0 = e->data[1];
      ex1 = e->data[(e->pts*2) - 2];
      ey1 = ey0;
      for (i=3;i<e->pts*2;i+=2)
	{
	  val = e->data[i];
	  if (ey0 > val) ey0 = val;
	  if (ey1 < val) ey1 = val;
	}
      if (ey0 > 0.0) ey0 = 0.0;
      if ((ey0 == ey1) && (ey1 == 0.0)) ey1 = 1.0; /* fixup degenerate case */
      if ((dots) && (ey1 < 1.0)) ey1 = 1.0;
    }
  else
    {
      ex0 = 0.0;
      ex1 = 1.0;
      ey0 = 0.0;
      ey1 = 1.0;
    }

  make_axis_cp(ss, name, cur_gc, x0, y0, width, height, ex0, ex1, ey0, ey1); 
  /* grf_x and grf_y (x|y, ap) can be used directly with XDrawLine */

  if (e)
    {
      ix1 = grf_x(e->data[0],axis_cp->axis);
      iy1 = grf_y(e->data[1],axis_cp->axis);
      if (dots)
	{
	  if (e->pts < 100)
	    {
	      size = POINT_SIZE;
	      half = HALF_POINT_SIZE;
	    }
	  else
	    {
	      size = TINY_POINT_SIZE;
	      half = HALF_TINY_POINT_SIZE;
	    }
	  set_current_point(0,ix1,iy1);
	  XFillArc(dp,wn,cur_gc,ix1-half,iy1-half,size,size,0,360*64);
	}
      for (j=1,i=2;i<e->pts*2;i+=2,j++)
	{
	  ix0 = ix1;
	  iy0 = iy1;
	  ix1 = grf_x(e->data[i],axis_cp->axis);
	  iy1 = grf_y(e->data[i+1],axis_cp->axis);
	  if (dots)
	    {
	      set_current_point(j,ix1,iy1);
	      XFillArc(dp,wn,cur_gc,ix1-half,iy1-half,size,size,0,360*64);
	    }
	  XDrawLine(dp,wn,cur_gc,ix0,iy0,ix1,iy1);
	  if (axis_cp->printing) ps_draw_line(axis_cp,ix0,iy0,ix1,iy1);
	}
    }
}

static void view_envs(snd_state *ss)
{
  /* divide space available into a grid (if needed) that shows all currently defined envelopes */
  /* I suppose it there were several hundred envelopes, we'd need a scrollable viewer... */
  int cols,rows,i,j,width,height,x,y,k;
  if (all_envs_top > 1)
    {
      cols = round(sqrt((float)(all_envs_top * env_window_width) / (float)env_window_height));
      rows = round((float)all_envs_top/(float)cols);
      if ((rows*cols) < all_envs_top) rows++;
    }
  else
    {
      cols = 1;
      rows = 1;
    }
  width = (int)((float)env_window_width/(float)cols);
  height = (int)((float)env_window_height/(float)rows);
  k=0;
  for (i=0,x=0;i<cols;i++,x+=width)
    {
      for (j=0,y=0;j<rows;j++,y+=height)
	{
	  display_env(ss,all_envs[k],all_names[k],(selected_env == all_envs[k]) ? rgc : gc,x,y,width,height,0);
	  k++;
	  if (k == all_envs_top) return;
	}
    }
}

static int hit_env(int xe, int ye)
{
  int cols,rows,i,j,width,height,x,y,k;
  if (all_envs_top == 0)
    return(-1);
  else
    {
      if (all_envs_top == 1)
	return(0);
      else
	{
	  cols = round(sqrt((float)(all_envs_top * env_window_width) / (float)env_window_height));
	  rows = round((float)all_envs_top/(float)cols);
	  if ((rows*cols) < all_envs_top) rows++;
	  width = (int)((float)env_window_width/(float)cols);
	  height = (int)((float)env_window_height/(float)rows);
	  k=0;
	  for (i=0,x=width;i<cols;i++,x+=width)
	    {
	      if (x > xe)
		{
		  for (j=0,y=height;j<rows;j++,y+=height)
		    {
		      if (y > ye) return(k);
		      k++;
		    }
		}
	      else k+=rows;
	    }
	}
    }
  return(0);
}

static void do_env_edit(env *new_env, int loading)
{
  int i;
  if (env_list_top == env_list_size)
    {
      env_list_size += 16;
      if (env_list)
	{
	  env_list = (env **)realloc(env_list,env_list_size * sizeof(env *));
	  for (i=env_list_top;i<env_list_size;i++) env_list[i] = NULL;
	}
      else env_list = (env **)calloc(env_list_size,sizeof(env *));
    }
  /* clear out current edit list above this edit */
  for (i=env_list_top;i<env_list_size;i++)
    {
      if (env_list[i]) env_list[i] = free_env(env_list[i]);
    }
  env_list[env_list_top] = copy_env(new_env);
  env_list_top++;
  if (!loading)
    {
      XtSetSensitive(undoB,TRUE);
      XtSetSensitive(redoB,FALSE);
      XtSetSensitive(saveB,TRUE);
      XtSetSensitive(revertB,TRUE);
    }
}

static void redo_env_edit(void)
{
  if (env_list)
    {
      if ((env_list_top < env_list_size) && (env_list[env_list_top])) 
	{
	  env_list_top++;
	  XtSetSensitive(undoB,TRUE);
	  XtSetSensitive(revertB,TRUE);
	}
      if ((env_list_top == env_list_size) || (env_list[env_list_top] == NULL)) 
	XtSetSensitive(redoB,FALSE);
      XtSetSensitive(saveB,TRUE);
    }
}

static void undo_env_edit(void)
{
  if (env_list)
    {
      if (env_list_top > 0)
	{
	  env_list_top--;
	  XtSetSensitive(redoB,TRUE);
	}
      if (env_list_top == 0)
	{
	  XtSetSensitive(undoB,FALSE);
	  XtSetSensitive(revertB,FALSE);
	}
      XtSetSensitive(saveB,TRUE);
    }
}

static void revert_env_edit(void)
{
  if (env_list)
    {
      if (env_list_top > 1) 
	env_list_top = 1; 
      else 
	{
	  env_list_top = 0;
	  XtSetSensitive(undoB,FALSE);
	  XtSetSensitive(revertB,FALSE);
	  XtSetSensitive(saveB,FALSE);
	}
    }
}


static int find_env(char *name)
{ /* -1 upon failure */
  int i;
  if (all_envs)
    {
      for (i=0;i<all_envs_top;i++)
	{
	  if (strcmp(name,all_names[i]) == 0) return(i);
	}
    }
  return(-1);
}

static void make_scrolled_env_list (snd_state *ss)
{
  XmString *strs;
  int n;
  XtVaSetValues(screnvlst,XmNbackground,(ss->sgx)->high,NULL); 
  strs = (XmString *)calloc(all_envs_top,sizeof(XmString)); 
  for (n=0;n<all_envs_top;n++) strs[n] = XmStringCreate(all_names[n],"button_font");
  XtVaSetValues(screnvlst,XmNitems,strs,XmNitemCount,all_envs_top,NULL);
  for (n=0;n<all_envs_top;n++) XmStringFree(strs[n]);
  free(strs);
}

static void add_envelope(snd_state *ss, char *name, env *val)
{
  if (all_envs_top == all_envs_size)
    {
      all_envs_size += 16;
      if (all_envs)
	{
	  all_envs = (env **)realloc(all_envs,all_envs_size * sizeof(env *));
	  all_names = (char **)realloc(all_names,all_envs_size * sizeof(char *));
	}
      else
	{
	  all_envs = (env **)calloc(all_envs_size,sizeof(env *));
	  all_names = (char **)calloc(all_envs_size,sizeof(char *));
	}
    }
  all_envs[all_envs_top] = val;
  all_names[all_envs_top] = name;
  all_envs_top++;
  if (enved_dialog)
    {
      XtSetSensitive(showB,TRUE);
      make_scrolled_env_list(ss);
    }
}

void alert_envelope_editor(snd_state *ss, char *name, env *val)
{
  /* whenever an envelope is defined or setf'd, we get notification through this function */
  int i;
  i = find_env(name);
  if (i != -1)
    all_envs[i] = val;
  else add_envelope(ss,name,val);
}

void alert_enved_amp_env(snd_info *sp)
{
  snd_state *ss;
  ss = sp->state;
  if ((enved_dialog) && (active_channel) && (ss->env_graphing) && (XtIsManaged(enved_dialog)))
    {
      if (active_channel->sound == sp) env_redisplay(sp->state);
    }
}

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

static void Dismiss_Enved_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  if (ss->checking_explicitly)
    ss->stopped_explicitly = 1;
  else XtUnmanageChild(enved_dialog);
}

static void Help_Enved_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,"Envelope Editor",get_envelope_editor_help());
}

static void apply_enved(snd_state *ss)
{
  char *str;
  XmString s1;
  if (active_env)
    {
      active_channel = current_channel((void *)ss);
      if (active_channel)
	{
	  XtSetSensitive(applyB,FALSE);
	  XtSetSensitive(apply2B,FALSE);
	  s1 = XmStringCreate(snd_string_Stop,XmFONTLIST_DEFAULT_TAG);
	  XtVaSetValues(cancelB,XmNlabelString,s1,NULL);
	  XmStringFree(s1);
	  check_for_event(ss);
	  switch (apply_to_amp)
	    {
	    case AMPLITUDE_ENV:
	      apply_env(active_channel,active_env,0,current_ed_samples(active_channel),1.0,0,TRUE); 
	      /* calls update_graph, I think, but in short files that doesn't update the amp-env */
	      if (ss->env_graphing) env_redisplay(ss);
	      break;
	    case SPECTRUM_ENV: 
	      apply_filter(active_channel,ss->filter_env_order,active_env,TRUE);
	      break;
	    case SRATE_ENV:
	      str = env_to_string(active_env);
	      src(ss,str,TRUE);
	      if (ss->env_graphing) env_redisplay(ss);
	      free(str);
	      break;
	    }
	  XtSetSensitive(applyB,TRUE);
	  XtSetSensitive(apply2B,TRUE);
	  s1 = XmStringCreate(snd_string_Dismiss,XmFONTLIST_DEFAULT_TAG);
	  XtVaSetValues(cancelB,XmNlabelString,s1,NULL);
	  XmStringFree(s1);
	}
    }
}

void env_redisplay(snd_state *ss)
{
  char *name;
  int samps,srate,pts;
  float samps_per_pixel;
  axis_info *ap,*active_ap;
  XClearWindow(XtDisplay(drawer),XtWindow(drawer));
  if (showing_all_envs) 
    view_envs(ss);
  else 
    {
      name = XmTextGetString(textL);
      if (!name) name = "noname";
      display_env(ss,active_env,name,gc,0,0,env_window_width,env_window_height,1);
      if (ss->env_graphing)
	{
	  active_channel = current_channel(ss);
	  if (active_channel)
	    {
	      if (active_channel->amp_env)
		{
		  /* show current channel overall view in gray scale */
		  ap = axis_cp->axis;
		  samps = current_ed_samples(active_channel);
		  srate = snd_SRATE(active_channel);
		  active_ap = active_channel->axis;
		  samps_per_pixel = (float)samps / (float)(ap->x_axis_x1 - ap->x_axis_x0);
		  gray_ap->x0 = 0.0;
		  gray_ap->x1 = (float)samps / (float)srate;
		  gray_ap->losamp = 0;
		  gray_ap->hisamp = samps-1;
		  gray_ap->y0 = active_ap->y0;
		  gray_ap->y1 = active_ap->y1;
		  gray_ap->x_axis_x0 = ap->x_axis_x0;
		  gray_ap->x_axis_x1 = ap->x_axis_x1;
		  gray_ap->y_axis_y0 = ap->y_axis_y0;
		  gray_ap->y_axis_y1 = ap->y_axis_y1;
		  gray_ap->x_scale = ((double)(gray_ap->x_axis_x1 - gray_ap->x_axis_x0))/((double)(gray_ap->x1 - gray_ap->x0));
		  gray_ap->y_scale = (gray_ap->y_axis_y1 - gray_ap->y_axis_y0)/(gray_ap->y1 - gray_ap->y0);
		  pts = amp_env_graph(active_channel,gray_ap,samps_per_pixel,srate);
		  draw_both_grfs(gray_ap->ax,pts);
		}
	      else
		{
		  start_amp_env(active_channel->sound,1); /* a no-op if one already in progress, will callback to us upon completion */
		}
	    }
	}
    }
}

static void save_button_pressed(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  char *name,*str;
  env *e = NULL;
  int err = 0;
  name = XmTextGetString(textL);
  if ((!name) || (!(*name))) 
    name = copy_string("unnamed");
  else
    {
      str = name;
      while (isspace(*str)) str++;
      e = find_lv_env(str);
      if (!e) e = scan_envelope_and_report_error(NULL,str,&err); /* null sp turns off error report */
      if (e) 
	{
	  if (active_env) active_env = free_env(active_env);
	  active_env = copy_env(e);
	  env_list_top = 0;
	  do_env_edit(active_env,TRUE);
	}
    }
  if (!e)
    {
      alert_envelope_editor(ss,name,copy_env(active_env));
      alert_lv_env(name,copy_env(active_env));
    }
  XtSetSensitive(saveB,FALSE);
  env_redisplay(ss);
}

static void save_button_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Save Current Envelope",
"The envelope in the editor is not saved until you\n\
press the 'save' button.  Once saved, it can be\n\
used in other portions of Snd (for example, as the\n\
argument to C-x C-a), or saved to a file via the\n\
Session dialog Save option.  Similarly, a file\n\
of CLM (lisp) envelope definitions can be loaded\n\
using the Session dialog's Load option.\n\
");
}

static void Apply_Enved_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  /* apply current envs to currently sync'd channels */
  snd_state *ss = (snd_state *)clientData;
  state_context *sgx;
  XmAnyCallbackStruct *cb = (XmAnyCallbackStruct *)callData;
  sgx = ss->sgx;
  /* since this is the OK button from Motif's point of view, text activation (<cr> in the text field)
   * causes a bogus activation of this callback; we trap that by looking for text_activate_event.
   * cr in text => save under that name
   */
  if (cb->event != sgx->text_activate_event)
    apply_enved((snd_state *)clientData);
  else save_button_pressed(saveB,clientData,callData);
}

static void Undo_and_Apply_Enved_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  /* undo upto 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 */
  snd_state *ss = (snd_state *)clientData;
  if ((active_channel) && (active_channel == current_channel((void *)ss)))
    {
      undo_EDIT((void *)ss,1);
    }
  apply_enved(ss);
}

static void Undo_and_Apply_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Undo and Apply",
"This button first tries to undo a previous apply\n\
then calls apply, the point obviously being to make\n\
it easy to retry an envelope after some minor change.\n\
");
}

static void select_or_edit_env(snd_state *ss, int pos)
{
  XmString s1;
  if (selected_env == all_envs[pos]) 
    {
      if (showing_all_envs)
	{
	  showing_all_envs = 0;
	  s1=XmStringCreateLtoR(snd_string_view_envs,"button_font");
	  XtVaSetValues(showB,XmNlabelString,s1,NULL);
	  XmStringFree(s1);
	}
      if (active_env) active_env = free_env(active_env);
      active_env = copy_env(all_envs[pos]);
      XmTextSetString(textL,all_names[pos]);
      env_list_top = 0;
      do_env_edit(active_env,TRUE);
      XtSetSensitive(undoB,FALSE);
      XtSetSensitive(revertB,FALSE);
      XtSetSensitive(saveB,FALSE);
      env_redisplay(ss);
    }
  else
    {
      selected_env = all_envs[pos];
      if (showing_all_envs) view_envs(ss);
    }
}

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

static char brkpt_buf[32];

static void display_point_label(float x, float y)
{
  XmString s1;
  sprintf(brkpt_buf,"%.3f : %.3f",x,y);
  s1=XmStringCreate(brkpt_buf,"button_font");
  XtVaSetValues(brkptL,XmNlabelString,s1,NULL);
  XmStringFree(s1);
}

void display_enved_progress(snd_state *ss, char *str)
{
  XmString s1;
  s1=XmStringCreate(str,"button_font");
  XtVaSetValues(brkptL,XmNlabelString,s1,NULL);
  XmStringFree(s1);
  check_for_event(ss);
}

static Time down_time;
static int env_dragged = 0;
static int env_pos = 0;
static int click_to_delete = 0;

static void drawer_button_motion(Widget w,XtPointer clientData,XEvent *event,Boolean *cont) 
{
  XMotionEvent *ev = (XMotionEvent *)event;
  Time motion_time;
  axis_info *ap;
  float x0,x1,x,y;
  if (!showing_all_envs)
    {
      motion_time = ev->time;
      if ((motion_time - down_time) < (0.5 * XtGetMultiClickTime(XtDisplay(w)))) return;
      env_dragged = 1;
      click_to_delete = 0;
      ap = axis_cp->axis;
      x = ungrf_x(ap,ev->x);
      if (env_pos > 0) x0 = active_env->data[env_pos*2-2]; else x0 = 0.0;
      if (env_pos < active_env->pts) x1 = active_env->data[env_pos*2+2]; else x1 = 1.0;
      if (x<x0) x=x0;
      if (x>x1) x=x1;
      if (env_pos == 0) x = active_env->data[0];
      if (env_pos == (active_env->pts-1)) x = active_env->data[(active_env->pts-1)*2];
      y = ungrf_y(ap,ev->y);
      if (env_clipped)
	{
	  if (y<ap->y0) y=ap->y0;
	  if (y>ap->y1) y=ap->y1;
	}
      move_point(active_env,env_pos,x,y);
      display_point_label(x,y);
      env_redisplay((snd_state *)clientData);
    }
}

static void drawer_button_press(Widget w,XtPointer clientData,XEvent *event,Boolean *cont) 
{
  XButtonEvent *ev = (XButtonEvent *)event;
  snd_state *ss = (snd_state *)clientData;
  int pos;
  float x,y;
  down_time = ev->time;
  env_dragged = 0;
  if (showing_all_envs)
    {
      pos = hit_env(ev->x,ev->y);
      XmListSelectPos(screnvlst,pos+1,FALSE);
      if ((pos >= 0) && (pos < all_envs_top)) select_or_edit_env((snd_state *)clientData,pos);
    }
  else
    {
      if (!active_env)
	{
	  active_env = (env *)calloc(1,sizeof(env));
	  active_env->data = (float *)calloc(4,sizeof(float));
	  active_env->data_size = 4;
	  active_env->pts = 2;
	  active_env->data[0] = 0.0; active_env->data[1] = 0.0; active_env->data[2] = 1.0; active_env->data[3] = 0.0;
	  env_redisplay(ss); /* needed to get current_xs set up correctly */
	}
      pos = hit_point(active_env->pts,ev->x,ev->y);
      x = ungrf_x(axis_cp->axis,ev->x);
      y = ungrf_y(axis_cp->axis,ev->y);
      env_pos = pos;
      /* if not -1, then user clicked existing point -- wait for drag/release to decide what to do */
      if (pos == -1) 
	{
	  pos = place_point(active_env->pts,ev->x);
	  /* place returns left point index of current segment or pts if off left end */
	  /* in this case, user clicked in middle of segment, so add point there */
	  add_point(active_env,pos+1,x,y);
	  env_pos = pos+1;
	  click_to_delete = 0;
	  env_redisplay(ss);
	}
      else click_to_delete = 1;
      display_point_label(x,y);
    }
}

static void drawer_button_release(Widget w,XtPointer clientData,XEvent *event,Boolean *cont) 
{
  if (!showing_all_envs)
    {
      if ((click_to_delete) && (!env_dragged))
	{
	  delete_point(active_env,env_pos);
	}
      do_env_edit(active_env,FALSE);
      env_pos = 0;
      env_dragged = 0;
      click_to_delete = 0;
      env_redisplay((snd_state *)clientData);
      clear_point_label();
    }
}

static void drawer_resize(Widget w,XtPointer clientData,XtPointer callData) 
{
  /* update display, can be either view of all envs or sequence of current envs */
  env_window_width = get_window_width(w);
  env_window_height = get_window_height(w);
  env_redisplay((snd_state *)clientData);
}

static void Drawer_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Envelope Editor Display",
"This portion of the envelope editor displays either\n\
the envelope currently being edited, or all currently\n\
loaded envelopes.  In the latter case, click an envelope\n\
to select it, click the selected envelope to load it into\n\
the editor portion.  In the former case, click anywhere in\n\
the graph to place a new breakpoint at that point; click\n\
an existing point to delete it; drag an existing point to\n\
move it.  Put some envelope name in the text field and click\n\
the 'save' button to save the current envelope under the\n\
chosen name.  Unlimited undo/redo are available through the\n\
'undo' and 'redo' buttons.  Set the 'w' button to see the\n\
currently active sound under the envelope; set the 'f' button\n\
to apply the envelope to the sound's frequency, rather than\n\
amplitude; set the 'c' button to clip mouse motion to the current\n\
y axis bounds.\n\
");
}

static void Text_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Envelope Name",
"To load an exisiting envelope into the editor,\n\
type its name in this field; to give a name to\n\
the envelope as it is currently defined in the\n\
graph viewer, type its name in this field, then\n\
either push return or the 'save' button; to define\n\
a new envelope by its break point values, give the\n\
values in this field as though it were a defvar\n\
call in M-X -- that is, '(0 0 1 1)<cr> in this\n\
field fires up a ramp as a new envelope.\n\
");
}

static void Scrolled_List_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Loaded Envelope Names",
"This is the list of all currently loaded envelopes\n\
by name.  When a new envelope is saved, its name is\n\
added to the end of the list.  Click a name to select\n\
that envelope; click the selected envelope to load it\n\
into the editor.  The selected envelope is displayed in\n\
red.\n\
");
}

static void Brkpt_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Breakpoint Data",
"This portion ofthe envelope editor displays the\n\
current breakpoint values while the mouse is dragging\n\
a point\n\
");
}

static void show_button_pressed(Widget w,XtPointer clientData,XtPointer callData) 
{
  /* if show all (as opposed to show current), loop through loaded LV_LISTs */
  XmString s1;
  showing_all_envs = (!showing_all_envs);
  s1=XmStringCreateLtoR((showing_all_envs) ? snd_string_edit_env : snd_string_view_envs,"button_font");
  XtVaSetValues(showB,XmNlabelString,s1,NULL);
  XmStringFree(s1);
  env_redisplay((snd_state *)clientData);
}

static void show_button_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "View Envs or Edit Env",
"There are two sides to the Envelope Editor, the\n\
editor itself, and an envelope viewer.  This button\n\
chooses which is active.  In the envelope viewer,\n\
all currently loaded envelopes are displayed, and\n\
you can select one by clicking it; click the selected\n\
envelope to load it into the editor portion.  The\n\
other choice is the editor which displays the current\n\
state of the envelope being edited.\n\
");
}

static void revert_button_pressed(Widget w,XtPointer clientData,XtPointer callData) 
{
  revert_env_edit();
  if (active_env) active_env = free_env(active_env);
  if (env_list_top > 0) active_env = copy_env(env_list[env_list_top-1]); else active_env = NULL;
  env_redisplay((snd_state *)clientData);
}

static void revert_button_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Revert to Original Envelope",
"The revert button undoes all the edits back to\n\
the original state, and if clicked again at that\n\
point, clears the editor to a clean state (that\n\
is, the envelope '(0 0 1 0)).\n\
");
}

static void undo_button_pressed(Widget w,XtPointer clientData,XtPointer callData) 
{
  undo_env_edit();
  if (active_env) active_env = free_env(active_env);
  if (env_list_top > 0) active_env = copy_env(env_list[env_list_top-1]); else active_env = NULL;
  env_redisplay((snd_state *)clientData);
}

static void undo_button_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Undo Edit",
"The undo button undoes the previous edit.  The\n\
list of edits is not cleared until you changed to\n\
a new envelope, so you can undo and redo every edit\n\
without limitation.\n\
");
}

static void redo_button_pressed(Widget w,XtPointer clientData,XtPointer callData) 
{
  redo_env_edit();
  if (active_env) active_env = free_env(active_env);
  if (env_list_top > 0) active_env = copy_env(env_list[env_list_top-1]); else active_env = NULL;
  env_redisplay((snd_state *)clientData);
}

static void redo_button_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Redo Edit",
"The redo button redoes an edit that was previously\n\
undone.  There is no limit on the number of edits\n\
that can be undone and redone.\n\
");
}

static void reflect_apply_state (snd_state *ss)
{
  XmString s1;
  s1=XmStringCreateLtoR(env_names[apply_to_amp],XmFONTLIST_DEFAULT_TAG);
  XtVaSetValues(nameL,XmNlabelString,s1,NULL);
  XmStringFree(s1);
  XmChangeColor(ampB,(apply_to_amp == AMPLITUDE_ENV) ? ((Pixel)(ss->sgx)->mixpix) : ((Pixel)(ss->sgx)->high));
  XmChangeColor(fltB,(apply_to_amp == SPECTRUM_ENV) ? ((Pixel)(ss->sgx)->mixpix) : ((Pixel)(ss->sgx)->high));
  XmChangeColor(srcB,(apply_to_amp == SRATE_ENV) ? ((Pixel)(ss->sgx)->mixpix) : ((Pixel)(ss->sgx)->high));
}

static void amp_button_pressed(Widget w,XtPointer clientData,XtPointer callData) 
{
  apply_to_amp++;
  if (apply_to_amp > SRATE_ENV) apply_to_amp = AMPLITUDE_ENV;
  reflect_apply_state((snd_state *)clientData);
}

static void amp_button_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Env Label",
"If you click the envelope name label, it\n\
is equivalent to pushing the next button\n\
in the sequence amp, flt, src; that is,\n\
it chooses the next in the list of possible\n\
envelope applications.\n\
");
}

static void Freq_Button_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  apply_to_amp = SPECTRUM_ENV;
  reflect_apply_state((snd_state *)clientData);
}

static void Freq_Button_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Apply Envelope to Spectrum",
"When the 'Apply' button is pressed, the envelope\n\
in the editor window is applied to the current sound\n\
and any sounds sync'd to it. If this button is set,\n\
the envelope is interpreted as a frequency response\n\
curve, and used to design an FIR filter that is then\n\
applied to the current sound. The order of the filter\n\
is determined by the variable filter-env-order\n\
which defaults to 40.\n\
");
}

static void Amp_Button_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  apply_to_amp = AMPLITUDE_ENV;
  reflect_apply_state((snd_state *)clientData);
}

static void Amp_Button_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Apply Envelope to Amplitude",
"When the 'Apply' button is pressed, the envelope\n\
in the editor window is applied to the current sound\n\
and any sounds sync'd to it. If this button is set,\n\
the envelope is interpreted as a amplitude envelope.\n\
");
}

static void Src_Button_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  apply_to_amp = SRATE_ENV;
  reflect_apply_state((snd_state *)clientData);
}

static void Src_Button_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Apply Envelope to Srate",
"When the 'Apply' button is pressed, the envelope\n\
in the editor window is applied to the current sound\n\
and any sounds sync'd to it. If this button is set,\n\
the envelope is applied to the sampling rate.\n\
");
}

void env_print(snd_state *ss, char *name)
{
  enved_print(name,axis_cp,env_window_height);
}

static void print_button_pressed(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  ss->print_choice = PRINT_ENV;
  File_Print_Callback(w,clientData,callData);
}

static void print_button_help_callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Print Envelopes",
"The print button causes the current envelope editor\n\
display (whether the envelope being edited, or all the\n\
envelopes currently loaded) to be saved in a postscript\n\
file named aaa.eps.  You can send this to a printer with\n\
lpr.\n\
");
}

static void env_browse_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  XmListCallbackStruct *cb = (XmListCallbackStruct *)callData;
  select_or_edit_env((snd_state *)clientData,cb->item_position - 1);
}

static void Graph_Button_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)callData;
  snd_state *ss = (snd_state *)clientData; 
  ss->env_graphing = cb->set;
  env_redisplay(ss);
}

static void Graph_Button_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Show Current Active Channel",
"This button, if set, causes a light-colored version\n\
of the current channel's overall data to be displayed\n\
in the envelope editor window, making it easier to\n\
design an envelope to fit the exact contours of the\n\
sound.  Since the envelope is always applied to the\n\
entire sound, the x-axis bounds in the envelope editor\n\
are arbitrary.\n\
");
}

static void Clip_Button_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)callData; 
  env_clipped = cb->set;
}

static void Clip_Button_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Y Axis Clipped",
"This button, if set, causes break point motion\n\
in the y direction to be clipped at the current\n\
axis bounds.  If unset, the bounds will try to\n\
change to accomodate the current mouse position.\n\
");
}

int get_env_clipping(void) {return(env_clipped);}
void set_env_clipping(int val) {env_clipped = val; if (enved_dialog) XmToggleButtonSetState(clipB,val,FALSE);}
int get_env_style(void) {return(apply_to_amp);}
void set_env_style(snd_state *ss, int val) {apply_to_amp = val; if (enved_dialog) reflect_apply_state(ss);}
void set_env_graphing(snd_state *ss, int val) {ss->env_graphing = val; if (enved_dialog) XmToggleButtonSetState(graphB,val,FALSE);}

void create_envelope_editor (snd_state *ss)
{
  int n;
  Arg args[32];
  XmString xhelp,xdismiss,xapply,titlestr,s1;
  XGCValues gv;
  Display *dpy;
  int scr;

  if (!enved_dialog)
    {

      xdismiss = XmStringCreate(snd_string_Dismiss,XmFONTLIST_DEFAULT_TAG);
      xhelp = XmStringCreate(snd_string_Help,XmFONTLIST_DEFAULT_TAG);
      titlestr = XmStringCreateLocalized(snd_string_Edit_Envelope);
      xapply = XmStringCreateLocalized(snd_string_Apply);

      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      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++;

#if RESIZE_DIALOG
      XtSetArg(args[n],XmNnoResize,FALSE); n++;
#endif
#if UNSET_TRANSIENT
      XtSetArg(args[n],XmNtransient,FALSE); n++;
#endif
      enved_dialog = XmCreateTemplateDialog(main_SHELL(ss),snd_string_envelope_editor,args,n);
#if UNSET_TRANSIENT
      add_dialog(ss,enved_dialog);
#endif
  
      XtAddCallback(enved_dialog,XmNcancelCallback,Dismiss_Enved_Callback,ss);
      XtAddCallback(enved_dialog,XmNhelpCallback,Help_Enved_Callback,ss);
      XtAddCallback(enved_dialog,XmNokCallback,Apply_Enved_Callback,ss);

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

      if (!(ss->using_schemes))
	{
	  XtVaSetValues(XmMessageBoxGetChild(enved_dialog,XmDIALOG_CANCEL_BUTTON),XmNarmColor,(ss->sgx)->text,NULL);
	  XtVaSetValues(XmMessageBoxGetChild(enved_dialog,XmDIALOG_HELP_BUTTON),XmNarmColor,(ss->sgx)->text,NULL);
	  XtVaSetValues(XmMessageBoxGetChild(enved_dialog,XmDIALOG_OK_BUTTON),XmNarmColor,(ss->sgx)->text,NULL);
	}

      n=0;
      if (!(ss->using_schemes)) 
	{
	  n = background_main_color(args,n,ss);
	  XtSetArg(args[n],XmNarmColor,(ss->sgx)->text); n++;
	}
      apply2B = XtCreateManagedWidget(snd_string_Undo_and_Apply,xmPushButtonWidgetClass,enved_dialog,args,n);
      XtAddCallback(apply2B,XmNactivateCallback,Undo_and_Apply_Enved_Callback,ss);
      XtAddCallback(apply2B,XmNhelpCallback,Undo_and_Apply_Help_Callback,ss);

      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      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);


      /* buttons */
      n=0;
      if (!(ss->using_schemes)) n = background_zoom_color(args,n,ss);
      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],XmNshadowThickness,4); n++;
      colF = XtCreateManagedWidget("env-button-frame",xmFrameWidgetClass,mainform,args,n);

      n=0;
      if (!(ss->using_schemes)) n = background_high_color(args,n,ss);
      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,XmVERTICAL); n++;
      XtSetArg(args[n],XmNspacing,0); n++;
      colB = XtCreateManagedWidget("env-button-holder",xmRowColumnWidgetClass,colF,args,n);

      n=0;
      if (!(ss->using_schemes)) 
	{
	  n = background_high_color(args,n,ss);
	  XtSetArg(args[n],XmNfontList,button_FONT(ss)); n++;
	  XtSetArg(args[n],XmNarmColor,(ss->sgx)->text); n++;
	}

      showB = XtCreateManagedWidget(snd_string_view_envs,xmPushButtonWidgetClass,colB,args,n);
      saveB = XtCreateManagedWidget(snd_string_save,xmPushButtonWidgetClass,colB,args,n);
      revertB = XtCreateManagedWidget(snd_string_revert,xmPushButtonWidgetClass,colB,args,n);
      undoB = XtCreateManagedWidget(snd_string_undo,xmPushButtonWidgetClass,colB,args,n);
      redoB = XtCreateManagedWidget(snd_string_redo,xmPushButtonWidgetClass,colB,args,n);
      printB = XtCreateManagedWidget(snd_string_print,xmPushButtonWidgetClass,colB,args,n);


      XtAddCallback(showB,XmNactivateCallback,show_button_pressed,ss);
      XtAddCallback(saveB,XmNactivateCallback,save_button_pressed,ss);
      XtAddCallback(revertB,XmNactivateCallback,revert_button_pressed,ss);
      XtAddCallback(undoB,XmNactivateCallback,undo_button_pressed,ss);
      XtAddCallback(redoB,XmNactivateCallback,redo_button_pressed,ss);
      XtAddCallback(printB,XmNactivateCallback,print_button_pressed,ss);

      XtAddCallback(showB,XmNhelpCallback,show_button_help_callback,ss);
      XtAddCallback(saveB,XmNhelpCallback,save_button_help_callback,ss);
      XtAddCallback(revertB,XmNhelpCallback,revert_button_help_callback,ss);
      XtAddCallback(undoB,XmNhelpCallback,undo_button_help_callback,ss);
      XtAddCallback(redoB,XmNhelpCallback,redo_button_help_callback,ss);
      XtAddCallback(printB,XmNhelpCallback,print_button_help_callback,ss);

      /* apply_to_amp choice (a row of three push buttons that acts like a "radio box") */
      n=0;
      if (!(ss->using_schemes)) n = background_high_color(args,n,ss);
      XtSetArg(args[n],XmNorientation,XmHORIZONTAL); n++;
      XtSetArg(args[n],XmNspacing,0); n++;
      XtSetArg(args[n],XmNmarginWidth,0); n++;
      rbrow = XtCreateManagedWidget("asf-holder",xmRowColumnWidgetClass,colB,args,n);  

      n=0;
      if (!(ss->using_schemes)) 
	{
	  n = background_high_color(args,n,ss);
	  XtSetArg(args[n],XmNfontList,button_FONT(ss)); n++;
	  XtSetArg(args[n],XmNarmColor,(ss->sgx)->text); n++;
	}
      /* XtSetArg(args[n],XmNmarginWidth,0); n++; */
      XtSetArg(args[n],XmNshadowThickness,1); n++;
      ampB = XtCreateManagedWidget(snd_string_amp,xmPushButtonWidgetClass,rbrow,args,n);
      fltB = XtCreateManagedWidget(snd_string_flt,xmPushButtonWidgetClass,rbrow,args,n);
      srcB = XtCreateManagedWidget(snd_string_src,xmPushButtonWidgetClass,rbrow,args,n);

      XtAddCallback(fltB,XmNactivateCallback,Freq_Button_Callback,ss);
      XtAddCallback(fltB,XmNhelpCallback,Freq_Button_Help_Callback,ss);
      XtAddCallback(ampB,XmNactivateCallback,Amp_Button_Callback,ss);
      XtAddCallback(ampB,XmNhelpCallback,Amp_Button_Help_Callback,ss);
      XtAddCallback(srcB,XmNactivateCallback,Src_Button_Callback,ss);
      XtAddCallback(srcB,XmNhelpCallback,Src_Button_Help_Callback,ss);


      n=0;
      s1=XmStringCreateLtoR(snd_string_amp_env_p,XmFONTLIST_DEFAULT_TAG);
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      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++;
      XtSetArg(args[n],XmNfillOnArm,FALSE); n++;
      nameL = XtCreateManagedWidget("nameL",xmPushButtonWidgetClass,mainform,args,n);
      XtAddCallback(nameL,XmNactivateCallback,amp_button_pressed,ss);
      XtAddCallback(nameL,XmNhelpCallback,amp_button_help_callback,ss);
      XmStringFree(s1);
#if OVERRIDE_TOGGLE
      override_toggle_translation(nameL);
#endif

      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      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,nameL); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_NONE); n++;
      textL = sndCreateTextFieldWidget(ss,"textL",mainform,args,n);
      XtAddCallback(textL,XmNhelpCallback,Text_Help_Callback,ss);

      n=0;
      if (!(ss->using_schemes)) 
	{
	  n = background_main_color(args,n,ss);
	  XtSetArg(args[n],XmNselectColor,(ss->sgx)->text); 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++;
#if NEED_TOGGLE_SIZE
      XtSetArg(args[n],XmNindicatorSize,TOGGLE_SIZE); n++;
#endif
      graphB = XtCreateManagedWidget(snd_string_wave,xmToggleButtonWidgetClass,mainform,args,n);
      XtAddCallback(graphB,XmNvalueChangedCallback,Graph_Button_Callback,ss);
      XtAddCallback(graphB,XmNhelpCallback,Graph_Button_Help_Callback,ss);

      n=0;
      if (!(ss->using_schemes)) 
	{
	  n = background_main_color(args,n,ss);
	  XtSetArg(args[n],XmNselectColor,(ss->sgx)->text); 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,graphB); n++;
#if NEED_TOGGLE_SIZE
      XtSetArg(args[n],XmNindicatorSize,TOGGLE_SIZE); n++;
#endif
      clipB = XtCreateManagedWidget(snd_string_clip,xmToggleButtonWidgetClass,mainform,args,n);
      XtAddCallback(clipB,XmNvalueChangedCallback,Clip_Button_Callback,ss);
      XtAddCallback(clipB,XmNhelpCallback,Clip_Button_Help_Callback,ss);

      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      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,textL); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNrightWidget,clipB); n++;
      XtSetArg(args[n],XmNrecomputeSize,FALSE); n++;
      XtSetArg(args[n],XmNfontList,button_FONT(ss)); n++;
      brkptL = XtCreateManagedWidget("         ",xmLabelWidgetClass,mainform,args,n);
      XtAddCallback(brkptL,XmNhelpCallback,Brkpt_Help_Callback,ss);

      dpy=main_DISPLAY(ss);
      scr=DefaultScreen(dpy);

      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNbottomWidget,textL); n++;
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNleftWidget,colF); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNallowResize,TRUE); n++;
      XtSetArg(args[n],XmNbackground,WhitePixel(dpy,scr)); n++;
      XtSetArg(args[n],XmNforeground,BlackPixel(dpy,scr)); n++;
      drawer = XtCreateManagedWidget("drawer",xmDrawingAreaWidgetClass,mainform,args,n);
      XtAddCallback(drawer,XmNhelpCallback,Drawer_Help_Callback,ss);

      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      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,colF); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n],XmNrightWidget,colF); n++;
      screnvname = XtCreateManagedWidget(snd_string_envs_p,xmLabelWidgetClass,mainform,args,n);
      XtAddCallback(screnvname,XmNhelpCallback,Scrolled_List_Help_Callback,ss);

      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNbottomWidget,textL); n++;
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNtopWidget,screnvname); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_OPPOSITE_WIDGET); n++;
      XtSetArg(args[n],XmNrightWidget,colF); n++;
      XtSetArg(args[n],XmNfontList,button_FONT(ss)); n++;
      screnvlst = XmCreateScrolledList(mainform,"scrolled-env-list",args,n);
      XtManageChild(screnvlst); 
      XtAddCallback(screnvlst,XmNbrowseSelectionCallback,env_browse_Callback,ss);
      map_over_children(screnvlst,set_main_color_of_widget,(void *)ss);
      if (all_envs) make_scrolled_env_list(ss);
      XtAddCallback(screnvlst,XmNhelpCallback,Scrolled_List_Help_Callback,ss);

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

      XtManageChild(enved_dialog); /* needed so that window is valid when resize callback is invoked */
      applyB = XmMessageBoxGetChild(enved_dialog,XmDIALOG_OK_BUTTON);
      cancelB = XmMessageBoxGetChild(enved_dialog,XmDIALOG_CANCEL_BUTTON);

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

      XtAddEventHandler(drawer,ButtonPressMask,FALSE,drawer_button_press,ss);
      XtAddEventHandler(drawer,ButtonMotionMask,FALSE,drawer_button_motion,ss);
      XtAddEventHandler(drawer,ButtonReleaseMask,FALSE,drawer_button_release,ss);

      if (!all_envs)
	{
	  XtSetSensitive(showB,FALSE);
	}
      XtSetSensitive(revertB,FALSE);
      XtSetSensitive(undoB,FALSE);
      XtSetSensitive(redoB,FALSE);
      XtSetSensitive(saveB,FALSE);

      XmToggleButtonSetState(clipB,env_clipped,FALSE);
      XmToggleButtonSetState(graphB,ss->env_graphing,FALSE);
      reflect_apply_state(ss);
    }
  else raise_dialog(enved_dialog);
  if (!XtIsManaged(enved_dialog)) XtManageChild(enved_dialog);
  active_channel = current_channel(ss);
}

void save_envelope_editor_state(snd_state *ss, int fd)
{
  /* minimal currently */
  char *buf;
  buf = (char *)calloc(128,sizeof(char));
  sprintf(buf,"(setf env-graphing %d) (setf env-clipping %d) (setf env-filtering %d)\n",
	  ss->env_graphing,env_clipped,!apply_to_amp);
  write(fd,buf,strlen(buf));
  free(buf);
}
