/* X-interface for the .WAV-Player/recorder */

#include <stdio.h>
#include <sys/wait.h>
#include <sys/errno.h>
#include <signal.h>
#include <fcntl.h>

#include "xplay.h"

char speed_buf[SPEED_LEN];
char limit_buf[LIMIT_LEN];
char song_name_buf[NAME_LEN];

char *edit_buf; 			/* edit buffer for samples */
int modified = 0;			/* 1, if sample was modified */
int x1_old = 0, x2_old = 0;             /* coordiantes of old select window */
header_data options;                    /* commandline options */
int visible_sample;			/* visible part of sample */
DWORD start_visible;			/* pointer to first data displayed */
int pid=-1; 				/* pid of the play process */
Arg args[3];

Display *dpy;
Screen *screen;

static XContext dialog_context = None;

#define DISC_CON_QUIT 1
#define DISC_CON_LOAD 2
#define DISC_CON_RECORD 3

typedef struct _DialogInfo {
   void (*func)();
   int flag; 		/* discard_question == YES: flag dependend action */
} DialogInfo;

/* additional resources */

typedef struct {
   String speed;	/* default speed */
   String limit;	/* default limit */
   String version;	/* check, if resource file is loaded */
} Resources;

Resources resources;

static XtResource resource_list[] = {
   { "speed",                         /* resource name */ 
     "Speed",                         /* resource class */ 
     XtRString,                       /* resource type */ 
     sizeof(String),                  /* size of type */ 
     XtOffsetOf(Resources, speed),    /* name of field in struct */
     XtRString,                       /* default type */
     "22050" },                       /* default */
   { "limit", "Limit", XtRString, sizeof(String), XtOffsetOf(Resources, limit),
     XtRString, "10" },
   { "version", "Version", XtRString, sizeof(String), 
     XtOffsetOf(Resources, version), XtRString, "no version" }
};

Widget top_level;                    /* the top-level widget */
widget_structure ww;                 /* widget tree */
graph_ctx g_ctx;                     /* graphics context */

static XtActionsRec action_list[] = {
   {"PopdownFileDialog", popdown_file_dialog_action}, 
   {"SetLimitBorders", set_limit_borders_action}, 
   {"ExposeGraph", expose_graph_action}
};

void main (int argc, char **argv)
{
   XtAppContext app_context; 
   Pixmap title;
   Widget w;

   top_level = XtVaAppInitialize (&app_context,
      "XPlay", 				/* application class */
      NULL, 0, 				/* command line option list */
      &argc, argv, 
      NULL, NULL);

   dpy = XtDisplay(top_level);           /* get the display pointer */
   screen = XDefaultScreenOfDisplay(dpy);

   /* set the applications action routines */

   XtAppAddActions(app_context, action_list, XtNumber(action_list));

   /* get the application resources */

   XtGetApplicationResources(top_level, &resources, resource_list, 
                             XtNumber(resource_list), NULL, 0);
   if (!strcmp(resources.version, "no version")) 
      XtAppWarningMsg(app_context, "noAppContext", 
      "getApplicationResources", "Resources", 
      "Could not open application defaults file", NULL, NULL);
   strncpy(speed_buf, resources.speed, sizeof(speed_buf));
   strncpy(limit_buf, resources.limit, sizeof(limit_buf));
   strcpy(song_name_buf, "");

   /* convert the pixmaps */

   title = XCreateBitmapFromData(dpy, DefaultRootWindow(dpy), 
   				title_bits, title_width, title_height);

   /* install the icon pixmap */

   XtVaSetValues (top_level, XtNiconPixmap, title, NULL);
   XtVaSetValues (top_level, XtNtitle, "WAVplay / record Version 0.21", NULL);
   XtVaSetValues (top_level, XtNiconName, "WAVplay", NULL);

   /* create the widget hierarchy */

   create_widget_hierarchy(top_level, speed_buf, limit_buf, song_name_buf, 
                           &g_ctx);

   /* now realize the widget tree... */

   XtRealizeWidget (top_level);

   /* ...and sit around forever waiting for events! */

   XtAppMainLoop (app_context);
}

/* callbacks and subroutines */

void quit_question(Widget w, XtPointer client_data, XtPointer call_data)
{
   if ((edit_buf != NULL) && (modified)) 
      discard_question(DISC_CON_QUIT);		/* quit, if discard */
   else
      quit_application();
}

void quit_application()
{
   XtReleaseGC(top_level, g_ctx.graph_GC);
   XtReleaseGC(top_level, g_ctx.line_GC);
   XtReleaseGC(top_level, g_ctx.delete_line_GC);
   XtDestroyWidget(top_level);
   exit(0);
}

void stop_playing(Widget w, XtPointer client_data, XtPointer call_data)
{
   kill((pid_t)pid, SIGKILL);	/* kill the play process */
   XtSetSensitive(w, False);
   pid = -1;
}

void play_sample(Widget w, XtPointer client_data, XtPointer call_data)
{
   if (!edit_buf) {
      err(1, "No sample loaded");
      return;
   }
   if (pid > 0) {
      if (waitpid((pid_t)pid, NULL, WNOHANG) == 0) { 
         err(1, "Play process has\nnot yet finished!");
         return;
      }
   }
   get_changeable_options();
   pid = fork();		/* create play process */
   XtSetSensitive(ww.stop_btn, True);
   if (!pid) {			/* we are the play process */
      XtDestroyApplicationContext(XtWidgetToApplicationContext(w));
      recplay(NULL, PLAY, options, edit_buf);
      exit(0);			/* finish playing - kill the process */
   } else if (pid <= 0) {
      err(1, "Process creation failed!");
      return;
   } 
   /* simply return after process creation */
}

void get_changeable_options()
{
   char *wid;

   options.speed = atoi(speed_buf); 
   wid = (char *) XawToggleGetCurrent(ww.stereo_btn);
   options.stereo = strcmp(wid, "MON");
   wid = (char *) XawToggleGetCurrent(ww.eight_bit_btn);
   if (wid[1] == '8')
      options.sample_size = 8;
   else if (wid[1] == '2') 
      options.sample_size = 12;
   else
      options.sample_size = 16;
   sscanf(limit_buf, "%f", &options.time_limit);
}

void record_question(Widget w, XtPointer client_data, XtPointer call_data)
{
   if (edit_buf != NULL) {
      if (pid > 0) {
         if (waitpid((pid_t)pid, NULL, WNOHANG) == 0) { 
            err(1, "Play process has\nnot yet finished!");
            return;
         }
      }
      if (!modified) {
         XtFree(edit_buf);
         edit_buf=NULL;
         x1_old = x2_old = 0;
         strcpy(song_name_buf, "");
         XtSetArg(args[0], XtNstring, song_name_buf);
         XtSetValues(ww.song_title, args, 1);
         update_display();
         XFlush(dpy);
         record_sample();
      } else
         discard_question(DISC_CON_RECORD);		/* record, if discard */
   } else
      record_sample();
}

void record_sample()
{
   wave_header head;
   DWORD buf_siz;

   get_changeable_options();

   /* force all options */
   options.force_speed = options.force_stereo = options.force_sample_size = 1;
   options.quiet_mode = 1;	/* always quiet */
   options.lower_border = 0;

   /* allocate the edit buffer */
   buf_siz = options.speed * options.time_limit;
   if (options.stereo) buf_siz *= 2;
   if (options.sample_size != 8) buf_siz *= 2;
   buf_siz += 30;                /* 30 additional bytes */
   edit_buf = XtMalloc(buf_siz);

   recplay(NULL, RECORD, options, edit_buf);

   memcpy(&head, edit_buf, sizeof(head));
   options.upper_border = head.data_length;
   visible_sample = head.data_length;	/* whole sample is to be seen */
   start_visible = 0;
   modified = 1;

   update_display();
}

void update_display()
{
   Window win = XtWindow(ww.graph);		/* get window ID */
   XPoint pt1[GRAPH_WIDTH+1], pt2[GRAPH_WIDTH+1];
   wave_header head;
   int factor;
   int step;
   char *pointer;
   int i;
   DWORD max_sample_data;
   int base_y1=0, base_y2=0;			/* baselines */
   union sample_data {
      char data8;				/* 8 bit per sample */
      unsigned int data16;			/* 12, 16 bit per sample */
   } sample1, sample2;

   XClearArea(dpy, win, 0, 0, 0, 0, False);
   if (!edit_buf) return;

   pointer = edit_buf; 
   memcpy(&head, pointer, sizeof(head));
   pointer += sizeof(head);
   pointer += start_visible;

   step = visible_sample / GRAPH_WIDTH;	/* step index */
   if (!step) step=1;

   if (head.modus == 2) {
      base_y1 = 0;
      base_y2 = GRAPH_HEIGHT/3;
      factor = GRAPH_HEIGHT/2;
      pt1[0].x = pt2[0].x = 0;
      pt1[0].y = base_y2;
      pt2[0].y = base_y2*2;
   } else {
      base_y1 = 0;
      factor = GRAPH_HEIGHT;
      pt1[0].x = 0;
      pt1[0].y = GRAPH_HEIGHT/2;
   }

   sample1.data16 = sample2.data16 = 0;		/* clear union */
      
   for (i=1; i<=GRAPH_WIDTH; i++, pointer += step) {	/* draw graph */
      if (head.bit_p_spl != 8) {
         sample1.data16 = (unsigned int) *pointer;
         sample2.data16 = (unsigned int) *(pointer+1);
         max_sample_data = 65535;
      } else {
         sample1.data8 = (char) *pointer;
         sample2.data8 = (char) *(pointer+1);
         max_sample_data = 255;
      }
      /* normalize and plot */
      pt1[i].x = i;
      pt1[i].y = base_y1 + sample1.data16 * factor / max_sample_data; 
      if (head.modus == 2) {
         pt2[i].x = i;
         pt2[i].y = base_y2 + sample2.data16 * factor / max_sample_data; 
      }
   }
   XDrawLines(dpy, win, g_ctx.graph_GC, pt1, GRAPH_WIDTH+1, CoordModeOrigin);
   if (head.modus == 2)
      XDrawLines(dpy, win, g_ctx.graph_GC, pt2, GRAPH_WIDTH+1, CoordModeOrigin);
   calculate_limit_borders();
}

void set_limit_borders_action(Widget w, XEvent *event, String *params, 
                              Cardinal *num_params)
{
   Window win = XtWindow(w);
   int win_x, win_y; 
   int x, y;
   unsigned int mask;
   wave_header head;
   Window root, child;
   float limit_val;
   int step, old_upper, old_lower;

   if (!edit_buf) return;

   if (win != event->xany.window) return;  /* event has to come from graph */

   XQueryPointer(dpy, win, 
                 &root,            /* root window under the pointer */
                 &child,           /* child window under the pointer */
                 &x, &y,           /* pointer coords relative to root */
                 &win_x, &win_y,   /* pointer coords relative to window */
                 &mask);           /* state of modifier keys and buttons */

   memcpy(&head, edit_buf, sizeof(head));
   step = visible_sample / GRAPH_WIDTH;

   /* set lower_border if Button1 was pressed, set upper border if 
      Button2 was pressed, otherwise set borders to default values */
   if (mask & Button1Mask) {
      old_lower = options.lower_border;
      options.lower_border = win_x * step + start_visible;
      if (options.upper_border < options.lower_border) {
         err(1, "Lower border must not exceed\nupper border!");
         options.lower_border = old_lower;
         return;
      }
      draw_limit_borders(win_x, x2_old);
   } else if (mask & Button2Mask) {
      old_upper = options.upper_border;
      options.upper_border = win_x * step + start_visible;
      if (options.upper_border < options.lower_border) {
         err(1, "Lower border must not exceed\nupper border!");
         options.upper_border = old_upper;
         return;
      }
      draw_limit_borders(x1_old, win_x);
   } else {
      options.lower_border = start_visible;
      options.upper_border = visible_sample + start_visible;
      draw_limit_borders(0, GRAPH_WIDTH);
   }
   limit_val = (float)(options.upper_border - options.lower_border) / 
                (float)(head.sample_fq * head.modus);
   sprintf(limit_buf, "%.1f", limit_val);
   XtSetArg(args[0], XtNstring, limit_buf);
   XtSetValues(ww.limit, args, 1);
   XFlush(dpy);
}

void calculate_limit_borders()
{
   int x1, x2, step;
   float limit;
   wave_header head;

   if (!edit_buf) return;

   memcpy(&head, edit_buf, sizeof(head));
   step = visible_sample / GRAPH_WIDTH;
   if (!step) step=1;

   sscanf(limit_buf, "%f", &limit);
   if (limit < 0.0001) return;

   options.upper_border = limit * head.sample_fq * head.modus + 
                           options.lower_border;

   x1 = (options.lower_border - start_visible) / step;
   x2 = (options.upper_border - start_visible) / step;

   draw_limit_borders(x1, x2);
}

void draw_limit_borders(int x1, int x2)
{
   Window win = XtWindow(ww.graph);

   XDrawLine(dpy, win, g_ctx.delete_line_GC, x1_old, 0, x1_old, GRAPH_HEIGHT);
   XDrawLine(dpy, win, g_ctx.delete_line_GC, x2_old, 0, x2_old, GRAPH_HEIGHT);
   XDrawLine(dpy, win, g_ctx.line_GC, x1, 0, x1, GRAPH_HEIGHT);
   XDrawLine(dpy, win, g_ctx.line_GC, x2, 0, x2, GRAPH_HEIGHT);
   XFlush(dpy);

   x1_old = x1;
   x2_old = x2;
}

void zoom_out(Widget w, XtPointer client_data, XtPointer call_data)
{
   wave_header head;

   memcpy(&head, edit_buf, sizeof(head));
   visible_sample = head.data_length;
   start_visible = 0;
   update_display();
}

void zoom_in(Widget w, XtPointer client_data, XtPointer call_data)
{
   visible_sample = options.upper_border - options.lower_border;
   start_visible = options.lower_border;
   update_display();
}

void open_dialog(Widget w, XtPointer client_data, XtPointer call_data)
{
   if (w == ww.load_btn) {
      if (edit_buf != NULL) {
         if (!modified) {
            XtFree(edit_buf);
            edit_buf=NULL;
            x1_old = x2_old = 0;
            strcpy(song_name_buf, "");
            XtSetArg(args[0], XtNstring, song_name_buf);
            XtSetValues(ww.song_title, args, 1);
            update_display();
            XFlush(dpy);
         } else {
            discard_question(DISC_CON_LOAD);		/* load, if discard */
            return;
         }
      }
      popup_file_dialog (w, "Load file: ", song_name_buf, load_file);
   } else
      popup_file_dialog (w, "Save as: ", song_name_buf, save_file);
}

void calculate_echo(Widget w, XtPointer client_data, XtPointer call_data)
{
   modified = 1;
   err(1, "Not yet implemented!\n(The file is now marked\nas modified!)");
}

void load_file()
{
   char *pointer;
   wave_header head;
   int fd; 
   DWORD l, length; 
    
   if ((fd = open (song_name_buf, O_RDONLY, 0)) == -1) {
      err(1, "Could not open file");
      return;
   }
   read(fd, &head, sizeof(head));
   if (!header_check(&head)) return;

   /* force all options */
   options.force_speed = options.force_stereo = options.force_sample_size = 1;
   options.quiet_mode = 1;	/* always quiet */
   options.speed = head.sample_fq;
   options.stereo = (head.modus == 2);
   options.sample_size = head.bit_p_spl;
   /* calculate full time limit */
   options.time_limit = (float) head.data_length / 
                        (float) (head.sample_fq * head.modus);
   sprintf(limit_buf, "%.1f", options.time_limit);

   options.lower_border = 0;
   options.upper_border = head.data_length;
   visible_sample = head.data_length;	/* whole sample is to be seen */
   start_visible = 0;

   /* set the widgets accordingly */

   XtSetArg(args[0], XtNstring, limit_buf);
   XtSetValues(ww.limit, args, 1);
   XtSetArg(args[0], XtNstring, song_name_buf);
   XtSetValues(ww.song_title, args, 1);
   sprintf(speed_buf, "%u", head.sample_fq);
   XtSetArg(args[0], XtNstring, speed_buf);
   XtSetValues(ww.speed, args, 1);
   if (options.stereo)  {
      XtSetArg(args[0], XtNstate, True);
      XtSetValues(ww.stereo_btn, args, 1);
      XtSetArg(args[0], XtNstate, False);
      XtSetValues(ww.mono_btn, args, 1);
   } else {
      XtSetArg(args[0], XtNstate, False);
      XtSetValues(ww.stereo_btn, args, 1);
      XtSetArg(args[0], XtNstate, True);
      XtSetValues(ww.mono_btn, args, 1);
   }
   if (options.sample_size == 8) {
      XtSetArg(args[0], XtNstate, True);
      XtSetValues(ww.eight_bit_btn, args, 1);
   } else if (options.sample_size == 12) {
      XtSetArg(args[0], XtNstate, True);
      XtSetValues(ww.twelve_bit_btn, args, 1);
   } else {
      XtSetArg(args[0], XtNstate, True);
      XtSetValues(ww.sixteen_bit_btn, args, 1);
   }

   /* allocate the edit buffer */
   length = head.length + 30;	/* 30 additional bytes */
   edit_buf = pointer = XtMalloc(length);

   memcpy(pointer, &head, sizeof(head));
   pointer += sizeof(head);

   /* load the file into the edit_buffer */

   l = length - sizeof(head);
   if ((l = read(fd, pointer, l)) == -1) {
      err(1, "Error loading sample!");
      return;
   }

   close(fd);

   /* drawing area will be updated automatically */
}

void save_file()
{
   int fd;
   char *pointer;
   wave_header head;

   /* remove file, if alread exists */
   remove(song_name_buf);

   if ((fd = open (song_name_buf, O_WRONLY | O_CREAT, 0666)) == -1) {
      err(1, "Could not open file");
      return;
   }

   pointer = edit_buf;
   memcpy(&head, pointer, sizeof(head));
   write(fd, pointer, sizeof(head));
   pointer += sizeof(head);
   write(fd, pointer, head.data_length);
   close(fd);
   modified = 0;
   XtSetArg(args[0], XtNstring, song_name_buf);
   XtSetValues(ww.song_title, args, 1);
   XFlush(dpy);
}

void popup_file_dialog (Widget w, String str, String def_fn, void (*func)() )
/* Pops up a file dialog for widget w, label str, default filename def_fn */
/* calls func if the user clicks on OK */
{
   DialogInfo *file_info;
   Widget file_dialog;

   Widget shell = XtCreatePopupShell("file_dialog_shell", 
   				     transientShellWidgetClass, 
          		             top_level, NULL, 0);

   if (dialog_context == None) dialog_context = XUniqueContext();

   file_dialog = MW ("file_dialog", dialogWidgetClass, shell, 
      XtNlabel, str, 
      XtNvalue, def_fn, 
      NULL);

   file_info = XtNew(DialogInfo);
   file_info->func = func;

   if (XSaveContext(dpy, (Window) file_dialog, 
      dialog_context, (caddr_t) file_info) != 0) {
      fprintf(stderr, "Error while trying to save context\n");
      XtDestroyWidget(shell);
      return;
   }

   XawDialogAddButton(file_dialog, "OK", popdown_file_dialog, (XtPointer) 1);
   XawDialogAddButton(file_dialog, "Cancel", popdown_file_dialog, NULL);

   popup_centered(shell);
}

void popup_centered(Widget w)
{
   int win_x, win_y; 
   int x, y;
   Dimension width, height, b_width;
   unsigned int mask;
   Window root, child;
   Cardinal num_args;

   XtRealizeWidget(w);

   XQueryPointer(dpy, XtWindow(w), 
    		 &root,  		/* root window under the pointer */
    		 &child, 		/* child window under the pointer */
    		 &x, &y, 		/* pointer coords relative to root */
	 	 &win_x, &win_y, 	/* pointer coords relative to window */
		 &mask);		/* state of modifier keys and buttons */
    		  
   num_args = 0;
   XtSetArg(args[num_args], XtNwidth, &width); num_args++;
   XtSetArg(args[num_args], XtNheight, &height); num_args++;
   XtSetArg(args[num_args], XtNborderWidth, &b_width); num_args++;
   XtGetValues(w, args, num_args);

   width += 2 * b_width;
   height += 2 * b_width;

   x -= ((int) width/2);
   y -= ( (Position) height/2 );

   num_args = 0;
   XtSetArg(args[num_args], XtNx, x); num_args++;
   XtSetArg(args[num_args], XtNy, y); num_args++;
   XtSetValues(w, args, num_args);
     
   XtPopup(w, XtGrabExclusive);
}

void popdown_file_dialog_action(Widget w, XEvent *event, String *params, 
                                Cardinal *num_params)
{
   char buf[80];
   Boolean val;

   if (*num_params != 1) {
      sprintf(buf, "Action `%s' must have exactly one argument.", 
              "popdown_file_dialog");
      err(1, buf);
      return;
   }

   XmuCopyISOLatin1Lowered(buf, params[0]);

   if (!strcmp(buf, "cancel"))
      val = FALSE;
   else if (!strcmp(buf, "okay"))
      val = TRUE;
   else {
      sprintf(buf, "Action %s's argument must be either `cancel' or `okay'.",
              "popdown_file_dialog");
      err(1, buf);
      return;
   }

   popdown_file_dialog(w, (XtPointer) val, (XtPointer) NULL);
}

void popdown_file_dialog(Widget w, XtPointer client_data, XtPointer call_data)
{
   Widget dialog = XtParent(w);
   caddr_t file_info_ptr;
   DialogInfo * file_info;

   if (XFindContext(dpy, (Window) dialog, dialog_context,
                    &file_info_ptr) == XCNOENT) {
      fprintf(stderr, "Error while trying to find context\n");
   }

   (void) XDeleteContext(dpy, (Window)dialog, 
                         dialog_context);

   file_info = (DialogInfo *) file_info_ptr;

   XtPopdown(XtParent(dialog));
   XtDestroyWidget(XtParent(dialog));
   XFlush(dpy);

   if ( ((Boolean) client_data) == 1 ) {
       String filename = XawDialogGetValueString(dialog);
       strncpy(song_name_buf, filename, sizeof(song_name_buf));
       (*file_info->func)(); 		/* call handler */
   }

   XtFree( (XtPointer) file_info); 	/* Free data. */
}

/* X11-error handler */
void err(int severity, char *text)
{
   Widget shell, dialog; 

   /* do something tricky to avoid changing the whole errorhandler */
   /* if pid=-1 or pid>0 we are the main process and are able to   */
   /* open an error dialog; if pid = 0, we aren't!                 */

   if (!pid) { 
      fprintf(stderr, "%s\n", text);
      return;
   }

   shell = XtCreatePopupShell("dialog_shell", 
   				     transientShellWidgetClass, 
          		             top_level, NULL, 0);

   dialog = MW ("warning", dialogWidgetClass, shell, 
      XtNlabel, text, 
      NULL);

   XawDialogAddButton(dialog, "OK", close_error, NULL);

   popup_centered(shell);
}

void close_error(Widget w, XtPointer client_data, XtPointer call_data)
{
   Widget dialog = XtParent(w);

   XtPopdown(XtParent(dialog));
   XtDestroyWidget(XtParent(dialog));
   XFlush(dpy);
}

void discard_question(int flag)
/* click on YES: execute flag dependend action */ 
{
   Widget dialog;
   DialogInfo *info;

   Widget shell = XtCreatePopupShell("dialog_shell", 
   				     transientShellWidgetClass, 
          		             top_level, NULL, 0);

   if (dialog_context == None) dialog_context = XUniqueContext();

   dialog = MW ("dialog", dialogWidgetClass, shell, 
      XtNlabel, "Discard modified file?", 
      NULL);

   info = XtNew(DialogInfo);
   info->flag = flag;

   if (XSaveContext(dpy, (Window) dialog,
      dialog_context, (caddr_t) info) != 0) {
      fprintf(stderr, "Error while trying to save context\n");
      XtDestroyWidget(shell);
      return;
   }

   XawDialogAddButton(dialog, "Yes", discard_answer, (XtPointer) 1);
   XawDialogAddButton(dialog, "No", discard_answer, NULL);

   popup_centered(shell);
}

void discard_answer(Widget w, XtPointer client_data, XtPointer call_data)
{
   Widget dialog = XtParent(w);
   caddr_t info_ptr;
   DialogInfo *info;

   if (XFindContext(dpy, (Window) dialog, dialog_context, &info_ptr) ==
       XCNOENT) {
      fprintf(stderr, "Error while trying to find context\n");
   }

   (void) XDeleteContext(dpy, (Window) dialog, dialog_context);

   info = (DialogInfo *) info_ptr;

   XtPopdown(XtParent(dialog));
   XtDestroyWidget(XtParent(dialog));
   XFlush(dpy);

   if ((Boolean) client_data == 1) {
      XtFree(edit_buf);
      edit_buf=NULL;
      x1_old = x2_old = 0;
      strcpy(song_name_buf, "");
      XtSetArg(args[0], XtNstring, song_name_buf);
      XtSetValues(ww.song_title, args, 1);
      update_display();
      XFlush(dpy);
      modified = 0;
      switch (info->flag) {
         case DISC_CON_QUIT:
            quit_application();
            break;
         case DISC_CON_LOAD:
            popup_file_dialog (w, "Load file: ", song_name_buf, load_file);
            break;
         case DISC_CON_RECORD:
            record_sample();
            break;
         default:
           fprintf(stderr, "internal error: no valid continuation flag\n");
           exit(-1);
     }
   }
   XtFree( (XtPointer) info); 	/* Free data. */
}

void expose_graph_action (Widget w, XEvent *event, String *params, 
                          Cardinal *num_params)
{
   if ((event->type == Expose) &&
       (event->xany.window == XtWindow(w))) {
      if (!event->xexpose.count) {   /* use only the last event */
         update_display();
      }
   }
}

