/* melder.c
 *
 * Copyright (C) 1992-2003 Paul Boersma
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
	pb 2002/02/05
	pb 2002/03/07 GPL
	pb 2002/03/13 Mach
	pb 2002/11/30 Melder_fixed
	pb 2002/12/01 Melder_single, Melder_half
	pb 2002/12/11 MelderInfo
	pb 2003/05/13 Melder_percent
	pb 2003/05/19 Melder_atof
	pb 2003/05/19 Melder_fixed: include a minimum precision of 1 digit
 */

#include <math.h>
#include <time.h>
#include <ctype.h>
#include "melder.h"
#include "longchar.h"
#include "NUM.h"
#ifdef _WIN32
	#include <windows.h>
#endif
#if defined (macintosh)
	#include "macport_on.h"
	#include <Sound.h>
	#include "macport_off.h"
#endif
#ifndef CONSOLE_APPLICATION
	#include "Graphics.h"
	#include "machine.h"
	#ifdef macintosh
		#include "macport_on.h"
		#include <Events.h>
		#include <Dialogs.h>
		#include <MacErrors.h>
		#include "macport_off.h"
	#endif
	#include "motif.h"
#endif

/********** Exported variables. **********/

int Melder_batch;   /* Don't we have a GUI?- Set once at application start-up. */
int Melder_backgrounding;   /* Are we running a script?- Set and unset dynamically. */
char Melder_buffer1 [30001], Melder_buffer2 [30001];
unsigned long Melder_systemVersion;

#ifndef CONSOLE_APPLICATION
	void *Melder_appContext;   /* XtAppContext* */
	void *Melder_topShell;   /* Widget */
#endif

static int defaultPause (char *message) {
	int key;
	fprintf (stderr, "Pause: %s\nPress 'q' to stop, or anyother key to continue.\n", message);
	key = getc (stdin);
	return key != 'q' && key != 'Q';
}

static void defaultInformation (char *message) {
	printf ("%s", message);
}

static void defaultHelp (const char *query) {
	Melder_flushError ("Do not know how to find help on \"%s\".", query);
}

static void defaultSearch (void) {
	Melder_flushError ("Do not know how to search.");
}

static void defaultWarning (char *message) {
	fprintf (stderr, "Warning: %s\n", message);
}

static void defaultError (char *message) {
	fprintf (stderr, strstr (message, "You interrupted") ? "User interrupt: %s\n" : "Error: %s\n", message);
}

static void defaultFatal (char *message) {
	fprintf (stderr, "Fatal error: %s\n", message);
}

static int defaultPublish (void *anything) {
	(void) anything;
	return 0;   /* Nothing published. */
}

static int defaultRecord (double duration) {
	(void) duration;
	return 0;   /* Nothing recorded. */
}

static int defaultRecordFromFile (MelderFile file) {
	(void) file;
	return 0;   /* Nothing recorded. */
}

static void defaultPlay (void) {}

static void defaultPlayReverse (void) {}

static int defaultPublishPlayed (void) {
	return 0;   /* Nothing published. */
}

/********** Current message methods: initialize to default (batch) behaviour. **********/

static struct {
	int (*pause) (char *message);
	void (*information) (char *message);
	void (*help) (const char *query);
	void (*search) (void);
	void (*warning) (char *message);
	void (*error) (char *message);
	void (*fatal) (char *message);
	int (*publish) (void *anything);
	int (*record) (double duration);
	int (*recordFromFile) (MelderFile fs);
	void (*play) (void);
	void (*playReverse) (void);
	int (*publishPlayed) (void);
}
	theMelder = {
		defaultPause, defaultInformation, defaultHelp, defaultSearch,
		defaultWarning, defaultError, defaultFatal,
		defaultPublish,
		defaultRecord, defaultRecordFromFile, defaultPlay, defaultPlayReverse, defaultPublishPlayed
	};

/********** CASUAL **********/

void Melder_casual (const char *format, ...) {
	va_list arg;
	va_start (arg, format);
	vsprintf (Melder_buffer1, format, arg);
	Longchar_nativize (Melder_buffer1, Melder_buffer2, ! Melder_batch);
	#if defined (_WIN32) && ! defined (CONSOLE_APPLICATION)
	if (! Melder_batch) {
		MessageBox (NULL, Melder_buffer2, "Casual info", MB_OK);
	} else
	#endif
	fprintf (stderr, "%s\n", Melder_buffer2);
	va_end (arg);
}

/********** STOPWATCH **********/

double Melder_stopwatch (void) {
	static clock_t lastTime;
	clock_t now = clock ();
	double timeElapsed = lastTime == 0 ? -1.0 : (now - lastTime) / (double) CLOCKS_PER_SEC;
	lastTime = now;
	return timeElapsed;
}

/********** PROGRESS **********/

static int theProgressDepth = 0;
void Melder_progressOff (void) { theProgressDepth --; }
void Melder_progressOn (void) { theProgressDepth ++; }

#ifndef CONSOLE_APPLICATION
static int waitWhileProgress (double progress, char *message, Widget dia, Widget scale, Widget label, Widget cancelButton) {
	#if defined (macintosh)
	{
		EventRecord event;
		(void) cancelButton;
		while (GetNextEvent (keyDownMask, & event)) {
			if ((event.modifiers & cmdKey) && (event.message & charCodeMask) == '.') {
				FlushEvents (everyEvent, 0);
				XtUnmanageChild (dia);
				return 0;
			}
		}
		do { XtNextEvent ((XEvent *) & event); XtDispatchEvent ((XEvent *) & event); } while (event.what);
	}
	#elif defined (_WIN32)
	{
		XEvent event;
		while (PeekMessage (& event, 0, 0, 0, PM_REMOVE)) {
			if (event. message == WM_KEYDOWN) {
				/*
				 * Ignore all key-down messages, except Escape.
				 */
				if (LOWORD (event. wParam) == VK_ESCAPE) {
					XtUnmanageChild (dia);
					return 0;
				}
			} else if (event. message == WM_LBUTTONDOWN) {
				/*
				 * Ignore all mouse-down messages, except click in Interrupt button.
				 */
				Widget me = (Widget) GetWindowLong (event. hwnd, GWL_USERDATA);
				if (me == cancelButton) {
					XtUnmanageChild (dia);
					return 0;
				}
			} else if (event. message != WM_SYSKEYDOWN) {
				/*
				 * Process paint messages etc.
				 */
				DispatchMessage (& event);
			}
		}
	}
	#else
	{
		XEvent event;
		if (XCheckTypedWindowEvent (XtDisplay (cancelButton), XtWindow (cancelButton), ButtonPress, & event)) {
			XtUnmanageChild (dia);
			return 0;
		}
	}
	#endif
	if (progress >= 1.0) {
		XtUnmanageChild (dia);
	} else {
		if (progress <= 0.0) progress = 0.0;
		XtManageChild (dia);
		XtVaSetValues (label, motif_argXmString (XmNlabelString, message), 0);
		XmScaleSetValue (scale, floor (progress * 1000.0));
		XmUpdateDisplay (dia);
	}
	return 1;
}
#endif

int Melder_progress (double progress, const char *format, ...) {
	(void) progress;
	(void) format;
	#ifndef CONSOLE_APPLICATION
	if (! Melder_batch && theProgressDepth >= 0) {
		static clock_t lastTime;
		static Widget dia = NULL, scale = NULL, label = NULL, cancelButton = NULL;
		clock_t now = clock ();
		if (progress <= 0.0 || progress >= 1.0 || now - lastTime > 0.25 * CLOCKS_PER_SEC) {
			int interruption;
			va_list arg;
			va_start (arg, format);
			if (format) {
				vsprintf (Melder_buffer1, format, arg);
				Longchar_nativize (Melder_buffer1, Melder_buffer2, ! Melder_batch);
			} else {
				Melder_buffer2 [0] = '\0';
			}
			va_end (arg);
			if (dia == NULL) {
				dia = XmCreateFormDialog (Melder_topShell, "melderProgress", NULL, 0);
				XtVaSetValues (XtParent (dia), XmNx, 200, XmNy, 100,
					XmNtitle, "Work in progress",
					XmNdeleteResponse, XmUNMAP,
					NULL);
				XtVaSetValues (dia, XmNautoUnmanage, True, NULL);
				label = XmCreateLabel (dia, "label", NULL, 0);
				XtVaSetValues (label, XmNwidth, 400, 0);
				XtManageChild (label);
				scale = XmCreateScale (dia, "scale", NULL, 0);
				XtVaSetValues (scale, XmNy, 40, XmNwidth, 400, XmNminimum, 0, XmNmaximum, 1000,
					XmNorientation, XmHORIZONTAL,
					#if ! defined (macintosh)
						XmNscaleHeight, 20,
					#endif
					0);
				XtManageChild (scale);
				#if ! defined (macintosh)
					cancelButton = XmCreatePushButton (dia, "Interrupt", NULL, 0);
					XtVaSetValues (cancelButton, XmNy, 140, XmNwidth, 400, 0);
					XtManageChild (cancelButton);
				#endif
			}
			interruption = waitWhileProgress (progress, Melder_buffer2, dia, scale, label, cancelButton);
			if (! interruption) Melder_error ("Interrupted!");
			lastTime = now;
			return interruption;
		}
	}
	#endif
	return 1;   /* Proceed. */
}

void * Melder_monitor (double progress, const char *format, ...) {
	(void) progress;
	(void) format;
	#ifndef CONSOLE_APPLICATION
	if (! Melder_batch && theProgressDepth >= 0) {
		static Widget dia = NULL, scale = NULL, label = NULL, cancelButton = NULL, drawingArea = NULL;
		static Any graphics = NULL;
		va_list arg;
		va_start (arg, format);
		if (format) {
			vsprintf (Melder_buffer1, format, arg);
			Longchar_nativize (Melder_buffer1, Melder_buffer2, ! Melder_batch);
		} else {
			Melder_buffer2 [0] = '\0';
		}
		va_end (arg);
		if (dia == NULL) {
			dia = XmCreateFormDialog (Melder_topShell, "melderMonitor", NULL, 0);
			XtVaSetValues (XtParent (dia), XmNx, 200, XmNy, 100,
				XmNtitle, "Work in progress",
				XmNdeleteResponse, XmUNMAP,
				NULL);
			XtVaSetValues (dia, XmNautoUnmanage, True, NULL);
			label = XmCreateLabel (dia, "label", NULL, 0);
			XtVaSetValues (label, XmNwidth, 400, 0);
			XtManageChild (label);
			scale = XmCreateScale (dia, "scale", NULL, 0);
			XtVaSetValues (scale, XmNy, 40, XmNwidth, 400, XmNminimum, 0, XmNmaximum, 1000,
				XmNorientation, XmHORIZONTAL,
				#if ! defined (macintosh) && ! defined (_WIN32)
					XmNscaleHeight, 20,
				#endif
				0);
			XtManageChild (scale);
			#if ! defined (macintosh)
				cancelButton = XmCreatePushButton (dia, "Interrupt", NULL, 0);
				XtVaSetValues (cancelButton, XmNy, 140, XmNwidth, 400, 0);
				XtManageChild (cancelButton);
			#endif
			drawingArea = XmCreateDrawingArea (dia, "drawingArea", NULL, 0);
			XtVaSetValues (drawingArea, XmNy, 200, XmNwidth, 400, XmNheight, 200,
				XmNmarginWidth, 10, XmNmarginHeight, 10, 0);
			XtManageChild (drawingArea);
			XtManageChild (dia);
			graphics = Graphics_create_xmdrawingarea (drawingArea);
		}
		if (! waitWhileProgress (progress, Melder_buffer2, dia, scale, label, cancelButton))
			return Melder_errorp ("Interrupted!");
		if (progress == 0.0)
			return graphics;
	}
	#endif
	return progress <= 0.0 ? NULL /* No Graphics. */ : & progress /* Any non-NULL pointer. */;
}

/********** PAUSE **********/

int Melder_pause (const char *format, ...) {
	int interruption;
	va_list arg;
	va_start (arg, format);
	if (format) {
		vsprintf (Melder_buffer1, format, arg);
		Longchar_nativize (Melder_buffer1, Melder_buffer2, ! Melder_batch);
	} else {
		Melder_buffer2 [0] = '\0';
	}
	interruption = theMelder. pause (Melder_buffer1);
	va_end (arg);
	return interruption;
}

/********** NUMBER TO STRING CONVERSION **********/

const char * Melder_integer (long value) {
	static char buffers [11] [12];
	static int index = 0;
	if (++ index == 11) index = 0;
	sprintf (buffers [index], "%ld", value);
	return buffers [index];
}

const char * Melder_boolean (int value) {
	return value ? "yes" : "no";
}

const char * Melder_double (double value) {
	static char buffers [11] [40];
	static int index = 0;
	if (value == NUMundefined) return "--undefined--";
	if (++ index == 11) index = 0;
	sprintf (buffers [index], "%.15g", value);
	if (atof (buffers [index]) != value) {
		sprintf (buffers [index], "%.16g", value);
		if (atof (buffers [index]) != value) sprintf (buffers [index], "%.17g", value);
	}
	return buffers [index];
}

const char * Melder_single (double value) {
	static char buffers [11] [40];
	static int index = 0;
	if (value == NUMundefined) return "--undefined--";
	if (++ index == 11) index = 0;
	sprintf (buffers [index], "%.8g", value);
	return buffers [index];
}

const char * Melder_half (double value) {
	static char buffers [11] [40];
	static int index = 0;
	if (value == NUMundefined) return "--undefined--";
	if (++ index == 11) index = 0;
	sprintf (buffers [index], "%.4g", value);
	return buffers [index];
}

const char * Melder_fixed (double value, int precision) {
	static char buffers [11] [40];
	static int index = 0;
	int minimumPrecision;
	if (value == NUMundefined) return "--undefined--";
	if (value == 0.0) return "0";
	if (++ index == 11) index = 0;
	if (precision == NUMundefined) precision = 60;
	minimumPrecision = - (int) floor (log (value) * NUMlog10e);
	sprintf (buffers [index], "%.*f",
		minimumPrecision > precision ? minimumPrecision : precision, value);
	return buffers [index];
}

const char * Melder_fixedExponent (double value, int exponent, int precision) {
	static char buffers [11] [40];
	static int index = 0;
	double factor = pow (10, exponent);
	int minimumPrecision;
	if (value == NUMundefined) return "--undefined--";
	if (value == 0.0) return "0";
	if (++ index == 11) index = 0;
	if (precision == NUMundefined) precision = 60;
	value /= factor;
	minimumPrecision = - (int) floor (log (value) * NUMlog10e);
	sprintf (buffers [index], "%.*fE%d",
		minimumPrecision > precision ? minimumPrecision : precision, value, exponent);
	return buffers [index];
}

const char * Melder_percent (double value, int precision) {
	static char buffers [11] [40];
	static int index = 0;
	int minimumPrecision;
	if (value == NUMundefined) return "--undefined--";
	if (value == 0.0) return "0";
	if (++ index == 11) index = 0;
	if (precision == NUMundefined) precision = 60;
	value *= 100.0;
	minimumPrecision = - (int) floor (log (value) * NUMlog10e);
	sprintf (buffers [index], "%.*f%%",
		minimumPrecision > precision ? minimumPrecision : precision, value);
	return buffers [index];
}

/********** STRING TO NUMBER CONVERSION **********/

double Melder_atof (const char *string) {
	char numberString [200], *q = & numberString [0];
	if (strnequ (string, "undefined", 9) ||
		strnequ (string, "--undefined--", 13) ||
		strnequ (string, "inf", 3) ||
		strnequ (string, "INF", 3) ||
		strnequ (string, "Inf", 3) ||
		strnequ (string, "nan", 3) ||
		strnequ (string, "NAN", 3) ||
		strnequ (string, "NaN", 3)) return NUMundefined;
	while (*string == ' ' || *string == '\n' || *string == '\t' || *string == '\r')
		string ++;
	if (*string == '-' || *string == '+') * (q ++) = * (string ++);
	while (*string >= '0' && *string <= '9') * (q ++) = * (string ++);
	if (*string == '.') {
		* (q ++) = * (string ++);
		while (*string >= '0' && *string <= '9') * (q ++) = * (string ++);
	}
	if (*string == 'e' || *string == 'E') {
		* (q ++) = * (string ++);
		if (*string == '-' || *string == '+') * (q ++) = * (string ++);
		while (*string >= '0' && *string <= '9') * (q ++) = * (string ++);
	}
	*q = '\0';
	return *string == '%' ? 0.01 * atof (numberString) : atof (numberString);
}

/***** INFO *****/

static char infoBuffer [30001];
static char *infos = & infoBuffer [0];

static void appendInfo (const char *message) {
	int length = strlen (infos);
	if (length + (int) strlen (message) > 30000 - 1) return;   /* 1 == length of "\n" */
	strcpy (infos + length, message);
}

static void appendInfoLine (const char *message) {
	appendInfo (message);
	strcpy (infos + strlen (infos), "\n");
}

void MelderInfo_open (void) {
	infos [0] = '\0';
}

void MelderInfo_write1 (const char *s1) {
	appendInfo (s1);
}
void MelderInfo_write2 (const char *s1, const char *s2) {
	appendInfo (s1);
	appendInfo (s2);
}
void MelderInfo_write3 (const char *s1, const char *s2, const char *s3) {
	appendInfo (s1);
	appendInfo (s2);
	appendInfo (s3);
}
void MelderInfo_write4 (const char *s1, const char *s2, const char *s3, const char *s4) {
	appendInfo (s1);
	appendInfo (s2);
	appendInfo (s3);
	appendInfo (s4);
}
void MelderInfo_write5 (const char *s1, const char *s2, const char *s3, const char *s4, const char *s5) {
	appendInfo (s1);
	appendInfo (s2);
	appendInfo (s3);
	appendInfo (s4);
	appendInfo (s5);
}

void MelderInfo_writeLine1 (const char *s1) {
	appendInfoLine (s1);
}
void MelderInfo_writeLine2 (const char *s1, const char *s2) {
	appendInfo (s1);
	appendInfoLine (s2);
}
void MelderInfo_writeLine3 (const char *s1, const char *s2, const char *s3) {
	appendInfo (s1);
	appendInfo (s2);
	appendInfoLine (s3);
}
void MelderInfo_writeLine4 (const char *s1, const char *s2, const char *s3, const char *s4) {
	appendInfo (s1);
	appendInfo (s2);
	appendInfo (s3);
	appendInfoLine (s4);
}
void MelderInfo_writeLine5 (const char *s1, const char *s2, const char *s3, const char *s4, const char *s5) {
	appendInfo (s1);
	appendInfo (s2);
	appendInfo (s3);
	appendInfo (s4);
	appendInfoLine (s5);
}

void MelderInfo_close (void) {
	if (infos == infoBuffer) {
		theMelder. information (infos);
	}
}

void Melder_information (const char *format, ...) {
	va_list arg;
	va_start (arg, format);
	vsprintf (infos, format, arg);
	/*
		When writing to the Info window, we must add a newline symbol,
		because a subsequent Melder_print call has to start on the next line.
		When writing to a diverted string, we must *not* add a newline symbol,
		because scripts expect returned strings without appended newlines!
	*/
	if (infos == infoBuffer)
		strcat (infos, "\n");
	MelderInfo_close ();
	va_end (arg);
}

void Melder_informationReal (double value, const char *units) {
	MelderInfo_open ();
	if (value == NUMundefined)
		MelderInfo_write1 ("--undefined--");
	else if (units == NULL)
		MelderInfo_write1 (Melder_double (value));
	else
		MelderInfo_write3 (Melder_double (value), " ", units);
	if (infos == infoBuffer)
		strcat (infos, "\n");
	MelderInfo_close ();
}

void Melder_info (const char *format, ...) {
	va_list arg;
	va_start (arg, format);
	vsprintf (Melder_buffer1, format, arg);
	if (infos == infoBuffer) {
		if (theMelder. information == defaultInformation) {
			printf ("%s\n", Melder_buffer1);   /* Do not print the previous lines again. */
		} else {
			appendInfoLine (Melder_buffer1);
			theMelder. information (infos);
		}
	} else {
		strcpy (infos, Melder_buffer1);   /* Without newline! */
	}
	va_end (arg);
}

void Melder_divertInfo (char *buffer) {
	infos = buffer == NULL ? infoBuffer : buffer;
}

void Melder_print (const char *format, ...) {
	va_list arg;
	va_start (arg, format);
	if (theMelder. information == defaultInformation) {
		vprintf (format, arg);   /* Do not print the previous lines again. */
	} else {
		vsprintf (Melder_buffer1, format, arg);
		appendInfo (Melder_buffer1);
		theMelder. information (infos);
	}
	va_end (arg);
}

void Melder_clearInfo (void) { if (infos == infoBuffer) { infos [0] = '\0'; theMelder. information (infos); } }

char * Melder_getInfo (void) { return infos; }

void Melder_help (const char *query) {
	theMelder. help (query);
}

void Melder_search (void) {
	theMelder. search ();
}

/********** WARNING **********/

void Melder_warning (const char *format, ...) {
	va_list arg;
	va_start (arg, format);
	vsprintf (Melder_buffer1, format, arg);
	Longchar_nativize (Melder_buffer1, Melder_buffer2, ! Melder_batch);
	theMelder. warning (Melder_buffer2);
	va_end (arg);
}

void Melder_beep (void) {
	#ifdef macintosh
		SysBeep (0);
	#else
		fprintf (stderr, "\a");
	#endif
}

/*********** ERROR **********/

static char errors [2001];   /* Safe in low-memory situations. */

static void appendError (const char *message) {
	int length = strlen (errors), messageLength = strlen (message);
	if (length + messageLength > 2000 - 1) return;   /* 1 == length of "\n" */
	strcpy (errors + length, message);
	strcpy (errors + length + messageLength, "\n");
}

int Melder_error (const char *format, ...) {
	va_list arg;
	va_start (arg, format);
	vsprintf (Melder_buffer1, format, arg);
	Longchar_nativize (Melder_buffer1, Melder_buffer2, ! Melder_batch);
	appendError (Melder_buffer2);
	va_end (arg);
	return 0;
}

void * Melder_errorp (const char *format, ...) {
	va_list arg;
	va_start (arg, format);
	vsprintf (Melder_buffer1, format, arg);
	Longchar_nativize (Melder_buffer1, Melder_buffer2, ! Melder_batch);
	appendError (Melder_buffer2);
	va_end (arg);
	return NULL;
}

int Melder_hasError (void) { return errors [0] != '\0'; }

void Melder_clearError (void) { errors [0] = '\0'; }

char * Melder_getError (void) { return & errors [0]; }

void Melder_flushError (const char *format, ...) {
	va_list arg;
	va_start (arg, format);
	if (format) {
		vsprintf (Melder_buffer1, format, arg);
		Longchar_nativize (Melder_buffer1, Melder_buffer2, ! Melder_batch);
		appendError (Melder_buffer2);
	}
	theMelder. error (errors);
	Melder_clearError ();
	va_end (arg);
}

int Melder_fatal (const char *format, ...) {
	va_list arg;
	va_start (arg, format);
	vsprintf (Melder_buffer1, format, arg);
	Longchar_nativize (Melder_buffer1, Melder_buffer2, ! Melder_batch);
	theMelder. fatal (Melder_buffer2);
	va_end (arg);
	abort ();
	return 0;   /* Make some compilers happy, some unhappy. */
}

int _Melder_assert (const char *condition, const char *fileName, int lineNumber) {
	return Melder_fatal ("Assertion failed in file \"%s\" at line %d:\n   %s\n",
		fileName, lineNumber, condition);
}

#ifndef CONSOLE_APPLICATION
static Widget makeMessage (unsigned char dialogType, const char *resourceName, const char *title) {
	Widget dialog = XmCreateMessageDialog (Melder_topShell, MOTIF_CONST_CHAR_ARG (resourceName), NULL, 0);
	XtVaSetValues (dialog,
		XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL,
		XmNdialogType, dialogType,
		XmNautoUnmanage, True,
		NULL);
	XtVaSetValues (XtParent (dialog), XmNtitle, title, XmNdeleteResponse, XmUNMAP, NULL);
	XtUnmanageChild (XmMessageBoxGetChild (dialog, XmDIALOG_CANCEL_BUTTON));
	XtUnmanageChild (XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON));
	return dialog;
}

static int pause_continued, pause_stopped;
MOTIF_CALLBACK (pause_continue_cb) pause_continued = 1; MOTIF_CALLBACK_END
MOTIF_CALLBACK (pause_stop_cb) pause_stopped = 1; MOTIF_CALLBACK_END
static int motif_pause (char *message) {
	static Widget dia = NULL, continueButton = NULL, stopButton = NULL, rc, buttons, text;
	if (dia == NULL) {
		dia = XmCreateFormDialog (Melder_topShell, "melderPause", NULL, 0);
		XtVaSetValues (XtParent (dia),
			XmNtitle, "Pause",
			XmNdeleteResponse, XmDO_NOTHING,
			NULL);
		XtVaSetValues (dia, XmNautoUnmanage, True, NULL);
		rc = XmCreateRowColumn (dia, "rc", NULL, 0);
		text = XmCreateLabel (rc, "text", NULL, 0);
		XtVaSetValues (text, XmNwidth, 400, 0);
		XtManageChild (text);
		buttons = XmCreateRowColumn (rc, "rc", NULL, 0);
		XtVaSetValues (buttons, XmNorientation, XmHORIZONTAL, 0);
		continueButton = XmCreatePushButton (buttons, "Continue", NULL, 0);
		XtVaSetValues (continueButton, XmNx, 10, XmNwidth, 300, 0);
		XtAddCallback (continueButton, XmNactivateCallback, pause_continue_cb, (XtPointer) dia);
		XtManageChild (continueButton);
		stopButton = XmCreatePushButton (buttons, "Stop", NULL, 0);
		XtVaSetValues (stopButton, XmNx, 320, XmNwidth, 60, 0);
		XtAddCallback (stopButton, XmNactivateCallback, pause_stop_cb, (XtPointer) dia);
		XtManageChild (stopButton);
		XtManageChild (buttons);
		XtManageChild (rc);
	}
	if (! message) message = "";
	XtVaSetValues (text, motif_argXmString (XmNlabelString, message), 0);
	XtManageChild (dia);
	pause_continued = pause_stopped = FALSE;
	do {
		XEvent event;
		XtAppNextEvent (Melder_appContext, & event);
		XtDispatchEvent (& event);
	} while (! pause_continued && ! pause_stopped);
	XtUnmanageChild (dia);
	return pause_continued;
}

static Widget shell, dialog, text;
MOTIF_CALLBACK (cb_destroy)
	dialog = NULL;
MOTIF_CALLBACK_END
static void makeInfo (void) {
	int screenWidth = WidthOfScreen (DefaultScreenOfDisplay (XtDisplay (Melder_topShell)));
	int screenHeight = HeightOfScreen (DefaultScreenOfDisplay (XtDisplay (Melder_topShell)));
	Dimension width, height;
	Arg arg [4];
	#if defined (macintosh) || defined (_WIN32)
		shell = XmCreateShell (Melder_topShell, "Praat", NULL, 0);
		XtVaSetValues (shell, XmNwidth, 580, XmNheight, 440, NULL);
	#else
		shell = XtVaCreateWidget ("info", topLevelShellWidgetClass, Melder_topShell, 0);
	#endif
	XtVaSetValues (shell, XmNdeleteResponse, XmDESTROY, NULL);
	XtAddCallback (shell, XmNdestroyCallback, cb_destroy, NULL);
	dialog = XtVaCreateWidget ("information", xmFormWidgetClass, shell,
		XmNautoUnmanage, False, XmNdialogStyle, XmDIALOG_MODELESS, 0);
	arg [0]. name = XmNscrollingPolicy; arg [0]. value = XmAUTOMATIC;
	arg [1]. name = XmNscrollBarDisplayPolicy; arg [1]. value = XmAS_NEEDED;
	arg [2]. name = XmNeditable; arg [2]. value = True;
	arg [3]. name = XmNeditMode; arg [3]. value = XmMULTI_LINE_EDIT;   /* On Linux, this must go before creation. */
	text = XmCreateScrolledText (dialog, "text", arg, 4);
	XtVaSetValues (XtParent (text), XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM,
		XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, NULL);
	XtVaSetValues (text, XmNeditable, False, XmNeditMode, XmMULTI_LINE_EDIT,
		XmNrows, 33, XmNcolumns, 90, NULL);
	XtManageChild (text);

	screenHeight -= Machine_getTitleBarHeight ();
	XtVaGetValues (dialog, XmNwidth, & width, XmNheight, & height, NULL);
	XtVaSetValues (XtParent (dialog), XmNx, (screenWidth - width) / 2, XmNy, (screenHeight - height) / 3, NULL);
	XtManageChild (dialog);
	XtRealizeWidget (shell);
}

static void motif_information (char *message) {
	if (! dialog) makeInfo ();
	XtVaSetValues (shell, XmNiconName, "Info", XmNtitle, "Info", 0);
	XmTextSetString (text, message);
	XMapRaised (XtDisplay (shell), XtWindow (shell));
	XmUpdateDisplay (shell);
}

static void motif_warning (char *message) {
#ifdef _WIN32
	MessageBox (NULL, message, "Warning", MB_OK);
#else
	static Widget dia = NULL;
	if (dia == NULL)
		dia = makeMessage (XmDIALOG_WARNING, "warning", "Warning");
	XtVaSetValues (dia, motif_argXmString (XmNmessageString, message), NULL);
	XtManageChild (dia);
#endif
}

#ifdef macintosh
static void motif_fatal (char *message)
{
	Str255 pmessage;
	int length, i;
	message [255] = '\0';
	length = strlen (message);
	pmessage [0] = length;
	strcpy ((char *) pmessage + 1, message);
	for (i = 1; i <= length; i ++) if (pmessage [i] == '\n') pmessage [i] = '\r';
	ParamText (pmessage, "\p", "\p", "\p");
	Alert (129, NULL);
	SysError (11);
}
static void motif_error (char *message) {
	Str255 pmessage;
	int length, i;
	length = strlen (message);
	if (length == 0) return;
	if (length > 255) message [length = 255] = '\0';
	pmessage [0] = length;
	strncpy ((char *) pmessage + 1, message, 255);   /* Not strcpy! */
	for (i = 1; i <= length; i ++) if (pmessage [i] == '\n') pmessage [i] = '\r';
	ParamText (pmessage, "\p", "\p", "\p");
	Alert (130, NULL);
	XmUpdateDisplay (0);
}
#elif defined (_WIN32)
static void motif_fatal (char *message) {
	MessageBox (NULL, message, "Fatal error", MB_OK);
}
static void motif_error (char *message) {
	MessageBox (NULL, message, "Message", MB_OK);
}
#else
static void motif_error (char *message) {
	static Widget dia = NULL;
	if (dia == NULL)
		dia = makeMessage (XmDIALOG_ERROR, "error", "Message");
	XtVaSetValues (dia, motif_argXmString (XmNmessageString, message), NULL);
	XtManageChild (dia);
}
#endif

void MelderMotif_create (void *appContext, void *parent) {
	Melder_appContext = appContext;
	Melder_topShell = (Widget) parent;
	Melder_setInformationProc (motif_information);
	Melder_setWarningProc (motif_warning);
	Melder_setErrorProc (motif_error);
	#if defined (macintosh) || defined (_WIN32)
		Melder_setFatalProc (motif_fatal);
	#endif
	Melder_setPauseProc (motif_pause);
}

#ifdef macintosh
	/* Divert the printf in Melder_casual. */
	#ifndef __CONSOLE__
	#include <console.h>
	#endif
	short InstallConsole (short fd) { (void) fd; return 0; }
	void RemoveConsole (void) { }
	long WriteCharsToConsole (char *buffer, long n) {
		static char bufferWithZero [20000];
		strncpy (bufferWithZero, buffer, n);
		bufferWithZero [n] = '\0';
		Melder_information ("%s", bufferWithZero);
		return n;
	}
	long ReadCharsFromConsole (char *buffer, long n) { (void) buffer; (void) n; return 0; }
	extern char * __ttyname (long fd) { return fd >= 0 && fd <= 2 ? "null device" : NULL; }
#endif
#endif

int Melder_publish (void *anything) {
	return theMelder. publish (anything);
}

int Melder_record (double duration) {
	return theMelder. record (duration);
}

int Melder_recordFromFile (MelderFile fs) {
	return theMelder. recordFromFile (fs);
}

void Melder_play (void) {
	theMelder. play ();
}

void Melder_playReverse (void) {
	theMelder. playReverse ();
}

int Melder_publishPlayed (void) {
	return theMelder. publishPlayed ();
}

/********** Procedures to override message methods (e.g., to enforce interactive behaviour). **********/

void Melder_setPauseProc (int (*pause) (char *))
	{ theMelder. pause = pause ? pause : defaultPause; }

void Melder_setInformationProc (void (*information) (char *))
	{ theMelder. information = information ? information : defaultInformation; }

void Melder_setHelpProc (void (*help) (const char *query))
	{ theMelder. help = help ? help : defaultHelp; }

void Melder_setSearchProc (void (*search) (void))
	{ theMelder. search = search ? search : defaultSearch; }

void Melder_setWarningProc (void (*warning) (char *))
	{ theMelder. warning = warning ? warning : defaultWarning; }

void Melder_setErrorProc (void (*error) (char *))
	{ theMelder. error = error ? error : defaultError; }

void Melder_setFatalProc (void (*fatal) (char *))
	{ theMelder. fatal = fatal ? fatal : defaultFatal; }

void Melder_setPublishProc (int (*publish) (void *))
	{ theMelder. publish = publish ? publish : defaultPublish; }

void Melder_setRecordProc (int (*record) (double))
	{ theMelder. record = record ? record : defaultRecord; }

void Melder_setRecordFromFileProc (int (*recordFromFile) (MelderFile))
	{ theMelder. recordFromFile = recordFromFile ? recordFromFile : defaultRecordFromFile; }

void Melder_setPlayProc (void (*play) (void))
	{ theMelder. play = play ? play : defaultPlay; }

void Melder_setPlayReverseProc (void (*playReverse) (void))
	{ theMelder. playReverse = playReverse ? playReverse : defaultPlayReverse; }

void Melder_setPublishPlayedProc (int (*publishPlayed) (void))
	{ theMelder. publishPlayed = publishPlayed ? publishPlayed : defaultPublishPlayed; }

/********** Memory allocations. **********/

static long totalNumberOfAllocations = 0, totalNumberOfDeallocations = 0, totalAllocationSize;

#define TRACE_MALLOC  0

void * Melder_malloc (long size) {
	void *result;
	if (size <= 0)
		return Melder_errorp ("(Melder_malloc:) Can never allocate %ld bytes.", size);
	result = malloc (size);
	if (result == NULL)
		return Melder_errorp ("(Melder_malloc:) Out of memory. Could not allocate %ld bytes.", size);
	totalNumberOfAllocations += 1;
	totalAllocationSize += size;
	#if TRACE_MALLOC
		Melder_casual ("malloc %ld", size);
	#endif
	return result;
}

void _Melder_free (void **ptr) {
	if (*ptr == NULL) return;
	free (*ptr);
	*ptr = NULL;
	totalNumberOfDeallocations += 1;
	#if TRACE_MALLOC
		Melder_casual ("free");
	#endif
}

void * Melder_realloc (void *ptr, long size) {
	void *result;
	if (size <= 0)
		return Melder_errorp ("(Melder_realloc:) Can never allocate %ld bytes.", size);
	result = realloc (ptr, size);   /* Will not show in the statistics... */
	if (result == NULL)
		return Melder_errorp ("(Melder_realloc:) "
			"Out of memory. Could not allocate %ld bytes.", size);
	if (ptr == NULL) {   /* Is it like malloc? */
		totalNumberOfAllocations += 1;
		totalAllocationSize += size;
	} else if (result != ptr) {   /* Did realloc do a malloc-and-free? */
		totalNumberOfAllocations += 1;
		totalAllocationSize += size;
		totalNumberOfDeallocations += 1;
	}
	#if TRACE_MALLOC
		Melder_casual ("realloc %ld", size);
	#endif
	return result;
}

void * Melder_calloc (long nelem, long elsize) {
	void *result;
	if (nelem <= 0)
		return Melder_errorp ("(Melder_calloc:) "
			"Can never allocate %ld elements.", nelem);
	if (elsize <= 0)
		return Melder_errorp ("(Melder_calloc:) "
			"Can never allocate elements whose size is %ld bytes.", elsize);
	result = calloc (nelem, elsize);
	if (result == NULL)
		return Melder_errorp ("(Melder_calloc:) Out of memory. "
			"Could not allocate %ld elements whose sizes are %ld bytes.", nelem, elsize);
	totalNumberOfAllocations += 1;
	totalAllocationSize += nelem * elsize;
	#if TRACE_MALLOC
		Melder_casual ("calloc %ld %ld", nelem, elsize);
	#endif
	return result;
}

char * Melder_strdup (const char *string) {
	char *result;
	long size;
	if (! string) return NULL;
	size = strlen (string) + 1;
	result = malloc (size);
	if (result == NULL)
		return Melder_errorp ("(Melder_strdup:) Out of memory. Could not allocate a string of length %ld.", size - 1);
	strcpy (result, string);
	totalNumberOfAllocations += 1;
	totalAllocationSize += size;
	#if TRACE_MALLOC
		Melder_casual ("strdup %ld", size);
	#endif
	return result;
}

long Melder_allocationCount (void) {
	return totalNumberOfAllocations;
}

long Melder_deallocationCount (void) {
	return totalNumberOfDeallocations;
}

unsigned long Melder_allocationSize (void) {
	return totalAllocationSize;
}

long Melder_killReturns_inline (char *text) {
	const char *from;
	char *to;
	for (from = text, to = text; *from != '\0'; from ++, to ++) {
		if (*from == 13) {   /* Carriage return? */
			if (from [1] == '\n') {   /* Followed by linefeed? Must be a Windows text. */
				from ++;   /* Ignore carriage return. */
				*to = '\n';   /* Copy linefeed. */
			} else {   /* Bare carriage return? Must be a Macintosh text. */
				*to = '\n';   /* Change to linefeed. */
			}
		} else {
			*to = *from;
		}
	}
	*to = '\0';   /* Closing null byte. */
	return to - text;
}

#if 0
/********** NEWLINE CONVERSION ROUTINES **********/

char * Melder_linefeedsToWin (const char *text);
/*
	 Replaces all bare linefeeds (generic = Unix) or bare returns (Mac) with return / linefeed sequences (Win).
	 Remove with Melder_free.
*/
void Melder_linefeedsUnixToMac_inline (char *text);
/*
	 Replaces all bare linefeeds (generic = Unix) with bare returns (Mac).
	 Lengths of new and old strings are equal.
*/
long Melder_linefeedsToMac_inline (char *text);
/*
	 Replaces all bare linefeeds (generic = Unix) or return / linefeed sequences (Win) with bare returns (Mac).
	 Returns new length of string (equal to or less than old length).
*/

char * Melder_linefeedsToWin (const char *text) {
	const char *from;
	char *result = Melder_malloc (2 * strlen (text) + 1), *to;   /* All new lines plus one null byte. */
	if (! result) return NULL;
	for (from = text, to = result; *from != '\0'; from ++, to ++) {
		if (*from == 13) {   /* Carriage return? */
			*to = 13;   /* Copy carriage return. */
			if (from [1] == '\n') {   /* Followed by linefeed? Must be a Windows text. */
				from ++, to ++;
				*to = '\n';   /* Copy linefeed. */
			} else {   /* Bare carriage return? Must be a Macintosh text. */
				* ++ to = '\n';   /* Insert linefeed. */
			}
		} else if (*from == '\n') {   /* Bare linefeed? Must be generic (Unix). */
			*to = 13;   /* Insert carriage return. */
			* ++ to = '\n';   /* Copy linefeed. */
		} else {
			*to = *from;
		}
	}
	*to = '\0';
	return result;
}

void Melder_linefeedsUnixToMac_inline (char *text) {
	char *p;
	for (p = text; *p != '\0'; p ++)
		if (*p == '\n')   /* Linefeed? */
			*p = 13;   /* Change to carriage return. */
}

long Melder_linefeedsToMac_inline (char *text) {
	const char *from;
	char *to;
	for (from = text, to = text; *from != '\0'; from ++, to ++) {
		if (*from == 13) {   /* Carriage return? */
			if (from [1] == '\n') {   /* Followed by linefeed? Must be a Windows text. */
				*to = 13;   /* Copy carriage return. */
				from ++;   /* Ignore linefeed. */
			} else {   /* Bare carriage return? Must be a Macintosh text. */
				*to = 13;   /* Copy carriage return. */
			}
		} else if (*from == '\n') {   /* Bare linefeed? Must be generic (Unix). */
			*to = 13;   /* Change to carriage return. */
		} else {
			*to = *from;
		}
	}
	*to = '\0';   /* Closing null byte. */
	return to - text;
}
#endif

/* End of file melder.c */
