#include "snd.h"
#include <X11/cursorfont.h>

static Cursor graph_cursor;

void make_graph_cursor(task_manager *tm)
{
  graph_cursor = XCreateFontCursor(tm->dpy,XC_crosshair);
}

static int w_chn_start_order[NUM_CHAN_WIDGETS] = {
  W_chn_main_window,
    W_chn_edhist,
    W_chn_wf_buttons,
      W_chn_f,W_chn_w,
    W_chn_left_scrollers,
      W_chn_zy,W_chn_sy,
    W_chn_bottom_scrollers,
      W_chn_sx,W_chn_zx,
    W_chn_graph,
      W_chn_gzy,W_chn_gsy
};

Widget chan_widget(chan_info *cp,int w)
{ 
  if ((cp) && (cp->cgx))
    return((cp->cgx)->chan_widgets[w]);
  else return(NULL);
}

int channel_open_pane(chan_info *cp, void *ptr)
{
  XtManageChild(chan_widget(cp,W_chn_main_window));
  return(0);
}


int channel_unlock_pane(chan_info *cp, void *ptr)
{
  XtVaSetValues(chan_widget(cp,W_chn_main_window),XmNpaneMinimum,5,XmNpaneMaximum,1000,NULL);
  return(0);
}

int channel_lock_pane(chan_info *cp, void *ptr)
{
  int val;
  val = (*((int *)ptr));
  if (val < 6) val = 6;
  XtUnmanageChild(chan_widget(cp,W_chn_main_window));
  XtVaSetValues(chan_widget(cp,W_chn_main_window),XmNpaneMinimum,val-5,XmNpaneMaximum,val+5,NULL);
  return(0);
}

static void W_sy_Drag_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  sy_changed(((XmScrollBarCallbackStruct *)callData)->value,(chan_info *)(clientData));
}

static void W_sy_ValueChanged_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  sy_changed(((XmScrollBarCallbackStruct *)callData)->value,(chan_info *)(clientData));
}

static void W_sx_Drag_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  sx_changed(((XmScrollBarCallbackStruct *)callData)->value,(chan_info *)(clientData));
}

static void W_sx_ValueChanged_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  sx_changed(((XmScrollBarCallbackStruct *)callData)->value,(chan_info *)(clientData));
}

static void W_sx_Increment_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  /* problem here is that in large files these increments, if determined via scrollbar values, are huge */
  /* so, move ahead one windowfull on each tick */
  sx_incremented((chan_info *)clientData,1.0);
}

static void W_sx_Decrement_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  sx_incremented((chan_info *)clientData,-1.0);
}

static void W_zy_Drag_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  zy_changed(((XmScrollBarCallbackStruct *)callData)->value,(chan_info *)clientData);
}

static void W_zy_ValueChanged_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  zy_changed(((XmScrollBarCallbackStruct *)callData)->value,(chan_info *)clientData);
}

static void W_zx_Drag_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  zx_changed(((XmScrollBarCallbackStruct *)callData)->value,(chan_info *)clientData);
}

static void W_zx_ValueChanged_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  zx_changed(((XmScrollBarCallbackStruct *)callData)->value,(chan_info *)clientData);
}

static void W_gzy_Drag_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  gzy_changed(((XmScrollBarCallbackStruct *)callData)->value,(chan_info *)clientData);
}

static void W_gzy_ValueChanged_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  gzy_changed(((XmScrollBarCallbackStruct *)callData)->value,(chan_info *)clientData);
}

static void W_gsy_Drag_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  gsy_changed(((XmScrollBarCallbackStruct *)callData)->value,(chan_info *)clientData);
}

static void W_gsy_ValueChanged_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  gsy_changed(((XmScrollBarCallbackStruct *)callData)->value,(chan_info *)clientData);
}

/* anything special for increment?  XmNincrementCallback W_sx_Increment_Callback */


/* help callbacks (for mouse click help) -- all take snd_state as clientData */

static void W_graph_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
#if HAVE_XmHTML
  snd_help((snd_state *)clientData,"Graph","#panelayout");
#else
  ssnd_help((snd_state *)clientData,
	    "Graph",
"This portion of the Snd display shows the\n\
sound data in the time and/or frequency domains.\n\
If you click on the time domain wave, you can\n\
edit it using emacs-like keyboard commands, as\n\
well as using mouse-click-and-drag to define the\n\
selection.  Once defined, the selected portion\n\
can be cut, deleted, or pasted elsewhere, the\n\
latter with the middle mouse button.  The keyboard\n\
commands are (c = control):\n\
\n\
",
get_graph_help(),
"\n",
get_mark_help(),
NULL);
#endif
}

static void W_sx_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "X axis scroll",
"This scrollbar controls the position of\n\
the x axis within the overall sound file.\n\
The arrows increment the view by one window.\n\
");
}

static void W_sy_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Y axis scroll",
"This (nearly useless) scrollbar controls the\n\
position of the y-axis within the current y axis\n\
limits.\n\
");
}

static void W_zx_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "X axis zoom",
"This scrollbar zooms in (as you move\n\
it to the left) or out along the x axis.\n\
");
}

static void W_zy_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Y axis zoom",
"This scrollbar zooms in (as you move\n\
it down) or out along the y axis.\n\
");
}

static void W_gsy_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Graph position",
"This scrollbar controls the position\n\
in the overall combined graph of the\n\
portion visible in the sound pane.\n\
");
}

static void W_gzy_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "Graph zoom",
"This scrollbar controls how much of\n\
the overall combined graph is visible\n\
in the sound pane.\n\
");
}

static void F_button_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "fft button",
"This button controls whether an FFT is\n\
displayed alongside the waveform.  To affect\n\
all channels at once, use control-click.\n\
");
}

static void W_button_Help_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_help((snd_state *)clientData,
	   "time domain waveform button",
"This button determines whether the time\n\
domain waveform is displayed.  If both the\n\
'w' and 'f' buttons are off, only the lisp\n\
graph (if any) is displayed.  To affect\n\
all channels at once, use control-click.\n\
");
}


void fftb(chan_info *cp, int on)
{
  cp->ffting = on;
  XmToggleButtonSetState(chan_widget(cp,W_chn_f),on,FALSE);
  calculate_fft(cp,NULL);
}

void waveb(chan_info *cp, int on)
{
  cp->waving = on;
  XmToggleButtonSetState(chan_widget(cp,W_chn_w),on,FALSE);
  update_graph(cp,NULL);
}

static void propogate_wf_state(snd_info *sp)
{
  int i,w,f;
  chan_info *cp;
  if (sp->combining != CHANNELS_SEPARATE)
    {
      cp = sp->chans[0];
      if (sp->combining == CHANNELS_SUPERIMPOSED) clear_window(((axis_info *)(cp->axis))->ax);
      w = cp->waving;
      f = cp->ffting;
      for (i=1;i<sp->nchans;i++) 
	{
	  cp = sp->chans[i];
	  cp->waving = w;
	  cp->ffting = f;
	  XmToggleButtonSetState(chan_widget(cp,W_chn_f),(f) ? TRUE : FALSE,FALSE);
	  XmToggleButtonSetState(chan_widget(cp,W_chn_w),(w) ? TRUE : FALSE,FALSE);
	}
      if (f) 
	map_over_sound_chans(sp,calculate_fft,NULL);
      else map_over_sound_chans(sp,update_graph,NULL);
    }
}

static void F_button_Callback(Widget w,XtPointer clientData,XtPointer callData)
{ 
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)callData;
  chan_info *cp = (chan_info *)clientData;
  snd_info *sp;
  int i;
  chan_info *ncp;
  XButtonEvent *ev;
  cp->ffting = cb->set;
  sp = cp->sound;
  if (sp->combining != CHANNELS_SEPARATE)
    propogate_wf_state(sp);
  else
    {
      if (cp->ffting) calculate_fft(cp,NULL); else update_graph(cp,NULL);
      ev = (XButtonEvent *)(cb->event);
      if (ev->state & snd_ControlMask)
	{
	  for (i=0;i<sp->nchans;i++) 
	    {
	      ncp = sp->chans[i];
	      if (cp != ncp)
		{
		  ncp->ffting = cb->set;
		  XmToggleButtonSetState(chan_widget(ncp,W_chn_f),(cb->set) ? TRUE : FALSE,FALSE);
		  if (ncp->ffting) calculate_fft(ncp,NULL); else update_graph(ncp,NULL);
		}
	    }
	}
      goto_graph(cp);
    }
}

static void W_button_Callback(Widget w,XtPointer clientData,XtPointer callData)
{
  XmToggleButtonCallbackStruct *cb = (XmToggleButtonCallbackStruct *)callData;
  chan_info *cp = (chan_info *)clientData;
  snd_info *sp;
  int i;
  chan_info *ncp;
  XButtonEvent *ev;
  cp->waving = cb->set;
  sp = cp->sound;
  if (sp->combining != CHANNELS_SEPARATE)
    propogate_wf_state(sp);
  else
    {
      update_graph(cp,NULL);
      ev = (XButtonEvent *)(cb->event);
      if (ev->state & snd_ControlMask)
	{
	  for (i=0;i<sp->nchans;i++) 
	    {
	      ncp = sp->chans[i];
	      if (cp != ncp)
		{
		  ncp->waving = cb->set;
		  XmToggleButtonSetState(chan_widget(ncp,W_chn_w),(cb->set) ? TRUE : FALSE,FALSE);
		  update_graph(ncp,NULL);
		}
	    }
	}
      goto_graph(cp);
    }
}

static int expose_looks_ok(chan_info *cp, XExposeEvent *ev)
{
  axis_info *ap;
  if (!(cp)) return(TRUE);
  if (!(cp->mixes)) return(TRUE);
  ap = cp->axis;
  if (!(ap)) return(TRUE);
  /* fprintf(stderr,"expose evx: %d evy: %d, evw: %d, evh: %d [apy0: %d, apy1: %d, aph: %d, apo: %d]\n ",
     ev->x,ev->y,ev->width,ev->height,ap->y_axis_y0,ap->y_axis_y1,ap->height,ap->y_offset);
     */
  /* mixers turnover at ap->height * .5 (from ap->y_offset) */
  return(((ev->width > 200) || (ev->height > (ap->height/2)) || (ev->y > (ap->y_offset + (ap->height/2)))));
}

static void Channel_Expose_Callback(Widget w,XtPointer clientData,XtPointer callData)
{
  snd_info *sp;
  chan_info *cp = (chan_info *)clientData;
  XmDrawingAreaCallbackStruct *cb = (XmDrawingAreaCallbackStruct *)callData;
  XExposeEvent *ev;
  ev = (XExposeEvent *)(cb->event);
  sp = cp->sound;
  if ((ev->count == 0) && (!(mix_dragging())) && (expose_looks_ok(cp,ev)))
    {
      if (sp->combining != CHANNELS_SEPARATE)
	map_over_sound_chans(sp,update_graph,NULL);
      else update_graph(cp,NULL);
    }
}

static void Channel_Resize_Callback(Widget w,XtPointer clientData,XtPointer callData)
{
  snd_info *sp;
  chan_info *cp = (chan_info *)clientData;
  sp = cp->sound;
  if (sp->combining != CHANNELS_SEPARATE)
    map_over_sound_chans(sp,update_graph,NULL);
  else update_graph(cp,NULL);
}

static void graph_mouse_enter(Widget w, XtPointer clientData, XEvent *event, Boolean *flag)
{
  XDefineCursor(XtDisplay(w),XtWindow(w),graph_cursor);
}

static void graph_mouse_leave(Widget w, XtPointer clientData, XEvent *event, Boolean *flag)
{
  XUndefineCursor(XtDisplay(w),XtWindow(w));
}


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 = chan_widget(sp->chans[0],W_chn_gsy);
  if ((w) && (XtIsManaged(w))) XtUnmanageChild(w);
  w = chan_widget(sp->chans[0],W_chn_gzy);
  if ((w) && (XtIsManaged(w))) XtUnmanageChild(w);
}

static void show_gz_scrollbars(snd_info *sp)
{
  Widget w;
  w = chan_widget(sp->chans[0],W_chn_gsy);
  if ((w) && (!XtIsManaged(w))) XtManageChild(w);
  w = chan_widget(sp->chans[0],W_chn_gzy);
  if ((w) && (!XtIsManaged(w))) XtManageChild(w);
}


/* edit history support */

static void edit_select_Callback(Widget w,XtPointer clientData,XtPointer callData) 
{
  /* undo/redo to reach selected position */
  chan_info *cp = (chan_info *)clientData;
  snd_state *ss;
  int ed,syncd;
  XmListCallbackStruct *cbs = (XmListCallbackStruct *)callData;
  XButtonEvent *ev;
  ss = cp->state;
  ed = cbs->item_position - 1; /* counts from 1, but edit list counts from 0 */
  ev = (XButtonEvent *)(cbs->event);
  syncd = (ev->state & snd_ControlMask);  /* follow sync chain if control-click */
  if (ed != cp->edit_ctr)
    {
      if (cp->edit_ctr > ed)
	if (syncd)
	  undo_EDIT(cp,cp->edit_ctr - ed);
	else undo_edit(cp,cp->edit_ctr - ed);
      else 
	{
	  if (syncd)
	    redo_EDIT(cp,ed - cp->edit_ctr);	      
	  else redo_edit(cp,ed - cp->edit_ctr);
	}
    }
  /* don't let this list trap all subsequent clicks and keypresses! */
  goto_graph(cp);
}

void reflect_edit_history_change(chan_info *cp)
{
  /* new edit so it is added, and any trailing lines removed */
  chan_context *cx;
  Widget lst;
  snd_state *ss;
  snd_info *sp;
  int i,eds;
  XmString *edits;
  ss = cp->state;
  if (show_edit_history(ss))
    {
      cx = cp->cgx;
      if (cx)
	{
	  lst = cx->chan_widgets[W_chn_edhist];
	  if (lst)
	    {
	      eds = cp->edit_ctr;
	      if (eds>=0)
		{
		  sp = cp->sound;
		  edits = (XmString *)calloc(eds+1,sizeof(XmString));
		  edits[0] = XmStringCreate(sp->fullname,XmFONTLIST_DEFAULT_TAG);
		  for (i=1;i<=eds;i++) edits[i] = XmStringCreate(edit_to_string(cp->edits[i]),XmFONTLIST_DEFAULT_TAG);
		  XtVaSetValues(lst,XmNitems,edits,XmNitemCount,eds+1,NULL);
		  XmListSelectPos(lst,eds+1,FALSE);
		  for (i=0;i<=eds;i++) XmStringFree(edits[i]);
		  free(edits);
		  XtVaGetValues(lst,XmNvisibleItemCount,&i,NULL);
		  if (i <= eds) XtVaSetValues(lst,XmNtopItemPosition,eds-i+2,NULL);
		  goto_graph(cp);
		}
	    }
	}
    }
}

void reflect_edit_counter_change(chan_info *cp)
{
  /* undo/redo/revert -- change which line is highlighted */
  chan_context *cx;
  Widget lst;
  int len,top;
  snd_state *ss;
  ss = cp->state;
  if (show_edit_history(ss))
    {
      cx = cp->cgx;
      if (cx)
	{
	  lst = cx->chan_widgets[W_chn_edhist];
	  if (lst)
	    {
	      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);
	    }
	}
    }
}

int open_edit_histories(chan_info *cp, void *ptr)
{
  chan_context *cx;
  snd_state *ss;
  Widget *cw;
  cx = cp->cgx;
  cw = cx->chan_widgets;
  ss = cp->state;
  if ((cx) && (cw[W_chn_edhist]))
    {
      XtUnmanageChild(cw[W_chn_main_window]);
      if (!(ss->using_schemes)) XtVaSetValues(cw[W_chn_edhist],XmNbackground,(ss->sgx)->white,NULL);
      XtVaSetValues(XtParent(cw[W_chn_edhist]),XmNwidth,edit_history_width(ss),NULL);
      XtVaSetValues(cw[W_chn_edhist],XmNwidth,edit_history_width(ss),NULL);
      XtManageChild(cw[W_chn_main_window]);
      reflect_edit_history_change(cp);
    }
  return(0);
}

int close_edit_histories(chan_info *cp, void *ptr)
{
  chan_context *cx;
  snd_state *ss;
  Widget *cw;
  cx = cp->cgx;
  cw = cx->chan_widgets;
  ss = cp->state;
  in_set_edit_history_width(ss,0);
  if ((cx) && (cw[W_chn_edhist]))
    {
      if (!(ss->using_schemes)) XtVaSetValues(cw[W_chn_edhist],XmNbackground,(ss->sgx)->main,NULL);
      XtVaSetValues(XtParent(cw[W_chn_edhist]),XmNwidth,edit_history_width(ss),NULL);
      XtVaSetValues(cw[W_chn_edhist],XmNwidth,edit_history_width(ss),NULL);
    }
  return(0);
}

int set_edit_history_width(snd_state *ss, int width)
{
  in_set_edit_history_width(ss,width);
  if (show_edit_history(ss))
    map_over_chans(ss,open_edit_histories,NULL);
  return(width);
}

void add_channel_window(snd_info *sound, int channel, snd_state *ss, int chan_y, int insertion, Widget main, int button_style)
{
  Widget *cw;
  chan_info *cs;
  chan_context *cx;
  axis_context *cax;
  int make_widgets,i,n,need_colors,need_extra_scrollbars;
  XGCValues gv;
  Arg args[32];
  if (sound == NULL) snd_error("null sound pointer passed to add_channel_window");
  make_widgets = ((sound->chans[channel]) == NULL);
  sound->chans[channel] = make_chan_info(sound->chans[channel],channel,sound,ss);
  cs = sound->chans[channel];
  if ((!cs) || ((snd_pointer_type(cs)) != CHAN_INFO)) snd_pointer_error("add_channel_window",CHAN_INFO,(void *)cs);
  cx = cs->cgx;
  cw = cx->chan_widgets;
  need_extra_scrollbars = ((!main) && (channel == 0));

  if (make_widgets)
    {
      /* allocate the entire widget apparatus for this channel of this sound */

      need_colors = (!(ss->using_schemes));

      if (!main)
	{
	  n=0;
	  if (need_colors) n = background_main_color(args,n,ss);
	  XtSetArg(args[n],XmNpaneMinimum,chan_y); n++;
	  if (insertion) {XtSetArg(args[n],XmNpositionIndex,(short)channel); n++;}
	  cw[W_chn_main_window] = XtCreateManagedWidget("chn-main-window",xmFormWidgetClass,snd_widget(sound,W_snd_pane),args,n);
	  XtAddEventHandler(cw[W_chn_main_window],KeyPressMask,FALSE,graph_key_press,(XtPointer)sound);

	  n=0;
#ifdef LESSTIF_VERSION
	  if (!(ss->using_schemes)) n = background_white_color(args,n,ss);
#else
	  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
#endif
	  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,edit_history_width(ss)); n++;
	  XtSetArg(args[n],XmNlistSizePolicy,XmCONSTANT); n++;
	  cw[W_chn_edhist] = XmCreateScrolledList(cw[W_chn_main_window],"edhist",args,n);
	  XtManageChild(cw[W_chn_edhist]);
	  XtVaSetValues(XtParent(cw[W_chn_edhist]),XmNwidth,edit_history_width(ss),NULL);
	  XtAddCallback(cw[W_chn_edhist],XmNbrowseSelectionCallback,edit_select_Callback,cs);
	  XtAddEventHandler(cw[W_chn_edhist],KeyPressMask,FALSE,graph_key_press,(XtPointer)sound);
	  XtAddEventHandler(XtParent(cw[W_chn_edhist]),KeyPressMask,FALSE,graph_key_press,(XtPointer)sound);
	}
      else cw[W_chn_main_window] = main;

      n=0;  
      if (need_colors) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_NONE); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
      if (cw[W_chn_edhist])
	{
	  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
	  XtSetArg(args[n],XmNleftWidget,cw[W_chn_edhist]); n++;
	}
      else
	{
	  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_chn_wf_buttons] = XtCreateManagedWidget("chn-buttons",xmRowColumnWidgetClass,cw[W_chn_main_window],args,n);	

      if (button_style == WITH_FW_BUTTONS)
	{
	  n=0;
	  if (need_colors) n = background_main_color(args,n,ss);
	  XtSetArg(args[n],XmNfontList,button_FONT(ss)); n++;
	  XtSetArg(args[n],XmNspacing,1); n++;
	  if (!(ss->using_schemes)) {XtSetArg(args[n],XmNselectColor,(ss->sgx)->text); n++;}
	  cw[W_chn_f] = XtCreateManagedWidget(snd_string_f,xmToggleButtonWidgetClass,cw[W_chn_wf_buttons],args,n);
	  XtAddCallback(cw[W_chn_f],XmNvalueChangedCallback,F_button_Callback,cs);
	  XtAddCallback(cw[W_chn_f],XmNhelpCallback,F_button_Help_Callback,ss);
	  XtAddEventHandler(cw[W_chn_f],KeyPressMask,FALSE,graph_key_press,(XtPointer)sound);
#if OVERRIDE_TOGGLE
	  override_toggle_translation(cw[W_chn_f]);
#endif
	  
	  XtSetArg(args[n],XmNset,TRUE); n++;
	  cw[W_chn_w] = XtCreateManagedWidget(snd_string_w,xmToggleButtonWidgetClass,cw[W_chn_wf_buttons],args,n);
	  XtAddCallback(cw[W_chn_w],XmNvalueChangedCallback,W_button_Callback,cs);
	  XtAddCallback(cw[W_chn_w],XmNhelpCallback,W_button_Help_Callback,ss);
	  XtAddEventHandler(cw[W_chn_w],KeyPressMask,FALSE,graph_key_press,(XtPointer)sound);
#if OVERRIDE_TOGGLE
	  override_toggle_translation(cw[W_chn_w]);
#endif
	}
      else
	{
	  n=0;
	  if (need_colors) n = background_main_color(args,n,ss);
	  XtSetArg(args[n],XmNarrowDirection,XmARROW_UP); n++;
	  XtSetArg(args[n],XmNsensitive,FALSE); n++;
	  cw[W_chn_f] = XtCreateManagedWidget("up",xmArrowButtonWidgetClass,cw[W_chn_wf_buttons],args,n);

	  n=0;
	  if (need_colors) n = background_main_color(args,n,ss);
	  XtSetArg(args[n],XmNarrowDirection,XmARROW_DOWN); n++;
	  XtSetArg(args[n],XmNsensitive,FALSE); n++;
	  cw[W_chn_w] = XtCreateManagedWidget("down",xmArrowButtonWidgetClass,cw[W_chn_wf_buttons],args,n);
	}

      n=0;
      if (need_colors) n = background_main_color(args,n,ss);
      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_chn_wf_buttons]); n++;
      if (cw[W_chn_edhist])
	{
	  XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
	  XtSetArg(args[n],XmNleftWidget,cw[W_chn_edhist]); n++;
	}
      else
	{
	  XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
	}
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_NONE); n++;
      XtSetArg(args[n],XmNspacing,0); n++;
      cw[W_chn_left_scrollers] = XtCreateManagedWidget("chn-left",xmRowColumnWidgetClass,cw[W_chn_main_window],args,n);

      n=0;
      if (need_colors) n = background_zoom_color(args,n,ss);
      XtSetArg(args[n],XmNwidth,ss->place_scroll_size); 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,make_callback_list(W_zy_Drag_Callback,(XtPointer)cs)); n++;
      XtSetArg(args[n],XmNvalueChangedCallback,make_callback_list(W_zy_ValueChanged_Callback,(XtPointer)cs)); n++;
      cw[W_chn_zy] = XtCreateManagedWidget("chn-zy",xmScrollBarWidgetClass,cw[W_chn_left_scrollers],args,n);
      XtAddCallback(cw[W_chn_zy],XmNhelpCallback,W_zy_Help_Callback,ss);

      n=0;
      if (need_colors) n = background_scale_color(args,n,ss);
      XtSetArg(args[n],XmNwidth,ZOOM_SCROLL_SIZE); 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_chn_zy]); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNorientation,XmVERTICAL); n++;
      XtSetArg(args[n],XmNmaximum,SCROLLBAR_SY_MAX); n++;
      XtSetArg(args[n],XmNincrement,1); n++;
      XtSetArg(args[n],XmNprocessingDirection,XmMAX_ON_TOP); n++;
      XtSetArg(args[n],XmNdragCallback,make_callback_list(W_sy_Drag_Callback,(XtPointer)cs)); n++;
      XtSetArg(args[n],XmNvalueChangedCallback,make_callback_list(W_sy_ValueChanged_Callback,(XtPointer)cs)); n++;
      cw[W_chn_sy] = XtCreateManagedWidget("chn-sy",xmScrollBarWidgetClass,cw[W_chn_left_scrollers],args,n);
      XtAddCallback(cw[W_chn_sy],XmNhelpCallback,W_sy_Help_Callback,ss);

      n=0;
      if (need_colors) n = background_main_color(args,n,ss);
      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_chn_wf_buttons]); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNspacing,0); n++;
      cw[W_chn_bottom_scrollers] = XtCreateManagedWidget("chn-bottom",xmRowColumnWidgetClass,cw[W_chn_main_window],args,n);

      n=0;
      if (need_colors) n = background_scale_color(args,n,ss);
      XtSetArg(args[n],XmNheight,ss->place_scroll_size); 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,sound->sx_scroll_max); n++;
      XtSetArg(args[n],XmNincrement,1); n++;
      XtSetArg(args[n],XmNdragCallback,make_callback_list(W_sx_Drag_Callback,(XtPointer)cs)); n++;
      XtSetArg(args[n],XmNincrementCallback,make_callback_list(W_sx_Increment_Callback,(XtPointer)cs)); n++;
      XtSetArg(args[n],XmNdecrementCallback,make_callback_list(W_sx_Decrement_Callback,(XtPointer)cs)); n++;
      XtSetArg(args[n],XmNvalueChangedCallback,make_callback_list(W_sx_ValueChanged_Callback,(XtPointer)cs)); n++;
      cw[W_chn_sx] = XtCreateManagedWidget("chn-sx",xmScrollBarWidgetClass,cw[W_chn_bottom_scrollers],args,n);
      XtAddCallback(cw[W_chn_sx],XmNhelpCallback,W_sx_Help_Callback,ss);

      n=0;
      if (need_colors) n = background_zoom_color(args,n,ss);
      XtSetArg(args[n],XmNheight,ZOOM_SCROLL_SIZE+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_chn_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,make_callback_list(W_zx_Drag_Callback,(XtPointer)cs)); n++;
      XtSetArg(args[n],XmNvalueChangedCallback,make_callback_list(W_zx_ValueChanged_Callback,(XtPointer)cs)); n++;
      cw[W_chn_zx] = XtCreateManagedWidget("chn-zx",xmScrollBarWidgetClass,cw[W_chn_bottom_scrollers],args,n);
      XtAddCallback(cw[W_chn_zx],XmNhelpCallback,W_zx_Help_Callback,ss);

      n=0;
      if (need_colors) n = background_white_color(args,n,ss);
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNbottomWidget,cw[W_chn_bottom_scrollers]); n++;
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_WIDGET); n++;
      XtSetArg(args[n],XmNleftWidget,cw[W_chn_left_scrollers]); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
      /* this collides with W_chn_gzy below, but a consistent version came up with half a window blank */
      XtSetArg(args[n],XmNnavigationType,XmNONE); n++;
      cw[W_chn_graph] = XtCreateManagedWidget("chn-graph",xmDrawingAreaWidgetClass,cw[W_chn_main_window],args,n);
      XtAddCallback(cw[W_chn_graph],XmNhelpCallback,W_graph_Help_Callback,ss);

      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 sound->nchans because these structs are re-used and added to as needed */
	  n=0;
	  if (need_colors) n = background_zoom_color(args,n,ss);
	  XtSetArg(args[n],XmNwidth,ss->place_scroll_size); n++;
	  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
	  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_WIDGET); n++;
	  XtSetArg(args[n],XmNbottomWidget,cw[W_chn_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],XmNdragCallback,make_callback_list(W_gzy_Drag_Callback,(XtPointer)cs)); n++;
	  XtSetArg(args[n],XmNvalueChangedCallback,make_callback_list(W_gzy_ValueChanged_Callback,(XtPointer)cs)); n++;
	  cw[W_chn_gzy] = XtCreateManagedWidget("chn-gzy",xmScrollBarWidgetClass,cw[W_chn_main_window],args,n);
	  XtAddCallback(cw[W_chn_gzy],XmNhelpCallback,W_gzy_Help_Callback,ss);

	  n=0;
	  if (need_colors) n = background_scale_color(args,n,ss);
	  XtSetArg(args[n],XmNwidth,ss->place_scroll_size); n++;
	  XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
	  XtSetArg(args[n],XmNbottomAttachment,XmATTACH_WIDGET); n++;
	  XtSetArg(args[n],XmNbottomWidget,cw[W_chn_bottom_scrollers]); n++;
	  XtSetArg(args[n],XmNleftAttachment,XmATTACH_NONE); n++;
	  XtSetArg(args[n],XmNrightAttachment,XmATTACH_WIDGET); n++;
	  XtSetArg(args[n],XmNrightWidget,cw[W_chn_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,make_callback_list(W_gsy_Drag_Callback,(XtPointer)cs)); n++;
	  XtSetArg(args[n],XmNvalueChangedCallback,make_callback_list(W_gsy_ValueChanged_Callback,(XtPointer)cs)); n++;
	  cw[W_chn_gsy] = XtCreateManagedWidget("chn-gsy",xmScrollBarWidgetClass,cw[W_chn_main_window],args,n);
	  XtAddCallback(cw[W_chn_gsy],XmNhelpCallback,W_gsy_Help_Callback,ss);
	  n=0;
	}
      else
	{
	  cw[W_chn_gsy] = NULL;
	  cw[W_chn_gzy] = NULL;
	}

      if ((!main) && (show_edit_history(ss)))
	{
	  XtVaSetValues(cw[W_chn_edhist],XmNwidth,50,NULL);
	  XtManageChild(cw[W_chn_edhist]);
	}

      /* also position of current graph in overall sound as window */
      XtVaGetValues(cw[W_chn_graph],XmNforeground, &gv.foreground,XmNbackground, &gv.background,NULL);
      cx->gc = XtGetGC(cw[W_chn_graph], GCForeground | GCBackground, &gv);
      gv.function = GXxor;
      gv.foreground = (ss->sgx)->cursor;
      cx->cgc = XtGetGC(cw[W_chn_graph], GCForeground | GCFunction, &gv);
      gv.function = GXxor;
      gv.foreground = (ss->sgx)->gray;
      cx->selgc = XtGetGC(cw[W_chn_graph], GCForeground | GCFunction, &gv);
      XtVaGetValues(cw[W_chn_graph],XmNbackground, &gv.foreground,XmNforeground, &gv.background,NULL);
      gv.function = GXcopy;
      cx->igc = XtGetGC(cw[W_chn_graph], GCForeground | GCBackground | GCFunction, &gv);
      /* should this be released and re-allocated alongside chan? */
    } /* alloc new chan */
  else
    { /* re-manage currently inactive chan */
      XtVaSetValues(cw[W_chn_main_window],XmNpaneMinimum,chan_y,NULL);
      for (i=0;i<NUM_CHAN_WIDGETS;i++)
	{
	  int j;
	  j=w_chn_start_order[i];
	  if ((cw[j]) && (!XtIsManaged(cw[j])))
	    {
	      XtManageChild(cw[j]);
	      if (j == W_chn_sx) XtVaSetValues(cw[j],XmNmaximum,sound->sx_scroll_max,NULL);
	    }
	}
    }
  if ((need_extra_scrollbars) && (sound->combining == CHANNELS_SEPARATE)) 
    hide_gz_scrollbars(sound); /* default is on in this case */  
  cax = cx->ax;
  cax->wn = XtWindow(cw[W_chn_graph]);
  cax->dp = XtDisplay(cw[W_chn_graph]);
  cax->gc = cx->gc;
}

int chan_fft_in_progress(chan_info *cp)
{
  return((cp->cgx)->fft_in_progress);
}

void set_chan_fft_in_progress(chan_info *cp, XtWorkProcId fp) 
{
  (cp->cgx)->fft_in_progress = fp;
}

int calculate_fft(chan_info *cp, void *ptr)
{
  Widget w;
  snd_state *ss;
  if (cp->ffting)
    {
      ss = cp->state;
      if (!(chan_fft_in_progress(cp)))
	{
	  w = chan_widget(cp,W_chn_graph);
	  if (fft_style(ss) == NORMAL_FFT)
	    {
	      if (fft_size(ss) >= 65536) report_in_minibuffer(cp->sound,"fft...");
	      /* other half is in display_snd_fft in snd-fft.c */
	      set_chan_fft_in_progress(cp,XtAppAddWorkProc(XtWidgetToApplicationContext(w),fft_in_slices,(XtPointer)make_fft_state(cp,1)));
	    }
	  else set_chan_fft_in_progress(cp,XtAppAddWorkProc(XtWidgetToApplicationContext(w),sonogram_in_slices,(XtPointer)make_sonogram_state(cp)));
	}
    }
  return(0);
}

void set_peak_numbers_font(chan_info *cp)
{
  XFontStruct *bf;
  chan_context *cx;
  cx = cp->tcgx;
  if (!cx) cx = cp->cgx;
  bf = peak_numbers_FONTSTRUCT(cp);
  XSetFont(XtDisplay(cx->chan_widgets[W_chn_graph]),cx->gc,bf->fid);
}

void set_bold_peak_numbers_font(chan_info *cp)
{
  XFontStruct *bf;
  chan_context *cx;
  cx = cp->tcgx;
  if (!cx) cx = cp->cgx;
  bf = bold_peak_numbers_FONTSTRUCT(cp);
  XSetFont(XtDisplay(cx->chan_widgets[W_chn_graph]),cx->gc,bf->fid);
}

#define CHAN_GC 0
#define CHAN_IGC 1
#define CHAN_SELGC 2
#define CHAN_CGC 3

static axis_context *set_context (chan_info *cp, int gc)
{
  axis_context *ax;
  chan_context *cx;
  cx = cp->tcgx;
  if (!cx) cx = cp->cgx;
  ax = cx->ax;
  switch (gc)
    {
    case CHAN_GC: ax->gc = cx->gc;       break;
    case CHAN_IGC: ax->gc = cx->igc;     break;
    case CHAN_SELGC: ax->gc = cx->selgc; break;
    case CHAN_CGC: ax->gc = cx->cgc;     break;
    }
  return(ax);
}

GC copy_GC(chan_info *cp)
{
  chan_context *cx;
  cx = cp->tcgx;
  if (!cx) cx = cp->cgx;
  return(cx->gc);
}

GC erase_GC(chan_info *cp)
{
  chan_context *cx;
  cx = cp->tcgx;
  if (!cx) cx = cp->cgx;
  return(cx->igc);
}

axis_context *copy_context (chan_info *cp)      {return(set_context(cp,CHAN_GC));}
static axis_context *erase_context (chan_info *cp)     {return(set_context(cp,CHAN_IGC));}
axis_context *selection_context (chan_info *cp) {return(set_context(cp,CHAN_SELGC));}
axis_context *cursor_context (chan_info *cp)    {return(set_context(cp,CHAN_CGC));}

static void draw_border(chan_info *cp, int ctxt)
{
  axis_info *ap;
  axis_context *ax;
  int h,w,displays,y0,y1;
  if (cp->waving) ap = cp->axis;
  else if (cp->ffting) ap = (cp->fft)->axis;
  else if (cp->lisp_graphing) ap = ((lisp_grf *)(cp->lisp_info))->axis;
  else ap = cp->axis;
  h = ap->height-2;
  if (cp->waving) displays = 1; else displays = 0;
  if (cp->ffting) displays++;
  if (cp->lisp_graphing) displays++;
  w = ap->width*displays - 1;
  y0 = 1+ap->y_offset;
  y1 = h+ap->y_offset;
  if (w > 0)
    {
      if (ctxt == CHAN_GC) ax = copy_context(cp); else ax = erase_context(cp);
      draw_line(ax,1,y0,1,y1);
      draw_line(ax,1,y0,w,y0);
      draw_line(ax,w,y0,w,y1);
      draw_line(ax,1,y1,w,y1);
    }
}

void erase_graph_border(chan_info *cp)   {if (cp->border_visible) draw_border(cp,CHAN_IGC); cp->border_visible = 0;}
void display_graph_border(chan_info *cp) {draw_border(cp,CHAN_GC); cp->border_visible = 1;}

void set_scrollbar(Widget w,float position,float range, int scrollbar_max) /* position and range 0 to 1.0 */
{
  int size,val;
  size = scrollbar_max * range;
  if (size > scrollbar_max) size = scrollbar_max; /* this can't happen!?! */
  if (size < 1) size = 1;
  val = scrollbar_max * position;
  if ((val+size)>scrollbar_max) val=scrollbar_max-size;
  if (val < 0) val = 0;
  /* fprintf(stderr,"%s %f %f to %d %d\n",XtName(w),position,range,val,size); */
  /* in Linux this occasionally generates bogus complaints about slide size and value */
  /* since the values going in are completely legal, I'll not try to kludge around the bug */
  XtVaSetValues(w,XmNsliderSize,size,XmNvalue,val,NULL);
}

float get_scrollbar(Widget w,int val, int scrollbar_max)
{
  int size;
  if (val == 0) return(0.0);
  XtVaGetValues(w,XmNsliderSize,&size,NULL);
  return((float)val/(float)(scrollbar_max-size));
}

/* 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
 */

#define SLOPPY_MOUSE 10

static int within_graph(chan_info *cp, int x, int y)
{
  axis_info *ap;
  fft_info *fp;
  snd_state *ss;
  int x0,x1;
  x0 = x-SLOPPY_MOUSE;
  x1 = x+SLOPPY_MOUSE;
  if (cp->waving)
    {
      ap = cp->axis;
      /* does (x,y) fall within the current axis bounds x_axis_x0|x1, y_axis_y0|y1 */
      if (((x0<=ap->x_axis_x1) && (x1>=ap->x_axis_x0)) && ((y<=ap->y_axis_y0) && (y>=ap->y_axis_y1)))
	return(WAVE);
    }
  if (cp->ffting)
    {
      fp = cp->fft;
      ap = fp->axis;
      ss = cp->state;
      if (fft_style(ss) != SONOGRAM)
	{
	  if (((x>=ap->x_axis_x0) && (x<=ap->x_axis_x1)) && (((y-SLOPPY_MOUSE)<=ap->y_axis_y0) && ((y+SLOPPY_MOUSE)>=ap->y_axis_y0)))
	    return(FFT);
	}
      else
	{
	  if ((((x-SLOPPY_MOUSE)<=ap->x_axis_x0) && ((x+SLOPPY_MOUSE)>=ap->x_axis_x0)) && ((y<=ap->y_axis_y0) && (y>=ap->y_axis_y1)))
	    return(FFT);
	}
      if (((x0<=ap->x_axis_x1) && (x1>=ap->x_axis_x0)) && ((y<=ap->y_axis_y0) && (y>=ap->y_axis_y1)))
	return(FFT_MAIN);
    }
  if ((cp->lisp_graphing) && (cp->lisp_info))
    {
      ap = (cp->lisp_info)->axis;
      if (((x0<=ap->x_axis_x1) && (x1>=ap->x_axis_x0)) && ((y<=ap->y_axis_y0) && (y>=ap->y_axis_y1)))
	return(LISP);
    }
  return(NOGRAPH);
}

void graph_key_press(Widget w,XtPointer clientData,XEvent *event,Boolean *cont) 
{
  /* called by every key-intercepting widget in the entire sound pane */
  XKeyEvent *ev = (XKeyEvent *)event;
  chan_info *ncp = (chan_info *)clientData;
  chan_info *cp;
  KeySym keysym;
  int key_state;
  int redisplay;
  snd_info *sp;
  if (snd_pointer_type(clientData) != CHAN_INFO) 
    {
      sp = (snd_info *)clientData;
      ncp = any_selected_channel(sp);
    }
  else
    {
      ncp = (chan_info *)clientData;
    }
  cp = virtual_selected_channel(ncp);
  sp = cp->sound;
  select_channel(sp,cp->chan);
  keysym = XKeycodeToKeysym(XtDisplay(w),(int)(ev->keycode),(ev->state & ShiftMask) ? 1 : 0);
  key_state = ev->state;
#if HAVE_GUILE
  if ((cp->lisp_graphing) && 
      (within_graph(cp,ev->x,ev->y) == LISP) &&
      (handle_key_press(cp->state,sp,cp,keysym,key_state) == TRUE))
    return;
#endif
  redisplay = keyboard_command(cp,keysym,key_state);
  if (redisplay == CURSOR_CLAIM_SELECTION)
    {
      reflect_edit_with_selection_in_menu();
      redisplay = CURSOR_UPDATE_DISPLAY;
    }
  /* if lisp graph has cursor? */
  handle_cursor_with_sync(cp,redisplay);
}

static int dragged = 0;
static Time mouse_down_time;
static mark *mouse_mark = NULL;
static mark *play_mark = NULL;
static int click_within_graph = NOGRAPH;

static chan_info *which_channel(snd_info *sp, int y)
{
  int i;
  chan_info *cp,*ncp;
  axis_info *ap;
  ncp = NULL;
  for (i=0;i<sp->nchans;i++)
    {
      cp = sp->chans[i];
      ap = cp->axis;
      if (y < ap->y_offset) return(ncp);
      ncp = cp;
    }
  return(ncp);
}

static int fft_axis_start = 0;

static void graph_button_press(Widget w,XtPointer clientData,XEvent *event,Boolean *cont) 
{
  chan_info *cp = (chan_info *)clientData;
  XButtonEvent *ev = (XButtonEvent *)event;
  snd_info *sp;
  snd_state *ss;
  sp = cp->sound;
  ss = cp->state;
  /* if combining, figure out which virtual channel the mouse is in */
  if (sp->combining == CHANNELS_COMBINED) cp = which_channel(sp,ev->y);
  start_selection(cp,ev->x);
  /* cp->cursor_on = 1; */
  mouse_down_time = ev->time;
  select_channel(sp,cp->chan);
  dragged = 0;
  mouse_mark = hit_mark(cp,ev->x,ev->y);
  if (!(mouse_mark)) play_mark = hit_triangle(cp,ev->x,ev->y);
  click_within_graph = within_graph(cp,ev->x,ev->y);
  if (click_within_graph == FFT) 
    {
      if (fft_style(ss) != SONOGRAM)
	fft_axis_start = ev->x;
      else fft_axis_start = ev->y;
    }
#if HAVE_GUILE
  else
    if (click_within_graph == LISP)
      handle_mouse_press(ss,sp,cp,ungrf_x((cp->lisp_info)->axis,ev->x),ungrf_y((cp->lisp_info)->axis,ev->y),ev->button,ev->state);
#endif
}

static float fft_axis_extent(chan_info *cp)
{
  axis_info *ap;
  fft_info *fp;
  snd_state *ss;
  fp = cp->fft;
  ap = fp->axis;
  ss = cp->state;
  if (fft_style(ss) != SONOGRAM)
    return((float)(ap->x_axis_x1 - ap->x_axis_x0));
  else return((float)(ap->y_axis_y0 - ap->y_axis_y1));
}

static char fftdes[64];
static char *describe_fft_point(chan_info *cp, int x, int y)
{
  float xf,yf;
  axis_info *ap;
  fft_info *fp;
  snd_state *ss;
  sono_info *si;
  int ind,time;
  fp = cp->fft;
  ap = fp->axis;
  ss = cp->state;
  if (x < ap->x_axis_x0) x = ap->x_axis_x0; else if (x > ap->x_axis_x1) x = ap->x_axis_x1;
  xf = ap->x0 + (ap->x1 - ap->x0) * (float)(x - ap->x_axis_x0)/(float)(ap->x_axis_x1 - ap->x_axis_x0);
  if (fft_style(ss) == NORMAL_FFT)        /* fp->data[bins] */
    {
      if (transform_type(ss) == FOURIER)
	ind = (fp->current_size * xf) / (float)snd_SRATE(cp);
      else ind = xf;
      sprintf(fftdes,"(%.1f Hz, transform val: %.3f%s (raw: %.3f)",xf,fp->data[ind]*fp->scale,(fft_log_magnitude(ss)) ? "dB" : "",fp->data[ind]);
    }
  else 
    {
      if (fft_style(ss) == SONOGRAM) 	  /* si->data[slices][bins] */
	{
	  yf = ap->y0 + (ap->y1 - ap->y0) * (float)(y - ap->y_axis_y0)/(float)(ap->y_axis_y1 - ap->y_axis_y0);
	  si = (sono_info *)(cp->sonogram_data);
	  if (transform_type(ss) == FOURIER)
	    ind = (fp->current_size * yf) / (float)snd_SRATE(cp);
	  else ind = yf;
	  time = si->target_slices * (float)(x - ap->x_axis_x0)/(float)(ap->x_axis_x1 - ap->x_axis_x0);
	  sprintf(fftdes,"(time: %.2f, freq: %.1f, val: %.3f (raw: %.3f))",xf,yf,si->data[time][ind]/si->scale,si->data[time][ind]);
	}
      else return("");
    }
  return(fftdes);
}

static int cursor_round(double x)
{
  int xint;
  float xfrac;
  xint = x;
  xfrac = x-xint;
  if (xfrac>.5) return(xint+1);
  return(xint);
}

static void graph_button_release(Widget w,XtPointer clientData,XEvent *event,Boolean *cont) 
{
  chan_info *cp = (chan_info *)clientData;
  snd_info *sp;
  axis_info *ap;
  mark *old_mark;
  int actax;
  XButtonEvent *ev = (XButtonEvent *)event;
  sp = cp->sound;
  if (sp->combining == CHANNELS_COMBINED) cp = which_channel(sp,ev->y);
  if (!dragged)
    {
      if (play_mark)
	{
	  old_mark = sp->playing_mark; /* needed because stop_playing clobbers sp->playing_mark */
	  if (sp->playing)
	    {
	      stop_playing(sp->playing);
	      set_play_button(sp,0);
	    }
	  if (play_mark != old_mark)
	    {
	      start_playing(cp,play_mark->samp);
	      sp->playing_mark = play_mark;
	      set_play_button(sp,1);
	    }
	  else sp->playing_mark = NULL;
	  play_mark = NULL;
	}
      else
	{
	  actax = within_graph(cp,ev->x,ev->y);
	  if (actax == WAVE)
	    {
	      if (ev->button == Button2) /* the middle button */
		{
		  cp->cursor_on = 1;
		  cursor_moveto(cp,cursor_round(ungrf_x(cp->axis,ev->x) * (double)snd_SRATE(cp)));
		  draw_graph_cursor(cp);
		  paste_region(0,cp,"Btn2");
		}
	      else 
		{
		  if ((ev->state) & (snd_ShiftMask | snd_ControlMask | snd_MetaMask))
		    {
		      /* zoom request -> each added key zooms closer, as does each successive click */
		      ap = cp->axis;
		      if (ev->state & snd_ShiftMask) ap->zx *= .5;
		      if (ev->state & snd_ControlMask) ap->zx *= .5;
		      if (ev->state & snd_MetaMask) ap->zx *= .5;
		      ap->sx = (((double)(cp->cursor)/(double)snd_SRATE(cp) - ap->zx*0.5*(ap->xmax-ap->xmin)) - ap->xmin)/(ap->xmax - ap->xmin);
		      apply_x_axis_change(ap,cp,cp->sound);
		      resize_sx(cp);
		      XtVaSetValues(chan_widget(cp,W_chn_zx),XmNvalue,(int)(sqrt(ap->zx)*SCROLLBAR_MAX),NULL);
		    }
		  else
		    {
		      cp->cursor_on = 1;
		      cursor_moveto(cp,cursor_round(ungrf_x(cp->axis,ev->x) * (double)snd_SRATE(cp)));
		      draw_graph_cursor(cp);
		    }
		}
	    }
	  else
	    {
	      if (actax == FFT_MAIN)
		report_in_minibuffer(cp->sound,describe_fft_point(cp,ev->x,ev->y));
#if HAVE_GUILE
	      else
		if (actax == LISP)
		  handle_mouse_release(cp->state,sp,cp,ungrf_x((cp->lisp_info)->axis,ev->x),ungrf_y((cp->lisp_info)->axis,ev->y),ev->button,ev->state);
#endif
	    }
	}
    }
  else
    {
      /* lisp graph dragged? */
      if (mouse_mark)
	{
	  finish_moving_mark(cp,mouse_mark);
	  mouse_mark = NULL;
	  dragged = 0;
	}
      else
	{
	  if (play_mark)
	    {
	      finish_moving_play_mark(cp);
	      stop_playing(((snd_info *)(cp->sound))->playing);
	      play_mark = NULL;
	      dragged = 0;
	    }
	  else
	    {
	      if (click_within_graph == WAVE)
		{
		  define_selection(cp);
		  dragged = 0;
		  reflect_edit_with_selection_in_menu();
		}
	    }
	}
    }
}

static Time first_time = 0;
static int mouse_cursor = 0;

static void graph_button_motion(Widget w,XtPointer clientData,XEvent *event,Boolean *cont) 
{ /* mouse drag */
  chan_info *cp = (chan_info *)clientData;
  snd_info *sp;
  snd_state *ss;
  Time mouse_time;
  int time_interval,samps;
  float old_cutoff;
  XMotionEvent *ev = (XMotionEvent *)event;
  /* this needs to be a little slow about deciding that we are dragging, as opposed to a slow click */
  mouse_time = ev->time;
  if ((mouse_time - mouse_down_time) < (0.5 * XtGetMultiClickTime(XtDisplay(w)))) return;
  sp = cp->sound;
  if (sp->combining == CHANNELS_COMBINED) cp = which_channel(sp,ev->y);
  select_channel(sp,cp->chan);
  if (mouse_mark)
    {
      move_mark(cp,mouse_mark,ev->x,ev->y);
      dragged = 1;
    }
  else
    {
      if (play_mark)
	{
	  if (!dragged)
	    {
	      first_time = mouse_time;
	      dragged = 1;
	      sp->srate = 0.0;
	      mouse_cursor = cp->cursor;
	      start_playing(cp,play_mark->samp);
	      set_play_button(sp,1);
	    }
	  else
	    {
	      time_interval = mouse_time - first_time;
	      first_time = mouse_time;
	      samps = move_play_mark(cp,&mouse_cursor,ev->x);
	      if (time_interval != 0)
		sp->srate = (float)(samps*1000)/(float)(time_interval*snd_SRATE(sp));
	      else sp->srate = 0.0;
	    }
	}
      else
	{
	  if (click_within_graph == WAVE)
	    {
	      if (!dragged) 
		{
		  deactivate_selection();
		  create_selection(cp);
		}
	      dragged = 1;
	      move_selection(cp,ev->x);
	    }
	  else
	    {
	      ss = cp->state;
	      if (click_within_graph == FFT)
		{
		  /* change spectro_cutoff(ss) and redisplay fft */
		  old_cutoff = spectro_cutoff(ss);
		  if (fft_style(ss) != SONOGRAM)
		    {
		      set_spectro_cutoff(ss,spectro_cutoff(ss) + ((float)(fft_axis_start - ev->x)/fft_axis_extent(cp)));
		      fft_axis_start = ev->x;
		    }
		  else 
		    {
		      set_spectro_cutoff(ss,spectro_cutoff(ss) + ((float)(ev->y - fft_axis_start)/fft_axis_extent(cp)));
		      fft_axis_start = ev->y;
		    }
		  if (spectro_cutoff(ss) > 1.0) set_spectro_cutoff(ss,1.0);
		  if (spectro_cutoff(ss) < 0.01) set_spectro_cutoff(ss,0.01);
		  if (old_cutoff != spectro_cutoff(ss)) 
		    {
		      reflect_spectro(ss);
		      if (fft_style(ss) != NORMAL_FFT)
			map_over_chans(ss,sono_update,NULL);
		      else map_over_chans(ss,update_graph,NULL);
		    }
		}
	      else
		{
#if HAVE_GUILE
		  if (click_within_graph == LISP)
		    {
		      handle_mouse_drag(ss,cp->sound,cp,
					ungrf_x((cp->lisp_info)->axis,ev->x),
					ungrf_y((cp->lisp_info)->axis,ev->y));
		      return;
		    }
#endif		    
		  if ((verbose_cursor(ss)) && (within_graph(cp,ev->x,ev->y) == FFT_MAIN))
		    report_in_minibuffer(cp->sound,describe_fft_point(cp,ev->x,ev->y));
		}
	    }
	}
    }
}

void add_chan_callbacks(chan_info *cp)
{
  Widget *cw;
  cw = (cp->cgx)->chan_widgets;
  XtAddCallback(cw[W_chn_graph],XmNresizeCallback,Channel_Resize_Callback,(XtPointer)cp);
  XtAddCallback(cw[W_chn_graph],XmNexposeCallback,Channel_Expose_Callback,(XtPointer)cp);
  XtAddEventHandler(cw[W_chn_graph],EnterWindowMask,FALSE,graph_mouse_enter,(XtPointer)cp);
  XtAddEventHandler(cw[W_chn_graph],LeaveWindowMask,FALSE,graph_mouse_leave,(XtPointer)cp);
  XtAddEventHandler(cw[W_chn_graph],ButtonPressMask,FALSE,graph_button_press,(XtPointer)cp);
  XtAddEventHandler(cw[W_chn_graph],ButtonMotionMask,FALSE,graph_button_motion,(XtPointer)cp);
  XtAddEventHandler(cw[W_chn_graph],ButtonReleaseMask,FALSE,graph_button_release,(XtPointer)cp);
  XtAddEventHandler(cw[W_chn_graph],KeyPressMask,FALSE,graph_key_press,(XtPointer)cp);
}

static void remove_chan_callbacks(chan_info *cp)
{
  Widget *cw;
  cw = (cp->cgx)->chan_widgets;
  XtRemoveCallback(cw[W_chn_graph],XmNresizeCallback,Channel_Resize_Callback,(XtPointer)cp);
  XtRemoveCallback(cw[W_chn_graph],XmNexposeCallback,Channel_Expose_Callback,(XtPointer)cp);
  XtRemoveEventHandler(cw[W_chn_graph],EnterWindowMask,FALSE,graph_mouse_enter,(XtPointer)cp);
  XtRemoveEventHandler(cw[W_chn_graph],LeaveWindowMask,FALSE,graph_mouse_leave,(XtPointer)cp);
  XtRemoveEventHandler(cw[W_chn_graph],ButtonPressMask,FALSE,graph_button_press,(XtPointer)cp);
  XtRemoveEventHandler(cw[W_chn_graph],ButtonMotionMask,FALSE,graph_button_motion,(XtPointer)cp);
  XtRemoveEventHandler(cw[W_chn_graph],ButtonReleaseMask,FALSE,graph_button_release,(XtPointer)cp);
  XtRemoveEventHandler(cw[W_chn_graph],KeyPressMask,FALSE,graph_key_press,(XtPointer)cp);
}

void chan_info_cleanup(chan_info *cp)
{
  int i;
  if ((cp) && (cp->cgx))
    {
      if ((cp->cgx)->fft_in_progress) 
	{
	  XtRemoveWorkProc((cp->cgx)->fft_in_progress);
	  (cp->cgx)->fft_in_progress = 0;
	}
      XtVaSetValues(chan_widget(cp,W_chn_w),XmNset,TRUE,NULL);
      XtVaSetValues(chan_widget(cp,W_chn_f),XmNset,FALSE,NULL);
      remove_chan_callbacks(cp);
      for (i=NUM_CHAN_WIDGETS-1;i>=0;i--)
	{
	  if ((chan_widget(cp,i)) && (XtIsManaged(chan_widget(cp,i)))) XtUnmanageChild(chan_widget(cp,i));
	}
    }
}


static XPoint *points = NULL;
static XPoint *points1 = NULL;

XPoint *points_address(int which) {if (which == 0) return(points); else return(points1);}

void allocate_grf_points(void)
{
  if (!points) points = (XPoint *)calloc(POINT_BUFFER_SIZE,sizeof(XPoint));
  if (!points1) points1 = (XPoint *)calloc(POINT_BUFFER_SIZE,sizeof(XPoint));
}

void set_grf_points(int xi, int j, int ymin, int ymax)
{
  if (j >= POINT_BUFFER_SIZE) {fprintf(stderr,"overran points!"); return;}
  points[j].x = xi;
  points1[j].x = xi;
  points[j].y = ymax;
  points1[j].y = ymin;
}

void set_grf_point(int xi, int j, int yi)
{
  points[j].x = xi;
  points[j].y = yi;
}

static Pixel g_green = 0;
static Pixel g_blue = 0;

static int get_channel_color(snd_state *ss, chan_info *cp)
{
  /* undoubtedly someone will want their own colors */
  snd_info *sp;
  int scr;
  Colormap cmap;
  XColor tmp_color,ignore;
  Display *dpy;
  sp = cp->sound;
  if (sp->combining == CHANNELS_SUPERIMPOSED)
    {
      if (cp->chan == 0) set_color(copy_context(cp),(ss->sgx)->black); else
      if (cp->chan == 1) set_color(copy_context(cp),(ss->sgx)->red); else
	{
	  if ((!g_green) || (!g_blue))
	    {
	      tmp_color.flags = DoRed | DoGreen | DoBlue;
	      dpy=XtDisplay(main_SHELL(ss));
	      scr=DefaultScreen(dpy);
	      cmap=DefaultColormap(dpy,scr);
	      if (!g_green)
		{
		  XAllocNamedColor(dpy,cmap,"green",&tmp_color,&ignore);
		  g_green = tmp_color.pixel;
		}
	      if (!g_blue)
		{
		  XAllocNamedColor(dpy,cmap,"blue",&tmp_color,&ignore);
		  g_blue = tmp_color.pixel;
		}
	    }
	  if (cp->chan == 2) set_color(copy_context(cp),g_green); else
	    set_color(copy_context(cp),g_blue);    
	}
      return(1);
    }
  return(0);
}

void draw_both_grf_points(snd_state *ss,chan_info *cp, int j)
{
  axis_context *ax;
  int need_reset;
  need_reset = get_channel_color(ss,cp);
  ax = copy_context(cp);
  switch (graph_style(ss))
    {
    case GRAPH_LINES:
      XDrawLines(ax->dp,ax->wn,ax->gc,points,j,CoordModeOrigin);
      XDrawLines(ax->dp,ax->wn,ax->gc,points1,j,CoordModeOrigin);
      break;
    case GRAPH_DOTS:
      draw_points(ax,points,j,dot_size(ss));
      draw_points(ax,points1,j,dot_size(ss));
      break;
    case GRAPH_FILLED:
      fill_two_sided_polygons(ax,points,points1,j);
      break;
    }
  if (need_reset) reset_color(cp);
}

void draw_grf_points(snd_state *ss,chan_info *cp, int j, axis_info *ap)
{
  int need_reset;
  need_reset = get_channel_color(ss,cp);
  switch (graph_style(ss))
    {
    case GRAPH_LINES: draw_lines(copy_context(cp),points,j); break;
    case GRAPH_DOTS: draw_points(copy_context(cp),points,j,dot_size(ss)); break;
    case GRAPH_FILLED: fill_polygons(copy_context(cp),points,j,ap); break;
    }
  if (need_reset) reset_color(cp);
}

void draw_both_grfs(axis_context *ax, int j) /* only for enved wave */
{
  XDrawLines(ax->dp,ax->wn,ax->gc,points,j,CoordModeOrigin);
  XDrawLines(ax->dp,ax->wn,ax->gc,points1,j,CoordModeOrigin);
}

static XtWorkProcId watch_mouse_button = 0;
static Boolean WatchMouse(XtPointer cp)
{
  if (watch_mouse_button)
    {
      move_mark_2((chan_info *)cp);
      return(FALSE);
    }
  else return(TRUE);
}

void StartMarkWatch(chan_info *cp)
{
  watch_mouse_button = XtAppAddWorkProc(XtWidgetToApplicationContext(main_PANE(cp)),WatchMouse,(XtPointer)cp);
}

void CancelMarkWatch(void)
{
  if (watch_mouse_button) XtRemoveWorkProc(watch_mouse_button);
  watch_mouse_button = 0;
}


/* colormaps */

static int hsv_colormap[] = {65535, 0, 0, 65535, 6147, 0, 65535, 12287, 0, 65535, 18428, 0, 65535, 24575, 0, 65535, 30722, 0, 65535, 36863, 0, 65535, 43004, 0, 65535, 49151, 0, 65535, 55298, 0, 65535, 61439, 0, 63490, 65535, 0, 57343, 65535, 0, 51195, 65535, 0, 45055, 65535, 0, 38914, 65535, 0, 32767, 65535, 0, 26620, 65535, 0, 20479, 65535, 0, 14339, 65535, 0, 8191, 65535, 0, 2044, 65535, 0, 0, 65535, 4095, 0, 65535, 10236, 0, 65535, 16383, 0, 65535, 22530, 0, 65535, 28671, 0, 65535, 34812, 0, 65535, 40959, 0, 65535, 47106, 0, 65535, 53247, 0, 65535, 59387, 0, 65535, 65535, 0, 59387, 65535, 0, 53247, 65535, 0, 47106, 65535, 0, 40959, 65535, 0, 34812, 65535, 0, 28671, 65535, 0, 22530, 65535, 0, 16383, 65535, 0, 10236, 65535, 0, 4095, 65535, 2044, 0, 65535, 8191, 0, 65535, 14339, 0, 65535, 20479, 0, 65535, 26620, 0, 65535, 32767, 0, 65535, 38914, 0, 65535, 45055, 0, 65535, 51195, 0, 65535, 57343, 0, 65535, 63490, 0, 65535, 65535, 0, 61439, 65535, 0, 55298, 65535, 0, 49151, 65535, 0, 43004, 65535, 0, 36863, 65535, 0, 30722, 65535, 0, 24575, 65535, 0, 18428, 65535, 0, 12287, 65535, 0, 6147};

static int gray_colormap[] = {0, 0, 0, 1042, 1042, 1042, 2077, 2077, 2077, 3119, 3119, 3119, 4161, 4161, 4161, 5203, 5203, 5203, 6238, 6238, 6238, 7280, 7280, 7280, 8322, 8322, 8322, 9364, 9364, 9364, 10400, 10400, 10400, 11442, 11442, 11442, 12484, 12484, 12484, 13519, 13519, 13519, 14561, 14561, 14561, 15603, 15603, 15603, 16645, 16645, 16645, 17681, 17681, 17681, 18723, 18723, 18723, 19765, 19765, 19765, 20807, 20807, 20807, 21842, 21842, 21842, 22884, 22884, 22884, 23926, 23926, 23926, 24968, 24968, 24968, 26004, 26004, 26004, 27046, 27046, 27046, 28088, 28088, 28088, 29123, 29123, 29123, 30165, 30165, 30165, 31207, 31207, 31207, 32249, 32249, 32249, 33285, 33285, 33285, 34327, 34327, 34327, 35369, 35369, 35369, 36411, 36411, 36411, 37446, 37446, 37446, 38488, 38488, 38488, 39530, 39530, 39530, 40566, 40566, 40566, 41608, 41608, 41608, 42650, 42650, 42650, 43692, 43692, 43692, 44727, 44727, 44727, 45769, 45769, 45769, 46811, 46811, 46811, 47853, 47853, 47853, 48889, 48889, 48889, 49931, 49931, 49931, 50973, 50973, 50973, 52015, 52015, 52015, 53050, 53050, 53050, 54092, 54092, 54092, 55134, 55134, 55134, 56170, 56170, 56170, 57212, 57212, 57212, 58254, 58254, 58254, 59296, 59296, 59296, 60331, 60331, 60331, 61373, 61373, 61373, 62415, 62415, 62415, 63457, 63457, 63457, 64492, 64492, 64492, 65535, 65535, 65535};

static int hot_colormap[] = {2732, 0, 0, 5459, 0, 0, 8191, 0, 0, 10924, 0, 0, 13650, 0, 0, 16383, 0, 0, 19116, 0, 0, 21842, 0, 0, 24575, 0, 0, 27308, 0, 0, 30034, 0, 0, 32767, 0, 0, 35500, 0, 0, 38226, 0, 0, 40959, 0, 0, 43692, 0, 0, 46418, 0, 0, 49151, 0, 0, 51884, 0, 0, 54610, 0, 0, 57343, 0, 0, 60075, 0, 0, 62802, 0, 0, 65535, 0, 0, 65535, 2732, 0, 65535, 5459, 0, 65535, 8191, 0, 65535, 10924, 0, 65535, 13650, 0, 65535, 16383, 0, 65535, 19116, 0, 65535, 21842, 0, 65535, 24575, 0, 65535, 27308, 0, 65535, 30034, 0, 65535, 32767, 0, 65535, 35500, 0, 65535, 38226, 0, 65535, 40959, 0, 65535, 43692, 0, 65535, 46418, 0, 65535, 49151, 0, 65535, 51884, 0, 65535, 54610, 0, 65535, 57343, 0, 65535, 60075, 0, 65535, 62802, 0, 65535, 65535, 0, 65535, 65535, 4095, 65535, 65535, 8191, 65535, 65535, 12287, 65535, 65535, 16383, 65535, 65535, 20479, 65535, 65535, 24575, 65535, 65535, 28671, 65535, 65535, 32767, 65535, 65535, 36863, 65535, 65535, 40959, 65535, 65535, 45055, 65535, 65535, 49151, 65535, 65535, 53247, 65535, 65535, 57343, 65535, 65535, 61439, 65535, 65535, 65535};

static int cool_colormap[] = {0, 65535, 65535, 1042, 64492, 65535, 2077, 63457, 65535, 3119, 62415, 65535, 4161, 61373, 65535, 5203, 60331, 65535, 6238, 59296, 65535, 7280, 58254, 65535, 8322, 57212, 65535, 9364, 56170, 65535, 10400, 55134, 65535, 11442, 54092, 65535, 12484, 53050, 65535, 13519, 52015, 65535, 14561, 50973, 65535, 15603, 49931, 65535, 16645, 48889, 65535, 17681, 47853, 65535, 18723, 46811, 65535, 19765, 45769, 65535, 20807, 44727, 65535, 21842, 43692, 65535, 22884, 42650, 65535, 23926, 41608, 65535, 24968, 40566, 65535, 26004, 39530, 65535, 27046, 38488, 65535, 28088, 37446, 65535, 29123, 36411, 65535, 30165, 35369, 65535, 31207, 34327, 65535, 32249, 33285, 65535, 33285, 32249, 65535, 34327, 31207, 65535, 35369, 30165, 65535, 36411, 29123, 65535, 37446, 28088, 65535, 38488, 27046, 65535, 39530, 26004, 65535, 40566, 24968, 65535, 41608, 23926, 65535, 42650, 22884, 65535, 43692, 21842, 65535, 44727, 20807, 65535, 45769, 19765, 65535, 46811, 18723, 65535, 47853, 17681, 65535, 48889, 16645, 65535, 49931, 15603, 65535, 50973, 14561, 65535, 52015, 13519, 65535, 53050, 12484, 65535, 54092, 11442, 65535, 55134, 10400, 65535, 56170, 9364, 65535, 57212, 8322, 65535, 58254, 7280, 65535, 59296, 6238, 65535, 60331, 5203, 65535, 61373, 4161, 65535, 62415, 3119, 65535, 63457, 2077, 65535, 64492, 1042, 65535, 65535, 0, 65535};

static int bone_colormap[] = {0, 0, 340, 910, 910, 1592, 1821, 1821, 2844, 2732, 2732, 4095, 3643, 3643, 5347, 4548, 4548, 6599, 5459, 5459, 7851, 6370, 6370, 9102, 7280, 7280, 10354, 8191, 8191, 11606, 9102, 9102, 12857, 10013, 10013, 14109, 10924, 10924, 15361, 11835, 11835, 16613, 12740, 12740, 17864, 13650, 13650, 19116, 14561, 14561, 20368, 15472, 15472, 21619, 16383, 16383, 22871, 17294, 17294, 24123, 18205, 18205, 25375, 19116, 19116, 26620, 20027, 20027, 27872, 20931, 20931, 29123, 21842, 22183, 30034, 22753, 23435, 30945, 23664, 24687, 31856, 24575, 25938, 32767, 25486, 27190, 33678, 26397, 28442, 34589, 27308, 29693, 35500, 28219, 30945, 36411, 29123, 32197, 37315, 30034, 33449, 38226, 30945, 34700, 39137, 31856, 35952, 40048, 32767, 37204, 40959, 33678, 38455, 41870, 34589, 39707, 42781, 35500, 40959, 43692, 36411, 42211, 44603, 37315, 43462, 45507, 38226, 44714, 46418, 39137, 45966, 47329, 40048, 47217, 48240, 40959, 48469, 49151, 41870, 49721, 50062, 42781, 50973, 50973, 44203, 51884, 51884, 45625, 52794, 52794, 47047, 53699, 53699, 48469, 54610, 54610, 49891, 55521, 55521, 51313, 56432, 56432, 52736, 57343, 57343, 54158, 58254, 58254, 55580, 59165, 59165, 57002, 60075, 60075, 58424, 60986, 60986, 59846, 61891, 61891, 61268, 62802, 62802, 62690, 63713, 63713, 64112, 64624, 64624, 65535, 65535, 65535};

static int copper_colormap[] = {0, 0, 0, 1297, 812, 517, 2601, 1625, 1035, 3899, 2437, 1553, 5203, 3250, 2070, 6501, 4063, 2588, 7798, 4875, 3106, 9102, 5688, 3624, 10400, 6501, 4141, 11704, 7313, 4659, 13002, 8126, 5177, 14306, 8938, 5694, 15603, 9751, 6212, 16901, 10564, 6730, 18205, 11376, 7248, 19503, 12189, 7765, 20807, 13002, 8277, 22104, 13814, 8794, 23402, 14627, 9312, 24706, 15440, 9830, 26004, 16252, 10347, 27308, 17065, 10865, 28606, 17877, 11383, 29903, 18690, 11901, 31207, 19503, 12418, 32505, 20315, 12936, 33809, 21128, 13454, 35107, 21941, 13972, 36411, 22753, 14489, 37708, 23566, 15007, 39006, 24379, 15525, 40310, 25191, 16042, 41608, 26004, 16560, 42912, 26816, 17078, 44209, 27629, 17596, 45507, 28442, 18113, 46811, 29254, 18631, 48109, 30067, 19149, 49413, 30880, 19667, 50710, 31692, 20184, 52015, 32505, 20702, 53312, 33317, 21220, 54610, 34130, 21737, 55914, 34943, 22255, 57212, 35755, 22773, 58516, 36568, 23291, 59813, 37381, 23808, 61111, 38193, 24326, 62415, 39006, 24837, 63713, 39819, 25355, 65017, 40631, 25873, 65535, 41444, 26390, 65535, 42256, 26908, 65535, 43069, 27426, 65535, 43882, 27944, 65535, 44694, 28461, 65535, 45507, 28979, 65535, 46320, 29497, 65535, 47132, 30015, 65535, 47945, 30532, 65535, 48758, 31050, 65535, 49570, 31568, 65535, 50383, 32085, 65535, 51195, 32603};

static int pink_colormap[] = {7726, 0, 0, 12838, 6743, 6743, 16429, 9535, 9535, 19365, 11678, 11678, 21908, 13480, 13480, 24188, 15073, 15073, 26272, 16514, 16514, 28199, 17838, 17838, 30008, 19070, 19070, 31712, 20224, 20224, 33324, 21318, 21318, 34864, 22360, 22360, 36345, 23350, 23350, 37761, 24306, 24306, 39130, 25224, 25224, 40448, 26109, 26109, 41726, 26967, 26967, 42971, 27793, 27793, 44177, 28599, 28599, 45350, 29385, 29385, 46490, 30146, 30146, 47611, 30893, 30893, 48699, 31620, 31620, 49767, 32328, 32328, 50226, 33914, 33029, 50671, 35434, 33704, 51117, 36883, 34373, 51562, 38285, 35028, 52002, 39635, 35670, 52434, 40939, 36306, 52867, 42204, 36922, 53299, 43430, 37531, 53719, 44622, 38134, 54145, 45782, 38724, 54557, 46916, 39307, 54977, 48024, 39884, 55390, 49105, 40448, 55796, 50167, 41005, 56202, 51202, 41555, 56602, 52218, 42099, 57002, 53214, 42637, 57402, 54197, 43167, 57795, 55154, 43692, 58188, 56097, 44209, 58575, 57028, 44721, 58961, 57946, 45225, 59348, 58843, 45723, 59728, 59728, 46215, 60108, 60108, 47657, 60488, 60488, 49052, 60862, 60862, 50409, 61235, 61235, 51726, 61602, 61602, 53017, 61969, 61969, 54276, 62336, 62336, 55501, 62697, 62697, 56707, 63064, 63064, 57880, 63418, 63418, 59033, 63778, 63778, 60167, 64132, 64132, 61281, 64486, 64486, 62369, 64840, 64840, 63444, 65187, 65187, 64499, 65535, 65535, 65535};

static int jet_colormap[] = {0, 0, 36863, 0, 0, 40959, 0, 0, 45055, 0, 0, 49151, 0, 0, 53247, 0, 0, 57343, 0, 0, 61439, 0, 0, 65535, 0, 4095, 65535, 0, 8191, 65535, 0, 12287, 65535, 0, 16383, 65535, 0, 20479, 65535, 0, 24575, 65535, 0, 28671, 65535, 0, 32767, 65535, 0, 36863, 65535, 0, 40959, 65535, 0, 45055, 65535, 0, 49151, 65535, 0, 53247, 65535, 0, 57343, 65535, 0, 61439, 65535, 0, 65535, 65535, 4095, 65535, 65535, 8191, 65535, 61439, 12287, 65535, 57343, 16383, 65535, 53247, 20479, 65535, 49151, 24575, 65535, 45055, 28671, 65535, 40959, 32767, 65535, 36863, 36863, 65535, 32767, 40959, 65535, 28671, 45055, 65535, 24575, 49151, 65535, 20479, 53247, 65535, 16383, 57343, 65535, 12287, 61439, 65535, 8191, 65535, 65535, 4095, 65535, 65535, 0, 65535, 61439, 0, 65535, 57343, 0, 65535, 53247, 0, 65535, 49151, 0, 65535, 45055, 0, 65535, 40959, 0, 65535, 36863, 0, 65535, 32767, 0, 65535, 28671, 0, 65535, 24575, 0, 65535, 20479, 0, 65535, 16383, 0, 65535, 12287, 0, 65535, 8191, 0, 65535, 4095, 0, 65535, 0, 0, 61439, 0, 0, 57343, 0, 0, 53247, 0, 0, 49151, 0, 0, 45055, 0, 0, 40959, 0, 0, 36863, 0, 0};

static int prism_colormap[] = {65535, 0, 0, 65535, 32767, 0, 65535, 65535, 0, 0, 65535, 0, 0, 0, 65535, 43692, 0, 65535, 65535, 0, 0, 65535, 32767, 0, 65535, 65535, 0, 0, 65535, 0, 0, 0, 65535, 43692, 0, 65535, 65535, 0, 0, 65535, 32767, 0, 65535, 65535, 0, 0, 65535, 0, 0, 0, 65535, 43692, 0, 65535, 65535, 0, 0, 65535, 32767, 0, 65535, 65535, 0, 0, 65535, 0, 0, 0, 65535, 43692, 0, 65535, 65535, 0, 0, 65535, 32767, 0, 65535, 65535, 0, 0, 65535, 0, 0, 0, 65535, 43692, 0, 65535, 65535, 0, 0, 65535, 32767, 0, 65535, 65535, 0, 0, 65535, 0, 0, 0, 65535, 43692, 0, 65535, 65535, 0, 0, 65535, 32767, 0, 65535, 65535, 0, 0, 65535, 0, 0, 0, 65535, 43692, 0, 65535, 65535, 0, 0, 65535, 32767, 0, 65535, 65535, 0, 0, 65535, 0, 0, 0, 65535, 43692, 0, 65535, 65535, 0, 0, 65535, 32767, 0, 65535, 65535, 0, 0, 65535, 0, 0, 0, 65535, 43692, 0, 65535, 65535, 0, 0, 65535, 32767, 0, 65535, 65535, 0, 0, 65535, 0, 0, 0, 65535, 43692, 0, 65535, 65535, 0, 0, 65535, 32767, 0, 65535, 65535, 0, 0, 65535, 0};


static int sono_size = -1;
static Pixel grays[GRAY_SCALES];
static int grays_allocated = -1;
static XRectangle *sono_data[GRAY_SCALES];

void allocate_color_map(snd_state *ss, int colormap)
{
  static int warned_color = 0;
  int i,j;
  Colormap cmap;
  XColor tmp_color;
  Display *dpy;
  int scr;
  int *curmap;
  if (grays_allocated != colormap)
    {
      switch (colormap)
	{
	case 0: curmap = gray_colormap; break;
	case 1: curmap = hsv_colormap; break;
	case 2: curmap = hot_colormap; break;
	case 3: curmap = cool_colormap; break;
	case 4: curmap = bone_colormap; break;
	case 5: curmap = copper_colormap; break;
	case 6: curmap = pink_colormap; break;
	case 7: curmap = jet_colormap; break;
	case 8: curmap = prism_colormap; break;
	default: curmap = gray_colormap; break;
	}
      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 (grays_allocated != -1) XFreeColors(dpy,cmap,grays,GRAY_SCALES,0);
      j = 0;
      for (i=0;i<GRAY_SCALES;i++)
	{
	  tmp_color.red = curmap[j++];
	  tmp_color.green = curmap[j++];
	  tmp_color.blue = curmap[j++];
	  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 == 0)
		    snd_printf(ss,"can't even allocated black?!?");
		  warned_color = 1;
		}
	    }
	  grays[i] = tmp_color.pixel;
	}
      grays_allocated = colormap;
    }
}

void allocate_sono_rects(snd_state *ss, int size)
{
  int i;
  if (spectro_color(ss) != -1) 
    allocate_color_map(ss,spectro_color(ss));
  else allocate_color_map(ss,0);
  if (size > sono_size)
    {
      for (i=0;i<GRAY_SCALES;i++)
	{
	  if ((sono_size > 0) && (sono_data[i])) free(sono_data[i]); 
	  sono_data[i] = NULL;
	}
      for (i=0;i<GRAY_SCALES;i++)
	{
	  sono_data[i] = (XRectangle *)calloc(size,sizeof(XRectangle));
	}
      sono_size = size;
    }
}

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 get_current_color(int colormap, int j, int *r, int *g, int *b)
{
  switch (colormap)
    {
    case 0: (*r) = gray_colormap[j*3];   (*g) = gray_colormap[j*3+1];   (*b) = gray_colormap[j*3+2];   break;
    case 1: (*r) = hsv_colormap[j*3];    (*g) = hsv_colormap[j*3+1];    (*b) = hsv_colormap[j*3+2];    break;
    case 2: (*r) = hot_colormap[j*3];    (*g) = hot_colormap[j*3+1];    (*b) = hot_colormap[j*3+2];    break;
    case 3: (*r) = cool_colormap[j*3];   (*g) = cool_colormap[j*3+1];   (*b) = cool_colormap[j*3+2];   break;
    case 4: (*r) = bone_colormap[j*3];   (*g) = bone_colormap[j*3+1];   (*b) = bone_colormap[j*3+2];   break;
    case 5: (*r) = copper_colormap[j*3]; (*g) = copper_colormap[j*3+1]; (*b) = copper_colormap[j*3+2]; break;
    case 6: (*r) = pink_colormap[j*3];   (*g) = pink_colormap[j*3+1];   (*b) = pink_colormap[j*3+2];   break;
    case 7: (*r) = jet_colormap[j*3];    (*g) = jet_colormap[j*3+1];    (*b) = jet_colormap[j*3+2];    break;
    case 8: (*r) = prism_colormap[j*3];  (*g) = prism_colormap[j*3+1];  (*b) = prism_colormap[j*3+2];  break;
    default: (*r) = gray_colormap[j*3];  (*g) = gray_colormap[j*3+1];   (*b) = gray_colormap[j*3+2];   break;
    }
}

void draw_sono_rectangles(chan_info *cp, int color, int jmax)
{
  set_color(copy_context(cp),grays[color]);
  fill_rectangles(copy_context(cp),sono_data[color],jmax);
}

void draw_spectro_line(chan_info *cp, int color, int x0, int y0, int x1, int y1)
{
  set_color(copy_context(cp),grays[color]);
  draw_line(copy_context(cp),x0,y0,x1,y1);
}

static void change_channel_style(snd_info *sp, int new_style)
{
  int i,j,k,old_style;
  snd_state *ss;
  chan_info *ncp,*cp,*pcp;
  int height[1];
  chan_context *mcgx;
  Widget *cw;
  axis_info *ap;
  chan_context *cx;
  if ((sp) && (sp->nchans > 1))
    {
      ss = sp->state;
      old_style = sp->combining;
      if (new_style != old_style)
	{
	  sp->combining = new_style;
	  if (old_style == CHANNELS_COMBINED)
	    hide_gz_scrollbars(sp);
	  else 
	    {
	      if (new_style == CHANNELS_COMBINED)
		show_gz_scrollbars(sp);
	    }
	  if (old_style == CHANNELS_SUPERIMPOSED)
	    {
	      syncb(sp,FALSE);
	      XtVaSetValues(snd_widget(sp,W_snd_combine),XmNselectColor,(ss->sgx)->text,NULL);
	    }
	  else
	    {
	      if (new_style == CHANNELS_SUPERIMPOSED)
		{
		  syncb(sp,TRUE);
		  XtVaSetValues(snd_widget(sp,W_snd_combine),XmNselectColor,(ss->sgx)->mixpix,NULL);
		  apply_y_axis_change((sp->chans[0])->axis,sp->chans[0]);
		  apply_x_axis_change((sp->chans[0])->axis,sp->chans[0],sp);
		}
	    }
	  height[0] = get_window_height(snd_widget(sp,W_snd_pane)) - get_window_height(snd_widget(sp,W_snd_ctrls)) - 16;
	  if (old_style == CHANNELS_SEPARATE)
	    {
	      ncp = sp->chans[0];
	      sound_lock_ctrls(sp,NULL);
	      channel_lock_pane(ncp,height);
	      mcgx = ncp->cgx;
	      for (i=1;i<sp->nchans;i++) 
		{
		  ncp = sp->chans[i];
		  chan_info_cleanup(ncp);
		  ncp->tcgx = mcgx;
		  regraph_all_mixmarks(ncp);
		}
	      channel_open_pane(sp->chans[0],NULL);
	      channel_unlock_pane(sp->chans[0],NULL);
	      sound_unlock_ctrls(sp,NULL);
	      XmToggleButtonSetState(snd_widget(sp,W_snd_combine),TRUE,FALSE);
	    }
	  else
	    {
	      if (new_style == CHANNELS_SEPARATE)
		{
		  /* height[0] = total space available */
		  height[0] /= sp->nchans;
		  sound_lock_ctrls(sp,NULL);
		  map_over_sound_chans(sp,channel_lock_pane,(void *)height);
		  map_over_sound_chans(sp,channel_open_pane,NULL);
		  map_over_sound_chans(sp,channel_unlock_pane,NULL);
		  sound_unlock_ctrls(sp,NULL);
		  for (i=0;i<sp->nchans;i++) regraph_all_mixmarks(sp->chans[i]);
		  pcp = sp->chans[0];
		  ap = pcp->axis;
		  for (i=1;i<sp->nchans;i++)
		    {
		      cp = sp->chans[i];
		      cp->tcgx = NULL;
		      cx = cp->cgx;
		      cw = cx->chan_widgets;
		      for (k=0;k<NUM_CHAN_WIDGETS;k++)
			{
			  j=w_chn_start_order[k];
			  if ((cw[j]) && (!XtIsManaged(cw[j]))) XtManageChild(cw[j]);
			}
		      XmToggleButtonSetState(cw[W_chn_f],cp->ffting,FALSE);
		      XmToggleButtonSetState(cw[W_chn_w],cp->waving,FALSE);
		      /* these can get out of sync if changes are made in the unseparated case */
		      add_chan_callbacks(cp);
		      set_axes(cp,ap->x0,ap->x1,ap->y0,ap->y1);
		    }
		  XmToggleButtonSetState(snd_widget(sp,W_snd_combine),FALSE,FALSE);
		}
	    }
	}
    }
}

void combine_sound(snd_info *sp) {change_channel_style(sp,CHANNELS_COMBINED);}
void superimpose_sound(snd_info *sp) {change_channel_style(sp,CHANNELS_SUPERIMPOSED);}
void separate_sound(snd_info *sp) {change_channel_style(sp,CHANNELS_SEPARATE);}

