#include "snd.h"

#include <X11/IntrinsicP.h>

axis_context *free_axis_context(axis_context *ax)
{
  if (ax) free(ax);
  return(NULL);
}

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

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

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

void fill_rectangles (axis_context *ax, XRectangle *rects, int num)
{
  XFillRectangles(ax->dp,ax->wn,ax->gc,rects,num);
}

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

void fill_polygon(axis_context *ax,int points, ...)
{
  int i;
  XPoint *pts;
  va_list ap;
  pts = (XPoint *)calloc(points,sizeof(XPoint));
  va_start(ap,points);
  for (i=0;i<points;i++)
    {
      pts[i].x = va_arg(ap,int);
      pts[i].y = va_arg(ap,int);
    }
  va_end(ap);
  XFillPolygon(ax->dp,ax->wn,ax->gc,pts,points,Convex,CoordModeOrigin);
  free(pts);
}

void draw_polygon(axis_context *ax,int points, ...)
{
  int i;
  XPoint *pts;
  va_list ap;
  pts = (XPoint *)calloc(points,sizeof(XPoint));
  va_start(ap,points);
  for (i=0;i<points;i++)
    {
      pts[i].x = va_arg(ap,int);
      pts[i].y = va_arg(ap,int);
    }
  va_end(ap);
  XDrawLines(ax->dp,ax->wn,ax->gc,pts,points,CoordModeOrigin);
  free(pts);
}


void set_number_font(axis_context *ax)
{
  XSetFont(ax->dp,ax->gc,((XFontStruct *)(ax->numbers_font))->fid);
}
   
void set_button_font(axis_context *ax, snd_state *ss)
{
  state_context *sgx;
  sgx = ss->sgx;
  XSetFont(ax->dp,ax->gc,(sgx->button_fontstruct)->fid);
}

void set_label_font(axis_context *ax)
{
  XSetFont(ax->dp,ax->gc,((XFontStruct *)(ax->label_font))->fid);
}

int label_width(axis_context *ax, char *txt)
{
  if (txt)
    return(XTextWidth(ax->label_font,txt,strlen(txt)));
  else return(0);
}

int mark_name_width(snd_state *ss, char *txt)
{
  state_context *sgx;
  if (txt)
    {
      sgx = ss->sgx;
      return(XTextWidth(sgx->button_fontstruct,txt,strlen(txt)));
    }
  return(0);
}

int number_width(axis_context *ax, char *num)
{
  if (num)
    return(XTextWidth(ax->numbers_font,num,strlen(num)));
  return(0);
}

int number_height(axis_context *ax)
{
  XFontStruct *numbers_font;
  numbers_font = ax->numbers_font;
  return(numbers_font->ascent+numbers_font->descent);
}

int label_height(axis_context *ax)
{
  XFontStruct *label_font;
  label_font = ax->label_font;
  return(label_font->ascent+label_font->descent);
}

void draw_lines (axis_context *ax, XPoint *points,int num)
{
  XDrawLines(ax->dp,ax->wn,ax->gc,points,num,CoordModeOrigin);
}

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

void draw_point (Display *dp, Drawable wn, GC gc, XPoint point, int size)
{
  if (size == 1)
    XDrawPoint(dp,wn,gc,point.x,point.y);
  else
    XFillArc(dp,wn,gc,point.x,point.y,size,size,0,360*64);
}

static XPoint polypts[4];

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

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

void clear_window(axis_context *ax)
{
  XClearWindow(ax->dp,ax->wn);
}

int map_over_children (Widget w, int (*func)(Widget,void *), void *userptr)
{
  /* apply func to each child in entire tree beneath top widget */
  /* taken from Douglas Young, "Motif Debugging and Performance Tuning" Prentice-Hall 1995 */
  /* used mostly to get colors right in non-scheme environments with "convenience" widgets */
  /* (also to make mix consoles handle key press correctly despite non-traversable widgets) */
  int i,res;
  res = 0;
  if (w)
    {
      (*func)(w,userptr);
      if (XtIsComposite(w))
	{
	  CompositeWidget cw = (CompositeWidget)w;
	  for (i=0;i<cw->composite.num_children;i++)
	    {
	      res = map_over_children(cw->composite.children[i],func,userptr);
	      if (res) return(res);
	    }
	}
      if (XtIsWidget(w))
	{
	  for (i=0;i<w->core.num_popups;i++)
	    {
	      Widget child = w->core.popup_list[i];
	      res = map_over_children(child,func,userptr);
	      if (res) return(res);}}}
  return(res);
}

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

void raise_widget(Widget w)
{
  /* try to change stacking order (mix consoles in graph; form widgets in drawingarea; overlap covers desired console) */
  XtWidgetGeometry *request;
  request = (XtWidgetGeometry *)calloc(1,sizeof(XtWidgetGeometry));
  request->request_mode = CWStackMode; /* (1<<6) */
  request->stack_mode = 0; /* Above */
  XtMakeGeometryRequest(w,request,NULL);
}

int set_main_color_of_widget (Widget w,void *userptr)
{
  if (XtIsWidget(w))
    {
      if (XmIsScrollBar(w)) 
	XmChangeColor(w,(Pixel)(((snd_state *)userptr)->sgx)->scale);
      else XmChangeColor(w,(Pixel)(((snd_state *)userptr)->sgx)->main);
    }
  return(0);
}

void high_color(snd_state *ss, Widget w) {XmChangeColor(w,(ss->sgx)->high);}
void white_color(snd_state *ss, Widget w) {XmChangeColor(w,(ss->sgx)->white);}

void make_button_label(Widget button,char *str)
{
  XmString s1;
  s1=XmStringCreate(str,"button_font");
  XtVaSetValues(button,XmNlabelString,s1,NULL);
  XmStringFree(s1);
}

void make_bold_button_label(Widget button,char *str)
{
  XmString s1;
  s1=XmStringCreate(str,"bold_button_font");
  XtVaSetValues(button,XmNlabelString,s1,NULL);
  XmStringFree(s1);
}

void make_name_label(Widget label,char *str)
{
  XmString s1;
  s1=XmStringCreate(str,XmFONTLIST_DEFAULT_TAG);
  XtVaSetValues(label,XmNlabelString,s1,NULL);
  XmStringFree(s1);
}


int background_zoom_color(Arg *args, int n, snd_state *ss) {XtSetArg(args[n],XmNbackground,(ss->sgx)->zoom); return(n+1);}
int background_scale_color(Arg *args, int n, snd_state *ss) {XtSetArg(args[n],XmNbackground,(ss->sgx)->scale); return(n+1);}
int background_main_color(Arg *args, int n, snd_state *ss) {XtSetArg(args[n],XmNbackground,(ss->sgx)->main); return(n+1);}
int background_high_color(Arg *args, int n, snd_state *ss) {XtSetArg(args[n],XmNbackground,(ss->sgx)->high); return(n+1);}
int background_white_color(Arg *args, int n, snd_state *ss) {XtSetArg(args[n],XmNbackground,(ss->sgx)->white); return(n+1);}
int background_mixer_color(Arg *args, int n, snd_state *ss) {XtSetArg(args[n],XmNbackground,(ss->sgx)->mixpix); return(n+1);}
int background_text_color(Arg *args, int n, snd_state *ss) {XtSetArg(args[n],XmNbackground,(ss->sgx)->text); return(n+1);}
int background_txt_color(Arg *args, int n, snd_state *ss) {XtSetArg(args[n],XmNbackground,(ss->sgx)->txt); return(n+1);}

static XmString error_dialog_msg = NULL;

void snd_printf(void *ss1,char *msg)
{
  /* our basic error handler -- if possible avoid this thing */
  snd_info *sp;
  snd_state *ss = (snd_state *)ss1;
  Arg args[20];
  int n;
  XmString titlestr;
  static Widget error_dialog = NULL;
  sp = selected_sound(ss);
  if (sp)
    report_in_minibuffer(sp,msg);
  else
    {
      if (!error_dialog)
	{
	  titlestr = XmStringCreateLocalized(snd_string_Error);
	  n=0;
	  if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
	  XtSetArg(args[n],XmNresizePolicy,XmRESIZE_GROW); n++;
#if RESIZE_DIALOG
	  XtSetArg(args[n],XmNnoResize,FALSE); n++;
#endif
	  XtSetArg(args[n],XmNdialogTitle,titlestr); n++;
	  error_dialog = XmCreateErrorDialog(main_PANE(ss),snd_string_error,args,n);
	  XtUnmanageChild(XmMessageBoxGetChild(error_dialog,XmDIALOG_SYMBOL_LABEL));
	  XtUnmanageChild(XmMessageBoxGetChild(error_dialog,XmDIALOG_CANCEL_BUTTON));
	  XtUnmanageChild(XmMessageBoxGetChild(error_dialog,XmDIALOG_HELP_BUTTON));
#if MANAGE_DIALOG
	  XtManageChild(error_dialog);
#endif
	  if (!(ss->using_schemes)) map_over_children(error_dialog,set_main_color_of_widget,(void *)ss);
	  XmStringFree(titlestr);
	}
      /* XtVaSetValues(error_dialog,XtVaTypedArg,XmNmessageString,XmRString,msg,strlen(msg)+1,NULL); */
      /* XtVaTypedArg above taken from Young p34, but does not work in Metrolink Motif */
      if (error_dialog_msg) XmStringFree(error_dialog_msg);
      error_dialog_msg = XmStringCreateLtoR(msg,XmFONTLIST_DEFAULT_TAG);
      XtVaSetValues(error_dialog,XmNmessageString,error_dialog_msg,NULL);
      if (!(XtIsManaged(error_dialog))) XtManageChild(error_dialog);
    }
}

static int yes_or_no = 0;

static void YesCallback(Widget w,XtPointer clientData,XtPointer callData) {yes_or_no = 1;}
static void NoCallback(Widget w,XtPointer clientData,XtPointer callData) {yes_or_no = 0;}

int snd_yes_or_no_p(snd_state *ss,char *question)
{
  /* bad form, but in this case we want a modal dialog that stops Snd cold and gets a response -- used only for serious trouble */
  static Widget yes_or_no_dialog = NULL;
  Arg args[20];
  int n;
  XmString titlestr;
  yes_or_no = 0;
  if (!yes_or_no_dialog)
    {
      titlestr = XmStringCreateLocalized(snd_string_Big_Trouble);
      n=0;
      if (!(ss->using_schemes)) n = background_main_color(args,n,ss);
      XtSetArg(args[n],XmNresizePolicy,XmRESIZE_GROW); n++;
#if RESIZE_DIALOG
      XtSetArg(args[n],XmNnoResize,FALSE); n++;
#endif
      XtSetArg(args[n],XmNdialogTitle,titlestr); n++;
      yes_or_no_dialog = XmCreateQuestionDialog(main_PANE(ss),"yow!",args,n);
      if (error_dialog_msg) XmStringFree(error_dialog_msg);
      error_dialog_msg = XmStringCreateLtoR(question,XmFONTLIST_DEFAULT_TAG);
#if MANAGE_DIALOG
      XtManageChild(yes_or_no_dialog);
#endif
      XtUnmanageChild(XmMessageBoxGetChild(yes_or_no_dialog,XmDIALOG_SYMBOL_LABEL)); /* idiotic */
      XtUnmanageChild(XmMessageBoxGetChild(yes_or_no_dialog,XmDIALOG_HELP_BUTTON));
      if (!(ss->using_schemes)) map_over_children(yes_or_no_dialog,set_main_color_of_widget,(void *)ss);
      XtVaSetValues(yes_or_no_dialog,XmNdialogStyle,XmDIALOG_FULL_APPLICATION_MODAL,XmNmessageString,error_dialog_msg,NULL);
      XtAddCallback(yes_or_no_dialog,XmNokCallback,YesCallback,NULL);
      XtAddCallback(yes_or_no_dialog,XmNcancelCallback,NoCallback,NULL);

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

      XmStringFree(titlestr);
    }
  if (!(XtIsManaged(yes_or_no_dialog))) XtManageChild(yes_or_no_dialog);
  while (XtIsManaged(yes_or_no_dialog))
    {
      XEvent event;
      XtAppNextEvent(XtWidgetToApplicationContext(yes_or_no_dialog),&event);
      XtDispatchEvent(&event);
    }
  return(yes_or_no);
}
  

void set_title(snd_state *ss, char *title)
{
  XtVaSetValues(main_SHELL(ss),XmNtitle,title,NULL);
}

void make_axes(chan_info *cp, axis_info *ap, int x_style)
{
  Widget w;
  snd_info *sp;
  axis_context *ax;
  chan_info *cp0;
  if (!(ap->ax))
    {
      ax = (axis_context *)calloc(1,sizeof(axis_context));
      ap->ax = ax;
      ax->label_font = axis_label_FONTSTRUCT(ap);
      ax->numbers_font = axis_numbers_FONTSTRUCT(ap);
    }
  else ax = ap->ax;
  sp = cp->sound;
  if (cp->tcgx) 
    w = chan_widget(sp->chans[0],W_chn_graph);
  else w = chan_widget(cp,W_chn_graph);
  ax->dp = XtDisplay(w);
  ax->gc = copy_GC(cp);
  ax->wn = XtWindow(w);
  if (cp->clear) 
    {
      switch (sp->combining)
	{
	case CHANNELS_SUPERIMPOSED:     /* clear our portion and mark so others don't clear */
	  cp0 = sp->chans[0];
	  if (cp0->clear)
	    {
	      clear_window(ap->ax);     /* clear entire channel window (once) */
	      cp0->clear = 0;           /* channel graphs can occur in any order (background procs) */
	    }
	  break;
	case CHANNELS_COMBINED:         /* clear only our (full width) portion of the window */
	  erase_rectangle(cp,ap->ax,0,ap->y_offset,ap->window_width,ap->height); 
	  break; 
	default: 
	  clear_window(ap->ax);         /* clear entire channel window */
	  break;
	}
      cp->clear = 0;
    }
  make_axes_1(cp,ap,x_style,snd_SRATE(cp));
}

static int complain_about_focus_policy = 1;

void goto_window(Widget text)
{
  int err;
  if ((XmIsTraversable(text)) && (XmGetVisibility(text) != XmVISIBILITY_FULLY_OBSCURED))
    {
      if (!(XmProcessTraversal(text,XmTRAVERSE_CURRENT)))
	{
	  if (complain_about_focus_policy)
	    {
	      XtVaGetValues(text,XmNkeyboardFocusPolicy,&err,NULL);
	      if (err == XmEXPLICIT)
		fprintf(stderr,"traverse to %s failed!",XtName(text));
	      else 
		{
		  fprintf(stderr,"keyboard focus policy is not explicit!");
		  complain_about_focus_policy = 0;
		}
	    }
	}
    }
}

void goto_graph(chan_info *cp)
{
  snd_info *sp;
  if (cp)
    {
      sp = cp->sound;
      if ((cp->chan == 0) || (sp->combining == CHANNELS_SEPARATE))
	goto_window(chan_widget(cp,W_chn_graph));
      else goto_window(chan_widget(sp->chans[0],W_chn_graph));
    }
}

void goto_minibuffer(snd_info *sp)
{
  if (sp) goto_window(snd_widget(sp,W_snd_info));
}

void text_set_string(Widget txt, char *str) {XmTextSetString(txt,str);}
char *text_get_string(Widget txt) {return(XmTextGetString(txt));} /* copy ? */

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

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

void set_color(axis_context *ax, Pixel color)
{
  XSetForeground(ax->dp,ax->gc,color);
}

void reset_color(chan_info *cp)
{
  snd_state *ss;
  state_context *sx;
  ss = cp->state;
  sx = ss->sgx;
  set_color(copy_context(cp),sx->black);
}

int get_raw_value(Widget w) {int val; XtVaGetValues(w,XmNvalue,&val,NULL); return(val);}
int get_raw_size(Widget w) {int val; XtVaGetValues(w,XmNsliderSize,&val,NULL); return(val);}
int set_raw_value(Widget w, int val) {XtVaSetValues(w,XmNvalue,val,NULL); return(val);}

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

#include <Xm/SashP.h>
int color_sashes(Widget w, void *ptr)
{
  if ((XtIsWidget(w)) && (XtIsManaged(w)) && (XtIsSubclass(w,xmSashWidgetClass)))
    XmChangeColor(w,(Pixel)(((snd_state *)ptr)->sgx)->mixpix);
  return(0);
}

void check_for_event(snd_state *ss)
{
  /* this is needed to force label updates and provide interrupts for long computations */
  XEvent event;
  XtAppContext app;
  ss->checking_explicitly = 1;
  app = main_APP(ss);
  while (XtAppPending(app))
    {
      XtAppNextEvent(app,&event);
      XtDispatchEvent(&event);
    }
  ss->checking_explicitly = 0;
}

static Dimension app_x,app_y;

void save_window_size(snd_state *ss)
{
  XtVaGetValues(main_SHELL(ss),XmNwidth,&app_x,XmNheight,&app_y,NULL);
}

void restore_window_size(snd_state *ss)
{
  XtVaSetValues(main_SHELL(ss),XmNwidth,app_x,XmNheight,app_y,NULL);
}


/* ---------------- text widget specializations ---------------- */

void textfield_focus_Callback(Widget w,XtPointer clientData,XtPointer callData)
{
  snd_state *ss = (snd_state *)clientData;
  if (!(ss->using_schemes)) XtVaSetValues(w,XmNbackground,(ss->sgx)->white,NULL);
}

void textfield_unfocus_Callback(Widget w,XtPointer clientData,XtPointer callData)
{
  snd_state *ss = (snd_state *)clientData;
  if (!(ss->using_schemes)) XtVaSetValues(w,XmNbackground,(ss->sgx)->main,NULL);
}


/* -------- specialized action procs -------- */

static snd_state *action_ss; /* need some way to get Snd global state into action proc */
static int actions_loaded = 0;
#define CONTROL_KEY 4

static void No_op (Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  /* return does not cause widget activation in many textfield cases -- it is a true no-op */
}

static void Activate_keyboard (Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  /* make the current channel active preloading kbd cmd with str[0]+ctrl bit */
  chan_info *cp;
  cp = current_channel(action_ss);
  if (cp) 
    {
      goto_graph(cp);
      keyboard_command(cp,(str[0][0] == 'u') ? snd_K_u : snd_K_x,CONTROL_KEY);
    }
}

static void Activate_channel (Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  /* make the current channel active */
  chan_info *cp;
  cp = current_channel(action_ss);
  if (cp) goto_graph(cp);
}

static char *listener_selection = NULL;

static void Kill_line(Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  /* C-k with storage of killed text */
  XmTextPosition curpos,loc;
  Boolean found;
  curpos = XmTextGetCursorPosition(w);
  found = XmTextFindString(w,curpos,"\n",XmTEXT_FORWARD,&loc);
  if (!found) loc = XmTextGetLastPosition(w);
  if (loc > curpos)
    {
      if (listener_selection) free(listener_selection);
      XmTextSetSelection(w,curpos,loc,CurrentTime);
      listener_selection = copy_string(XmTextGetSelection(w));
      XmTextCut(w,CurrentTime);
    }
}

static void Yank(Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  /* copy current selection at current cursor position */
  XmTextPosition curpos;
  if (listener_selection) 
    {
      curpos = XmTextGetCursorPosition(w);
      XmTextInsert(w,curpos,copy_string(listener_selection));
      curpos+=strlen(listener_selection);
      XmTextShowPosition(w,curpos);
      XmTextSetCursorPosition(w,curpos);
    }
}

static int cmd_eot,last_prompt;

static void Begin_of_line(Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  /* don't back up before ">" */
  XmTextPosition curpos,loc;
  Boolean found;
  curpos = XmTextGetCursorPosition(w) - 1;
  found = XmTextFindString(w,curpos,"\n",XmTEXT_BACKWARD,&loc);
  XmTextSetCursorPosition(w,((found) && (loc>last_prompt)) ? (loc+1) : (last_prompt+1));
}

static void Delete_region(Widget w, XEvent *ev, char **str, Cardinal *num) 
{
  XmTextCut(w,CurrentTime);
}

static XmTextPosition down_pos, last_pos;

static void B1_press(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  XmTextPosition pos;
  XButtonEvent *ev = (XButtonEvent *)event;
  pos = XmTextXYToPos(w,ev->x,ev->y);
  XmTextSetCursorPosition(w,pos);
  down_pos = pos;
  last_pos = pos;
}

static void B1_move(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  XmTextPosition pos;
  XButtonEvent *ev = (XButtonEvent *)event;
  pos = XmTextXYToPos(w,ev->x,ev->y);
  if (last_pos > pos) /* must have backed up the cursor */
    XmTextSetHighlight(w,pos,last_pos,XmHIGHLIGHT_NORMAL);
  if (down_pos != pos)
    XmTextSetHighlight(w,down_pos,pos,XmHIGHLIGHT_SELECTED);
  last_pos = pos;
}

static void B1_release(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  XmTextPosition pos;
  XButtonEvent *ev = (XButtonEvent *)event;
  pos = XmTextXYToPos(w,ev->x,ev->y);
  XmTextSetCursorPosition(w,pos);
  if (down_pos != pos)
    {
      XmTextSetHighlight(w,down_pos,pos,XmHIGHLIGHT_SELECTED);
      if (listener_selection) free(listener_selection);
      XmTextSetSelection(w,down_pos,pos,CurrentTime);
      listener_selection = copy_string(XmTextGetSelection(w));
    }
}

static void Text_transpose(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  XmTextPosition curpos;
  char buf[3]; /* needs room for null */
  char tmp;
  curpos = XmTextGetCursorPosition(w);
  if (curpos > 1)
    {
      XmTextGetSubstring(w,(XmTextPosition)(curpos-1),2,3,buf);
      tmp = buf[0];
      buf[0]=buf[1];
      buf[1]=tmp;
      XmTextReplace(w,curpos-1,curpos+1,buf);
      XmTextSetCursorPosition(w,curpos+1);
    }
}

static void Word_upper(Widget w, XEvent *event, char **str, Cardinal *num) 
{
  int i,j,length,wstart,wend,up,cap;
  XmTextPosition curpos,endpos;
  char *buf;
  up = (str[0][0] == 'u');
  cap = (str[0][0] == 'c');
  curpos = XmTextGetCursorPosition(w);
  endpos = XmTextGetLastPosition(w);
  if (curpos < endpos)
    {
      length = endpos - curpos;
      buf = (char *)calloc(length+1,sizeof(char));
      XmTextGetSubstring(w,curpos,length,length+1,buf);
      wstart = 0;
      wend = length;
      for (i=0;i<length;i++)
	if (!isspace(buf[i]))
	  {
	    wstart=i;
	    break;
	  }
      for (i=wstart+1;i<length;i++)
	if (isspace(buf[i]))
	  {
	    wend = i;
	    break;
	  }
      if (cap)
	{
	  buf[0] = toupper(buf[wstart]);
	  buf[1]='\0';
	  XmTextReplace(w,curpos+wstart,curpos+wstart+1,buf);
	}
      else
	{
	  for (i=wstart,j=0;i<wend;i++,j++)
	    if (up) 
	      buf[j] = toupper(buf[i]);
	    else buf[j] = tolower(buf[i]);
	  buf[j]='\0';
	  XmTextReplace(w,curpos+wstart,curpos+wend,buf);
	}
      XmTextSetCursorPosition(w,curpos+wend);
    }
}
/* need c-u for local reps and M-d should load listener-selection
   c sp + motion?
   tab (for indent)
   m t
*/

#define NUM_ACTS 12
static XtActionsRec acts[] = {
  {"no-op",No_op},
  {"activate-keyboard",Activate_keyboard},
  {"activate-channel",Activate_channel},
  {"yank",Yank},
  {"delete-region",Delete_region},
  {"kill-line",Kill_line},
  {"begin-of-line",Begin_of_line},
  {"b1-press",B1_press},
  {"b1-move",B1_move},
  {"b1-release",B1_release},
  {"text-transpose",Text_transpose},
  {"word-upper",Word_upper},
};

/* translation tables for emacs compatibility and better inter-widget communication */

/* for textfield (single-line) widgets */
static char TextTrans2[] =
       "Ctrl <Key>a:            beginning-of-line()\n\
        Ctrl <Key>b:            backward-character()\n\
        Mod1 <Key>b:            backward-word()\n\
        Mod1 <Key>c:            word-upper(c)\n\
        Ctrl <Key>d:            delete-next-character()\n\
        Mod1 <Key>d:            delete-next-word()\n\
        Ctrl <Key>e:            end-of-line()\n\
        Ctrl <Key>f:            forward-character()\n\
        Mod1 <Key>f:            forward-word()\n\
        Ctrl <Key>g:            activate()\n\
        Ctrl <Key>k:            delete-to-end-of-line()\n\
        Mod1 <Key>l:            word-upper(l)\n\
        Ctrl <Key>t:            text-transpose()\n\
        Mod1 <Key>u:            word-upper(u)\n\
        Mod1 <Key><:            beginning-of-line()\n\
        Mod1 <Key>>:            end-of-line()\n\
        <Key>Delete:            delete-previous-character()\n\
        Mod1 <Key>Delete:       delete-to-start-of-line()\n\
        <Key>Return:            activate()\n";
static XtTranslations transTable2 = NULL;

/* same but try to avoid causing the currently active pushbutton widget to appear to be activated by <cr> in the text widget */
static char TextTrans6[] =
       "Ctrl <Key>a:            beginning-of-line()\n\
        Ctrl <Key>b:            backward-character()\n\
        Mod1 <Key>b:            backward-word()\n\
        Mod1 <Key>c:            word-upper(c)\n\
        Ctrl <Key>d:            delete-next-character()\n\
        Mod1 <Key>d:            delete-next-word()\n\
        Ctrl <Key>e:            end-of-line()\n\
        Ctrl <Key>f:            forward-character()\n\
        Mod1 <Key>f:            forward-word()\n\
        Ctrl <Key>g:            no-op()\n\
        Ctrl <Key>k:            delete-to-end-of-line()\n\
        Mod1 <Key>l:            word-upper(l)\n\
        Ctrl <Key>t:            text-transpose()\n\
        Mod1 <Key>u:            word-upper(u)\n\
        Mod1 <Key><:            beginning-of-line()\n\
        Mod1 <Key>>:            end-of-line()\n\
        <Key>Delete:            delete-previous-character()\n\
        Mod1 <Key>Delete:       delete-to-start-of-line()\n\
        <Key>Return:            no-op()\n";
static XtTranslations transTable6 = NULL;

/* for text (multi-line) widgets */
static char TextTrans3[] =
       "Ctrl <Key>a:            beginning-of-line()\n\
        Ctrl <Key>b:            backward-character()\n\
        Mod1 <Key>b:            backward-word()\n\
        Mod1 <Key>c:            word-upper(c)\n\
        Ctrl <Key>d:            delete-next-character()\n\
        Mod1 <Key>d:            delete-next-word()\n\
        Ctrl <Key>e:            end-of-line()\n\
        Ctrl <Key>f:            forward-character()\n\
        Mod1 <Key>f:            forward-word()\n\
        Ctrl <Key>g:            activate()\n\
        Ctrl <Key>j:            newline-and-indent()\n\
        Ctrl <Key>k:            kill-line()\n\
        Mod1 <Key>l:            word-upper(l)\n\
        Ctrl <Key>n:            next-line()\n\
        Ctrl <Key>o:            newline-and-backup()\n\
        Ctrl <Key>p:            previous-line()\n\
        Ctrl <Key>t:            text-transpose()\n\
        Mod1 <Key>u:            word-upper(u)\n\
        Ctrl <Key>v:            next-page()\n\
        Mod1 <Key>v:            previous-page()\n\
        Ctrl <Key>w:            delete-region()\n\
        Ctrl <Key>y:            yank()\n\
        Ctrl <Key>z:            activate()\n\
        Mod1 <Key>[:            backward-paragraph()\n\
        Mod1 <Key>]:            forward-paragraph()\n\
        Mod1 <Key><:            beginning-of-file()\n\
        Mod1 <Key>>:            end-of-file()\n\
        <Key>Delete:            delete-previous-character()\n\
        Mod1 <Key>Delete:       delete-to-start-of-line()\n\
        Ctrl <Key>osfLeft:      page-left()\n\
        Ctrl <Key>osfRight:     page-right()\n\
        Ctrl <Key>osfDown:      next-page()\n\
        Ctrl <Key>osfUp:        previous-page()\n\
        Ctrl <Key>space:        set-anchor()\n\
        <Btn1Down>:             b1-press()\n\
        <Btn1Up>:               b1-release()\n\
        <Btn1Motion>:           b1-move()\n\
        <Key>Return:            newline()\n";
static XtTranslations transTable3 = NULL;

/* for lisp listener */
static char TextTrans4[] =
       "Ctrl <Key>a:            begin-of-line()\n\
        Ctrl <Key>b:            backward-character()\n\
        Mod1 <Key>b:            backward-word()\n\
        Mod1 <Key>c:            word-upper(c)\n\
        Ctrl <Key>d:            delete-next-character()\n\
        Mod1 <Key>d:            delete-next-word()\n\
        Ctrl <Key>e:            end-of-line()\n\
        Ctrl <Key>f:            forward-character()\n\
        Mod1 <Key>f:            forward-word()\n\
        Ctrl <Key>g:            activate-channel()\n\
        Ctrl <Key>j:            newline-and-indent()\n\
        Ctrl <Key>k:            kill-line()\n\
        Ctrl <Key>l:            redraw-display()\n\
        Mod1 <Key>l:            word-upper(l)\n\
        Ctrl <Key>n:            next-line()\n\
        Ctrl <Key>o:            newline-and-backup()\n\
        Ctrl <Key>p:            previous-line()\n\
        Ctrl <Key>t:            text-transpose()\n\
        Ctrl <Key>u:            activate-keyboard(u)\n\
        Mod1 <Key>u:            word-upper(u)\n\
        Ctrl <Key>v:            next-page()\n\
        Mod1 <Key>v:            previous-page()\n\
        Ctrl <Key>w:            delete-region()\n\
        Ctrl <Key>x:            activate-keyboard(x)\n\
        Ctrl <Key>y:            yank()\n\
        Ctrl <Key>z:            activate()\n\
        Mod1 <Key>[:            backward-paragraph()\n\
        Mod1 <Key>]:            forward-paragraph()\n\
        Mod1 <Key><:            beginning-of-file()\n\
        Mod1 <Key>>:            end-of-file()\n\
        <Key>Delete:            delete-previous-character()\n\
        Mod1 <Key>Delete:       delete-to-start-of-line()\n\
        Ctrl <Key>osfLeft:      page-left()\n\
        Ctrl <Key>osfRight:     page-right()\n\
        Ctrl <Key>osfDown:      next-page()\n\
        Ctrl <Key>osfUp:        previous-page()\n\
        Ctrl <Key>space:        set-anchor()\n\
        <Btn1Down>:             b1-press()\n\
        <Btn1Up>:               b1-release()\n\
        <Btn1Motion>:           b1-move()\n\
        <Key>Return:            activate()\n";
static XtTranslations transTable4 = NULL;


/* -------- text related widgets -------- */

static void remember_event(Widget w,XtPointer clientData,XtPointer callData) 
{
  snd_state *ss = (snd_state *)clientData;
  XmAnyCallbackStruct *cb = (XmAnyCallbackStruct *)callData;
  (ss->sgx)->text_activate_event = cb->event;
}

Widget sndCreateTextFieldWidget(snd_state *ss, char *name, Widget parent, Arg *args, int n, int activatable)
{
  /* white background when active, emacs translations, text_activate_event in ss->sgx for subsequent activation check */
  Widget df;
  if (!actions_loaded) {XtAppAddActions(main_APP(ss),acts,NUM_ACTS); actions_loaded = 1; action_ss = ss;}
  XtSetArg(args[n],XmNactivateCallback,make_callback_list(remember_event,(XtPointer)ss)); n++;
  df = XtCreateManagedWidget(name,xmTextFieldWidgetClass,parent,args,n);
  XtAddCallback(df,XmNfocusCallback,textfield_focus_Callback,ss);
  XtAddCallback(df,XmNlosingFocusCallback,textfield_unfocus_Callback,ss);
  if (activatable == ACTIVATABLE)
    {
      if (!transTable2) transTable2 = XtParseTranslationTable(TextTrans2);
      XtOverrideTranslations(df,transTable2);
    }
  else
    {
      if (!transTable6) transTable6 = XtParseTranslationTable(TextTrans6);
      XtOverrideTranslations(df,transTable6);
    }
  return(df);
}

Widget sndCreateTextWidget(snd_state *ss, char *name, Widget parent, Arg *args, int n)
{
  /* white background when active, emacs translations, text_activate_event in ss->sgx for subsequent activation check */
  Widget df;
  if (!actions_loaded) {XtAppAddActions(main_APP(ss),acts,NUM_ACTS); actions_loaded = 1; action_ss = ss;}
  XtSetArg(args[n],XmNactivateCallback,make_callback_list(remember_event,(XtPointer)ss)); n++;
  XtSetArg(args[n],XmNeditMode,XmMULTI_LINE_EDIT); n++;
  df = XmCreateScrolledText(parent,name,args,n);
  XtManageChild(df);
  XtAddCallback(df,XmNfocusCallback,textfield_focus_Callback,ss);
  XtAddCallback(df,XmNlosingFocusCallback,textfield_unfocus_Callback,ss);
  if (!transTable3) transTable3 = XtParseTranslationTable(TextTrans3);
  XtOverrideTranslations(df,transTable3);
  return(df);
}

/* ---------------- command widget replacement ---------------- */
/*
 * the XmCommand widget itself is almost usable...
 */

static Widget lisp_listener = NULL;
static Widget lisp_window = NULL;

void snd_append_char(snd_state *ss, char *msg)
{
  if (lisp_listener)
    {
      XmTextInsert(lisp_listener,XmTextGetLastPosition(lisp_listener),msg);
    }
}
  
void snd_append_command(snd_state *ss, char *msg)
{
  if (lisp_listener)
    {
      if (ss->result_printout) 
	{
	  XmTextInsert(lisp_listener,cmd_eot,"\n");
	  cmd_eot = XmTextGetLastPosition(lisp_listener);
	}
      if (msg)
	{
	  XmTextInsert(lisp_listener,XmTextGetLastPosition(lisp_listener),msg);
	}
      cmd_eot = XmTextGetLastPosition(lisp_listener);
      if (ss->result_printout) XmTextInsert(lisp_listener,cmd_eot,"\n>");
      ss->result_printout = 0;
      cmd_eot = XmTextGetLastPosition(lisp_listener);
      last_prompt = cmd_eot-1;
      XmTextShowPosition(lisp_listener,cmd_eot);
    }
}

static void Command_Return_Callback(Widget w,XtPointer clientData,XtPointer callData)
{
  XmTextPosition old_eot,new_eot;
  char *str;
  int i,len,parens = 0;
  snd_state *ss = (snd_state *)clientData;
  XmAnyCallbackStruct *cb = (XmAnyCallbackStruct *)callData;
  old_eot = cmd_eot;
  new_eot = XmTextGetLastPosition(w);
  XmTextSetSelection(w,old_eot,new_eot,cb->event->xkey.time);
  str = XmTextGetSelection(w);
  XmTextClearSelection(w,cb->event->xkey.time);
  if (str)
    {
      parens = 0;
      len = strlen(str);
      for (i=0;i<len;i++) if (str[i]=='(') parens++; else if (str[i]==')') parens--;
      if (parens == 0)
	{
	  for (i=0;i<len;i++) if (str[i] == '\n') str[i] = ' ';
	  cmd_eot = new_eot+1;
	  clm_just_doit(ss,str);
	}
      free(str);
      if (parens != 0)
	{
	  XmTextInsert(w,new_eot,"\n"); 
	  XmTextSetInsertionPosition(w,new_eot+2); 
	  return;
	}
    }
  else XmTextInsert(w,new_eot,"\n>");
  cmd_eot = XmTextGetLastPosition(w);
  last_prompt = cmd_eot-1;
  XmTextShowPosition(w,cmd_eot);
  XmTextSetInsertionPosition(w,cmd_eot+1);
}

static void Command_Modify_Callback(Widget w,XtPointer clientData,XtPointer callData)
{
  /* don't let user type back over last prompt */
  XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)callData;
  if (cbs->startPos <= last_prompt) cbs->doit = FALSE;
}

static void Command_Help_Callback(Widget w,XtPointer clientData,XtPointer callData)
{
#if HAVE_XmHTML
  snd_help((snd_state *)clientData,"Lisp Listener","#customization");
#else
  ssnd_help((snd_state *)clientData,
	    "Lisp Listener",
"This is the lisp listener pane; it is one way to\n\
access the Guile Scheme interpreter.\n\
\n",
	   get_init_file_help(),
	   NULL);
#endif
}

static Widget pane_frm = NULL;

static void sndCreateCommandWidget(snd_state *ss)
{
  Arg args[32];
  Widget wv,wh;
  int n;
  if (!lisp_listener)
    {
      if (!actions_loaded) {XtAppAddActions(main_APP(ss),acts,NUM_ACTS); actions_loaded = 1; action_ss = ss;}

      n=0;
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNheight,100); n++;
      pane_frm = XtCreateManagedWidget("frm",xmFormWidgetClass,sound_PANE(ss),args,n);
      /* this widget is not redundant at least in Metroworks Motif */

      n=0;
      if (!(ss->using_schemes)) n = background_txt_color(args,n,ss);
      if ((ss->sgx)->listener_fontlist) {XtSetArg(args[n],XmNfontList,(ss->sgx)->listener_fontlist); n++;}
      XtSetArg(args[n],XmNtopAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNbottomAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNleftAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNrightAttachment,XmATTACH_FORM); n++;
      XtSetArg(args[n],XmNactivateCallback,make_callback_list(remember_event,(XtPointer)ss)); n++;
      XtSetArg(args[n],XmNeditMode,XmMULTI_LINE_EDIT); n++;
      XtSetArg(args[n],XmNskipAdjust,TRUE); n++;
      XtSetArg(args[n],XmNvalue,">"); n++;
      XtSetArg(args[n],XmNpendingDelete,FALSE); n++; /* don't cut selection upon paste */
      XtSetArg(args[n],XmNpositionIndex,XmLAST_POSITION); n++;
      XtSetArg(args[n],XmNpaneMinimum,100); n++;

      lisp_listener = XmCreateScrolledText(/* sound_PANE(ss) */ pane_frm,"lisp-listener",args,n);
#if 0
      {
	XtActionList lst;
	XtActionsRec *ls;
	Cardinal num,i;
	XtGetActionList(xmTextWidgetClass,&lst,&num);
	ls = lst;
	for (i=0;i<num;i++) fprintf(stderr,"%s\n",ls[i].string);
      }
#endif

      XtManageChild(lisp_listener);
      XmTextSetCursorPosition(lisp_listener,1);
      if (!transTable4) transTable4 = XtParseTranslationTable(TextTrans4);
      XtOverrideTranslations(lisp_listener,transTable4);
      cmd_eot = 1;
      last_prompt = 0;
      XtAddCallback(lisp_listener,XmNactivateCallback,Command_Return_Callback,ss);
      XtAddCallback(lisp_listener,XmNmodifyVerifyCallback,Command_Modify_Callback,ss);
      XtAddCallback(lisp_listener,XmNhelpCallback,Command_Help_Callback,ss);
      
      lisp_window = XtParent(lisp_listener);
      
      if (!(ss->using_schemes))
	{
	  XmChangeColor(lisp_window,(ss->sgx)->main);
	  XtVaGetValues(lisp_window,XmNverticalScrollBar,&wv,XmNhorizontalScrollBar,&wh,NULL);
	  XmChangeColor(wv,(ss->sgx)->main);
	  XmChangeColor(wh,(ss->sgx)->main);
	  map_over_children(sound_PANE(ss),color_sashes,(void *)ss);
	}
      XtVaSetValues(lisp_window,XmNpaneMinimum,1,NULL);
    }
}

void goto_listener(void) 
{
  goto_window(lisp_listener);
  XmTextSetCursorPosition(lisp_listener,XmTextGetLastPosition(lisp_listener)+1);
  XmTextSetInsertionPosition(lisp_listener,XmTextGetLastPosition(lisp_listener)+1);
}

void handle_listener(snd_state *ss)
{
  if (!lisp_listener)
    {
      /* fire up listener at bottom of overall snd window */
      sndCreateCommandWidget(ss);
      ss->listening = 1;
      set_view_listener_label(snd_string_Hide_lisp);
      goto_window(lisp_listener);
    }
  else
    {
      if (ss->listening == 1)
	{
	  /* close listener window but it remains active */
	  ss->listening = 2;
	  set_view_listener_label(snd_string_Show_lisp);
	  XtUnmanageChild(pane_frm);
	  XtVaSetValues(pane_frm,XmNpaneMaximum,1,XmNpaneMinimum,1,NULL);
	  XtManageChild(pane_frm);
	  XtVaSetValues(pane_frm,XmNpaneMaximum,1000,XmNpaneMinimum,1,NULL);
	}
      else
	{
	  /* reopen listener pane */
	  ss->listening = 1;
	  set_view_listener_label(snd_string_Hide_lisp);
	  XtUnmanageChild(pane_frm);
	  XtVaSetValues(pane_frm,XmNpaneMinimum,100,NULL);
	  XtManageChild(pane_frm);
	  XtVaSetValues(pane_frm,XmNpaneMinimum,1,NULL);
	}
    }
}

int lisp_listener_height(void)
{
  Dimension hgt;
  if (lisp_listener)
    {
      XtVaGetValues(lisp_listener,XmNheight,&hgt,NULL);
      return(hgt);
    }
  return(0);
}

void set_listener_width(int width)
{
  if (lisp_listener)
    {
      XtUnmanageChild(pane_frm);
      XtVaSetValues(pane_frm,XmNwidth,width,NULL);
      XtManageChild(pane_frm);
    }
}
