#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;
  sgx = ss->sgx;
  return(XTextWidth(sgx->button_fontstruct,txt,strlen(txt)));
}

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

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);
}

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);
      /* can't tell what XtGrabNone does, but this seems to work */
    }
}

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);}

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;
	  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;
      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);
      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));
}

void goto_window(Widget text)
{
  if ((XmIsTraversable(text)) && (XmGetVisibility(text) != XmVISIBILITY_FULLY_OBSCURED))
    if (!(XmProcessTraversal(text,XmTRAVERSE_CURRENT)))
      fprintf(stderr,"traverse to %s failed!",XtName(text));
}

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);
}

void change_color(Widget w,Pixel color)
{
  XmChangeColor(w,color);
}

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;
}

