#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_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) 
{
  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);
}

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.\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, nothing is\n\
displayed.\n\
");
}


void show_fft(chan_info *cp)
{
  cp->ffting = 1;
  XmToggleButtonSetState(chan_widget(cp,W_chn_f),TRUE,FALSE);
  calculate_fft(cp,NULL);
}

void hide_fft(chan_info *cp)
{
  cp->ffting = 0;
  XmToggleButtonSetState(chan_widget(cp,W_chn_f),FALSE,FALSE);
  update_graph(cp,NULL);
}

void show_wave(chan_info *cp)
{
  cp->waving = 1;
  XmToggleButtonSetState(chan_widget(cp,W_chn_w),TRUE,FALSE);
  update_graph(cp,NULL);
}

void hide_wave(chan_info *cp)
{
  cp->waving = 0;
  XmToggleButtonSetState(chan_widget(cp,W_chn_w),FALSE,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;
  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);
      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;
  cp->waving = cb->set;
  sp = cp->sound;
  if (sp->combining != CHANNELS_SEPARATE)
    propogate_wf_state(sp);
  else
    {
      update_graph(cp,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);
}


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));
      n=0;

      if (!main)
	{
	  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);
	}
      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++;
      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);
	  
	  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);
	}
      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++;
      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],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_MAX); 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],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],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++;
      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],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],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;
	}

      /* 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 (ss->fft_style == normal_fft)
	    {
	      if (ss->global_fft_size >= 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->ufuning) ap = ((ufun_info *)(cp->ui))->axis;
  else ap = cp->axis;
  h = ap->height-2;
  if (cp->waving) displays = 1; else displays = 0;
  if (cp->ffting) displays++;
  if (cp->ufuning) 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
 */

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),0);
  key_state = ev->state;
  redisplay = keyboard_command(cp,keysym,key_state);
  if (redisplay == CURSOR_CLAIM_SELECTION)
    {
      ClaimSelection(w,(XButtonEvent *)event,cp);
      redisplay = CURSOR_UPDATE_DISPLAY;
    }
  handle_cursor(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);
}
#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;
  if (cp->waving)
    {
      x0 = x-SLOPPY_MOUSE;
      x1 = x+SLOPPY_MOUSE;
      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 (ss->fft_style != 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 return(NOGRAPH);
	}
      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);
	  else return(NOGRAPH);
	}
    }
#if 0
  if (cp->ufuning)
    {
      up = cp->ui;
      ap = up->axis;
      if (((x0<=ap->x_axis_x1) && (x1>=ap->x_axis_x0)) && ((y<=ap->y_axis_y0) && (y>=ap->y_axis_y1)))
	return(UFUN);
    }
#endif
  return(NOGRAPH);
}

static axis_info *active_axis(chan_info *cp, int ax)
{
  fft_info *fp;
  ufun_info *up;
  switch (ax)
    {
    case NOGRAPH: return(NULL); break;
    case WAVE: return(cp->axis); break;
    case FFT: fp=cp->fft; return(fp->axis); break;
    case UFUN: up=cp->ui; return(up->axis); break;
    }
  return(NULL);
}

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 (ss->fft_style != sonogram)
	fft_axis_start = ev->x;
      else fft_axis_start = ev->y;
    }
}

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 (ss->fft_style != sonogram)
    return((float)(ap->x_axis_x1 - ap->x_axis_x0));
  else return((float)(ap->y_axis_y0 - ap->y_axis_y1));
}

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)
	    {
	      cp->active_axis = active_axis(cp,actax);
	      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);
		  PasteSelection(w,cp);
		}
	      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 (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)
		{
		  cp->active_axis = active_axis(cp,click_within_graph);
		  define_selection(cp);
		  dragged = 0;
		  /* now tell everyone else about our new selection */
		  ClaimSelection(w,ev,cp);
		}
	    }
	}
    }
}

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)
	    {
	      cp->active_axis = active_axis(cp,click_within_graph);
	      if (!dragged) 
		{
		  relinquish_selection_ownership();
		  create_selection(cp);
		}
	      dragged = 1;
	      move_selection(cp,ev->x);
	    }
	  else
	    {
	      if (click_within_graph == FFT)
		{
		  /* change ss->sonogram_cutoff and redisplay fft */
		  ss = cp->state;
		  old_cutoff = ss->sonogram_cutoff;
		  if (ss->fft_style != sonogram)
		    {
		      ss->sonogram_cutoff += ((float)(fft_axis_start - ev->x)/fft_axis_extent(cp));
		      fft_axis_start = ev->x;
		    }
		  else 
		    {
		      ss->sonogram_cutoff += ((float)(ev->y - fft_axis_start)/fft_axis_extent(cp));
		      fft_axis_start = ev->y;
		    }
		  if (ss->sonogram_cutoff > 1.0) ss->sonogram_cutoff = 1.0;
		  if (ss->sonogram_cutoff < 0.01) ss->sonogram_cutoff = 0.01;
		  if (old_cutoff != ss->sonogram_cutoff) 
		    {
		      reflect_spectro(ss);
		      set_sonogram_cutoff(ss,ss->sonogram_cutoff);
		    }
		}
	    }
	}
    }
}

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);
  if (ss->graph_style == graph_lines)
    {
      ax = copy_context(cp);
      XDrawLines(ax->dp,ax->wn,ax->gc,points,j,CoordModeOrigin);
      XDrawLines(ax->dp,ax->wn,ax->gc,points1,j,CoordModeOrigin);
    }
  else
    {
      draw_points(copy_context(cp),points,j,ss->dot_size);
      draw_points(copy_context(cp),points1,j,ss->dot_size);
    }
  if (need_reset) reset_color(cp);
}

void draw_grf_points(snd_state *ss,chan_info *cp, int j)
{
  int need_reset;
  need_reset = get_channel_color(ss,cp);
  if (ss->graph_style == graph_lines)
      draw_lines(copy_context(cp),points,j);
  else draw_points(copy_context(cp),points,j,ss->dot_size);
  if (need_reset) reset_color(cp);
}

void draw_both_grfs(axis_context *ax, int j)
{
  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;
  allocate_color_map(ss,ss->sonogram_color);
  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)
	    {
	      unsyncb(sp);
	      XtVaSetValues(snd_widget(sp,W_snd_combine),XmNselectColor,(ss->sgx)->text,NULL);
	    }
	  else
	    {
	      if (new_style == CHANNELS_SUPERIMPOSED)
		{
		  syncb(sp);
		  XtVaSetValues(snd_widget(sp,W_snd_combine),XmNselectColor,(ss->sgx)->mixpix,NULL);
		}
	    }
	  height[0] = get_window_height(snd_widget(sp,W_snd_pane)) - get_window_height(snd_widget(sp,W_snd_ctrls));
	  if (old_style == CHANNELS_SEPARATE)
	    {
	      ncp = sp->chans[0];
	      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);
	      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);}
