/* TextTierEditor.c */
/* Paul Boersma, March 8, 2001 */

#include "Sound.h"
#include "TextTierEditor.h"
#include "EditorM.h"

#define TextTierEditor_members FunctionEditor_members \
	long selectedMark; \
	Widget publishButton; \
	Widget moveMarkToCursorButton, moveMarkToBButton, moveMarkToEButton; \
	Widget moveMarkToZeroButton, editMarkTextButton; \
	Widget addMarkAtCursorButton, addMarkAtBButton, addMarkAtEButton; \
	Widget removeSelectedMarkButton, removeSelectedMarksButton;
#define TextTierEditor_methods FunctionEditor_methods
class_create_opaque (TextTierEditor, FunctionEditor)

static void updateMenus (TextTierEditor me) {
	Editor_setMenuSensitive (me, "Mark", my selectedMark > 0);
	XtSetSensitive (my moveMarkToCursorButton,
		my selectedMark > 0 && my cursor >= my startWindow && my cursor <= my endWindow);
	XtSetSensitive (my addMarkAtCursorButton,
		my cursor >= my startWindow && my cursor <= my endWindow);
	XtSetSensitive (my addMarkAtBButton,
		my startSelection >= my startWindow && my startSelection <= my endWindow);
	XtSetSensitive (my addMarkAtEButton,
		my endSelection >= my startWindow && my endSelection <= my endWindow);
	XtSetSensitive (my editMarkTextButton, my selectedMark > 0);
	XtSetSensitive (my removeSelectedMarksButton, my endSelection > my startSelection);
	XtSetSensitive (my removeSelectedMarkButton,
		my selectedMark > 0 && my cursor >= my startWindow && my cursor <= my endWindow);
	if (my publishButton)
		XtSetSensitive (my publishButton, my endSelection > my startSelection);
}

/********** MENU COMMANDS **********/

FORM_WRITE (TextTierEditor, cb_writeToTextFile, "Write to TextTier text file", 0);
	sprintf (defaultName, "%s.TextTier", ((TextTier) my data) -> name);
DO_WRITE
	if (! Data_writeToTextFile (my data, file)) return 0;
END

DIRECT (TextTierEditor, cb_publish)
	Sound sound = my sound.data, publish;
	long first, last, numberOfSamples;
	numberOfSamples = Sampled_getWindowSamples (sound, my startSelection, my endSelection, & first, & last);
	if (numberOfSamples &&
		(publish = Sound_create (0.0, numberOfSamples * sound -> dx,
			numberOfSamples, sound -> dx, 0.5 * sound -> dx)) != NULL)
	{
		NUMfvector_copyElements (sound -> z [1], publish -> z [1] - first + 1, first, last);
		if (my publishCallback)
			my publishCallback (me, my publishClosure, publish);
	}
END

DIRECT (TextTierEditor, cb_removeSelectedMark)
	TextTier tier = my data;
	if (my selectedMark < 1 || my selectedMark > tier -> points -> size) return 0;
	Editor_save (me, "Remove mark");
	Collection_removeItem (tier -> points, my selectedMark);
	my selectedMark = 0;
	FunctionEditor_redraw (me);
	Editor_broadcastChange (me);
END

DIRECT (TextTierEditor, cb_removeSelectedMarks)
	TextTier tier = my data;
	Editor_save (me, "Remove marks");
	AnyTier_removePointsBetween (tier, my startSelection, my endSelection);
	my selectedMark = 0;
	FunctionEditor_redraw (me);
	Editor_broadcastChange (me);
END

DIRECT (TextTierEditor, cb_moveMarkToCursor)
	TextTier tier = my data;
	TextPoint point;
	if (my selectedMark < 1 || my selectedMark > tier -> points -> size) return 0;
	Editor_save (me, "Move mark to cursor");
	point = Data_copy (tier -> points -> item [my selectedMark]);
	point -> time = my cursor;
	Collection_removeItem (tier -> points, my selectedMark);
	Collection_addItem (tier -> points, point);
	my selectedMark = AnyTier_timeToNearestIndex (tier, my cursor);
	FunctionEditor_redraw (me);
	Editor_broadcastChange (me);
END

DIRECT (TextTierEditor, cb_moveMarkToB)
	TextTier tier = my data;
	TextPoint point;
	if (my selectedMark < 1 || my selectedMark > tier -> points -> size) return 0;
	Editor_save (me, "Move mark to B");
	point = Data_copy (tier -> points -> item [my selectedMark]);
	point -> time = my cursor = my startSelection;
	Collection_removeItem (tier -> points, my selectedMark);
	Collection_addItem (tier -> points, point);
	my selectedMark = AnyTier_timeToNearestIndex (tier, my startSelection);
	FunctionEditor_marksChanged (me);
	Editor_broadcastChange (me);
END

DIRECT (TextTierEditor, cb_moveMarkToE)
	TextTier tier = my data;
	TextPoint point;
	if (my selectedMark < 1 || my selectedMark > tier -> points -> size) return 0;
	Editor_save (me, "Move mark to E");
	point = Data_copy (tier -> points -> item [my selectedMark]);
	point -> time = my cursor = my endSelection;
	Collection_removeItem (tier -> points, my selectedMark);
	Collection_addItem (tier -> points, point);
	my selectedMark = AnyTier_timeToNearestIndex (tier, my endSelection);
	FunctionEditor_marksChanged (me);
	Editor_broadcastChange (me);
END

DIRECT (TextTierEditor, cb_moveCursorToZero)
	double zero = Sound_getNearestZeroCrossing (my sound.data, my cursor);   /* INCORRECT ? */
	if (NUMdefined (zero)) {
		my cursor = zero;
		FunctionEditor_marksChanged (me);
	}
END
DIRECT (TextTierEditor, cb_moveBtoZero)
	double zero = Sound_getNearestZeroCrossing (my sound.data, my startSelection);
	if (NUMdefined (zero)) {
		my startSelection = zero;
		FunctionEditor_marksChanged (me);
	}
END
DIRECT (TextTierEditor, cb_moveEtoZero)
	double zero = Sound_getNearestZeroCrossing (my sound.data, my endSelection);
	if (NUMdefined (zero)) {
		my endSelection = zero;
		FunctionEditor_marksChanged (me);
	}
END
DIRECT (TextTierEditor, cb_moveMarkToZero)
	double position;
	TextTier tier = my data;
	TextPoint point;
	if (my selectedMark < 1 || my selectedMark > tier -> points -> size) return 0;
	point = tier -> points -> item [my selectedMark];
	position = Sound_getNearestZeroCrossing (my sound.data, point -> time);
	if (NUMdefined (position)) {
		if (position < tier -> xmin) position = tier -> xmin;
		if (position > tier -> xmax) position = tier -> xmax;

		Editor_save (me, "Move boundary to zero crossing");

		point = Data_copy (point);
		point -> time = my cursor = position;
		Collection_removeItem (tier -> points, my selectedMark);
		Collection_addItem (tier -> points, point);
		my selectedMark = AnyTier_timeToNearestIndex (tier, position);
		FunctionEditor_marksChanged (me);
		Editor_broadcastChange (me);
	}
END

FORM (TextTierEditor, cb_editMarkText, "Edit mark text", 0)
	TEXTFIELD ("markText", "")
	OK
TextTier tier = my data;
TextPoint point;
if (my selectedMark < 1 || my selectedMark > tier -> points -> size) return 0;
point = tier -> points -> item [my selectedMark];
SET_STRING ("markText", point -> mark);
DO
	char *newMarkText = GET_STRING ("markText");
	TextTier tier = my data;
	TextPoint point;
	Melder_assert (my selectedMark >= 1 && my selectedMark <= tier -> points -> size);

	Editor_save (me, "Edit mark text");

	point = tier -> points -> item [my selectedMark];
	if (strspn (newMarkText, " \n\t") == strlen (newMarkText)) {   /* No visible characters? */
		if (point -> mark) point -> mark [0] = '\0';
 	} else {
		Melder_free (point -> mark);
		point -> mark = Melder_strdup (newMarkText);
	}
	FunctionEditor_redraw (me);
	Editor_broadcastChange (me);
END

static void addMarkAt (TextTierEditor me, double position) {
	TextTier tier = my data;
	TextPoint point = TextPoint_create (position, "");
	Editor_save (me, "Add mark");
	Collection_addItem (tier -> points, point);
	my selectedMark = AnyTier_timeToNearestIndex (tier, my cursor = position);
	FunctionEditor_marksChanged (me);
	Editor_broadcastChange (me);
}
DIRECT (TextTierEditor, cb_addMarkAtCursor) addMarkAt (me, my cursor); END
DIRECT (TextTierEditor, cb_addMarkAtB) addMarkAt (me, my startSelection); END
DIRECT (TextTierEditor, cb_addMarkAtE) addMarkAt (me, my endSelection); END

FORM (TextTierEditor, cb_addMarkAt, "Add mark", 0)
	REAL ("Time (s)", "0.0");
	LABEL ("", "Mark:");
	TEXTFIELD ("Mark", "");
	OK
SET_REAL ("Time", my cursor)
DO
	TextTier tier = my data;
	TextPoint point;
	double time = GET_REAL ("Time");
	if (time < tier -> xmin || time > tier -> xmax)
		return Melder_error ("\"Time\" must be between %.17g and %.17g.", tier -> xmin, tier -> xmax);
	point = TextPoint_create (time, GET_STRING ("Mark"));
	Editor_save (me, "Add mark");
	Collection_addItem (tier -> points, point);
	my selectedMark = AnyTier_timeToNearestIndex (tier, my cursor = point -> time);
	FunctionEditor_marksChanged (me);
	Editor_broadcastChange (me);
END

DIRECT (TextTierEditor, cb_TextTierEditorHelp) Melder_help ("TextTierEditor"); END
DIRECT (TextTierEditor, cb_TextTierHelp) Melder_help ("TextTier"); END
DIRECT (TextTierEditor, cb_AboutSpecialSymbols) Melder_help ("Special symbols"); END
DIRECT (TextTierEditor, cb_PhoneticSymbolChart) Melder_help ("Phonetic symbol chart"); END
DIRECT (TextTierEditor, cb_AboutTextStyles) Melder_help ("Text styles"); END

static void createMenus (I) {
	iam (TextTierEditor);
	Sound sound = my sound.data;
	inherited (TextTierEditor) createMenus (me);

	Editor_addCommand (me, "File", "Write TextTier to text file...", 0, cb_writeToTextFile);
	if (sound)
		my publishButton = Editor_addCommand (me, "File", "Publish Sound selection", 0, cb_publish);
	Editor_addCommand (me, "File", "-- close --", 0, NULL);

	Editor_addCommand (me, "Edit", "-- edit text --", 0, NULL);
	my editMarkTextButton = Editor_addCommand (me, "Edit", "Edit mark text...", 0, cb_editMarkText);

	if (sound) {
		Editor_addCommand (me, "C", "-- zero cursor --", 0, 0);
		Editor_addCommand (me, "C", "Move cursor to nearest zero crossing", 0, cb_moveCursorToZero);
		Editor_addCommand (me, "B", "-- zero B --", 0, 0);
		Editor_addCommand (me, "B", "Move B to nearest zero crossing", 0, cb_moveBtoZero);
		Editor_addCommand (me, "E", "-- zero E --", 0, 0);
		Editor_addCommand (me, "E", "Move E to nearest zero crossing", 0, cb_moveEtoZero);
	}

	Editor_addMenu (me, "Mark", 0);
	my moveMarkToCursorButton = Editor_addCommand (me, "Mark", "Move mark to cursor", 0, cb_moveMarkToCursor);
	my moveMarkToBButton = Editor_addCommand (me, "Mark", "Move mark to B", 0, cb_moveMarkToB);
	my moveMarkToEButton = Editor_addCommand (me, "Mark", "Move mark to E", 0, cb_moveMarkToE);
	if (sound)
		my moveMarkToZeroButton = Editor_addCommand (me, "Mark", "Move mark to nearest zero crossing", 0, cb_moveMarkToZero);

	Editor_addMenu (me, "Add", 0);
	my addMarkAtCursorButton = Editor_addCommand (me, "Add", "Add mark at cursor", 0, cb_addMarkAtCursor);
	my addMarkAtBButton = Editor_addCommand (me, "Add", "Add mark at B", 0, cb_addMarkAtB);
	my addMarkAtEButton = Editor_addCommand (me, "Add", "Add mark at E", 0, cb_addMarkAtE);
	Editor_addCommand (me, "Add", "Add mark at...", 0, cb_addMarkAt);

	Editor_addMenu (me, "Remove", 0);
	my removeSelectedMarkButton = Editor_addCommand (me, "Remove", "Remove selected mark", 0, cb_removeSelectedMark);
	my removeSelectedMarksButton = Editor_addCommand (me, "Remove", "Remove all marks in selection", 0, cb_removeSelectedMarks);

	Editor_addCommand (me, "Help", "TextTierEditor help", '?', cb_TextTierEditorHelp);
	Editor_addCommand (me, "Help", "TextTier help", 0, cb_TextTierHelp);
	Editor_addCommand (me, "Help", "About special symbols", 0, cb_AboutSpecialSymbols);
	Editor_addCommand (me, "Help", "Phonetic symbol chart", 0, cb_PhoneticSymbolChart);
	Editor_addCommand (me, "Help", "About text styles", 0, cb_AboutTextStyles);
}

/********** DRAWING AREA **********/

static int TextTierEditor_labelLength (const char *string) {
	int result = 0;
	char c;
	while ((c = *string) != '\0') {
		if (c == '^' || c == '_' || c == '%' || c == '#') { string ++; continue; }
		else if (c == '\\' && string [1] && string [2]) string += 3;
		else string ++;
		result ++;
	}
	return result;
}

static int TextTierEditor_labelPosition (const char *string, int position) {
	int target = 0;
	const char *start = string;
	char c;
	while ((c = *string) != '\0') {
		if (c == '^' || c == '_' || c == '%' || c == '#') { string ++; continue; }
		else if (c == '\\' && string [1] && string [2]) string += 3;
		else string ++;
		target ++;
		if (target == position) break;
	}
	return string - start;
}

static void draw (I) {
	iam (TextTierEditor);
	TextTier tier = my data;
	Sound sound = my sound.data;
	Graphics_Viewport vp;
	long first, last;
	int oldFont = Graphics_inqFont (my graphics);
	int oldFontSize = Graphics_inqFontSize (my graphics);
	short x1DC, x2DC, yDC;
	double soundY = 0.5;
	int imark, nmarks = tier -> points -> size;

	Graphics_setFont (my graphics, Graphics_TIMES);
	Graphics_setFontSize (my graphics, 18);
	Graphics_setColour (my graphics, Graphics_BLACK);

	if (sound) {
		vp = Graphics_insetViewport (my graphics, 0.0, 1.0, soundY, 1.0);
		if (Sampled_getWindowSamples (sound, my startWindow, my endWindow, & first, & last) > 1) {
			Graphics_setWindow (my graphics, my startWindow, my endWindow, -1.0, 1.0);
			Graphics_setLineType (my graphics, Graphics_DOTTED);
			Graphics_line (my graphics, my startWindow, 0.0, my endWindow, 0.0);
			Graphics_setLineType (my graphics, Graphics_DRAWN);      
			Graphics_function (my graphics, sound -> z [1], first, last,
				Sampled_indexToX (sound, first), Sampled_indexToX (sound, last));
		}
		Graphics_resetViewport (my graphics, vp);
	}
	Graphics_setWindow (my graphics, my startWindow, my endWindow, 0.0, 1.0);
	Graphics_WCtoDC (my graphics, my startWindow, 0.0, & x1DC, & yDC);
	Graphics_WCtoDC (my graphics, my endWindow, 0.0, & x2DC, & yDC);
	if (sound)
		Graphics_line (my graphics, my startWindow, soundY, my endWindow, soundY);
	Graphics_setTextAlignment (my graphics, Graphics_CENTER, Graphics_HALF);
	for (imark = 1; imark <= nmarks; imark ++) {
		TextPoint point = tier -> points -> item [imark];
		double t = point -> time, textY = 0.4 - 0.1 * ((imark - 1) % 4);
		if (t >= my startWindow && t <= my endWindow) {
			Graphics_setColour (my graphics, Graphics_BLUE);
			Graphics_setLineWidth (my graphics, 3.0);
			Graphics_line (my graphics, t, 0.0, t, textY - 0.05);
			Graphics_line (my graphics, t, textY + 0.05, t, 0.5);
			Graphics_setLineWidth (my graphics, 1.0);
			Graphics_setLineType (my graphics, Graphics_DOTTED);
			Graphics_line (my graphics, t, 0.5, t, 1.0);
			Graphics_setLineType (my graphics, Graphics_DRAWN);      
			if (imark == my selectedMark) {
				Graphics_setColour (my graphics, Graphics_RED);
				Graphics_fillCircle_mm (my graphics, t, 0.5, 4.0);
			}
			Graphics_setColour (my graphics, imark == my selectedMark ? Graphics_RED : Graphics_BLACK);
			if (point -> mark) Graphics_text (my graphics, t, textY, point -> mark);
			Graphics_setColour (my graphics, Graphics_BLACK);
		}
	}
	Graphics_setColour (my graphics, Graphics_BLACK);
	Graphics_setFont (my graphics, oldFont);
	Graphics_setFontSize (my graphics, oldFontSize);
	updateMenus (me);
}

static void drawWhileDragging (Graphics g, double x, double textY, const char *text) {
	int oldFont = Graphics_inqFont (g);
	int oldFontSize = Graphics_inqFontSize (g);
	Graphics_setFont (g, Graphics_TIMES);
	Graphics_setFontSize (g, 18);
	Graphics_fillCircle_mm (g, x, 0.5, 4.0);
	Graphics_setLineWidth (g, 3.0);
	Graphics_line (g, x, 0.0, x, textY - 0.05);
	Graphics_setTextAlignment (g, Graphics_CENTER, Graphics_HALF);
	if (text) Graphics_text (g, x, textY, text);
	Graphics_line (g, x, textY + 0.05, x, 0.5);
	Graphics_setLineWidth (g, 1.0);
	Graphics_setLineType (g, Graphics_DOTTED);
	Graphics_line (g, x, 0.5, x, 1.0);
	Graphics_setLineType (g, Graphics_DRAWN);      
	Graphics_setFont (g, oldFont);
	Graphics_setFontSize (g, oldFontSize);
	Graphics_setTextAlignment (g, Graphics_CENTER, Graphics_BOTTOM);
	Graphics_printf (g, x, 1.01, "%f", x);
}

static void dragBoundary (TextTierEditor me) {
	TextTier tier = my data;
	double xWC = my cursor, yWC, textY = 0.4 - 0.1 * ((my selectedMark - 1) % 4);
	TextPoint point = tier -> points -> item [my selectedMark];

	Graphics_xorOn (my graphics, Graphics_MAGENTA);
	while (Graphics_mouseStillDown (my graphics)) {
		double xWC_old = xWC;
		drawWhileDragging (my graphics, xWC, textY, point -> mark);
		Graphics_getMouseLocation (my graphics, & xWC, & yWC);
		drawWhileDragging (my graphics, xWC_old, textY, point -> mark);
	}
	Graphics_xorOff (my graphics);

	if (xWC <= my startWindow || xWC >= my endWindow) {   /* Dragged outside window? */
		(void) 0;   /* Ignore dragging. */
	} else {
		TextPoint point = Data_copy (tier -> points -> item [my selectedMark]);

		Editor_save (me, "Drag mark");

		my cursor = point -> time = xWC;
		Collection_removeItem (tier -> points, my selectedMark);
		Collection_addItem (tier -> points, point);
		my selectedMark = AnyTier_timeToNearestIndex (tier, xWC);
		FunctionEditor_marksChanged (me);
		Editor_broadcastChange (me);
	}
}

static int click (I, double xWC, double yWC, int shiftKeyPressed) {
	iam (TextTierEditor);
	TextTier tier = my data;
	TextPoint point;
	Sound sound = my sound.data;
	double soundY = sound ? 0.5 : 1.0;
	long imark;

	imark = AnyTier_timeToNearestIndex (tier, xWC);
	point = tier -> points -> item [imark];
	if (imark && fabs (Graphics_dxWCtoMM (my graphics, xWC - point -> time)) < 2.0) {   /* Clicked near a point? */
		if (imark == my selectedMark && Graphics_distanceWCtoMM (my graphics, xWC, yWC, point -> time, 0.5) < 2.0) {
			dragBoundary (me);
		} else if (yWC < soundY) {
			my cursor = point -> time;
			my selectedMark = imark;
			return 1;
		} else {
			return inherited (TextTierEditor) click (me, xWC, yWC, shiftKeyPressed);
		}
	} else if (yWC < soundY && fabs (Graphics_dxWCtoMM (my graphics, xWC - my cursor)) < 2.0) {   /* Clicked near cursor? */
		point = TextPoint_create (my cursor, "");

		Editor_save (me, "Insert mark");

		Collection_addItem (tier -> points, point);
		my selectedMark = AnyTier_timeToNearestIndex (tier, my cursor);
		Editor_broadcastChange (me);
		return 1;
	} else {
		return inherited (TextTierEditor) click (me, xWC, yWC, shiftKeyPressed);
	}
	return 0;   /* No update needed. */
}

static void key (I, unsigned char theKey) {
	iam (TextTierEditor);
	TextTier tier = my data;
	TextPoint point;
	char newName [1000];
	int length;
	if (my selectedMark < 1 || my selectedMark > tier -> points -> size) return;
	point = tier -> points -> item [my selectedMark];
	if (point -> mark) strcpy (newName, point -> mark); else newName [0] = '\0';
	length = strlen (newName);
	if (theKey == 8 || theKey == 127) {
		char *end = newName, *last = newName - 1, *previous = newName - 1;
		for (end = newName; *end != '\0'; end ++) {
			previous = last;
			if (*end != '\\') last = end;
			else if (* ++ end == '\0' || * ++ end == '\0') break;
			else last = end;
		}
		previous [1] = '\0';

	} else if (theKey == 27) {
		Editor_save (me, "Erase label text");
		newName [0] = '\0';
	} else {
		newName [length] = theKey;
		newName [length + 1] = '\0';
	}
	if (strspn (newName, " \n\t") == strlen (newName)) {
		newName [0] = '\0';
	}
	Melder_free (point -> mark);
	point -> mark = Melder_strdup (newName);
	FunctionEditor_redraw (me);
	Editor_broadcastChange (me);
}

static void play (I, double tmin, double tmax) {
	iam (TextTierEditor);
	Sound sound = my sound.data;
	if (sound) Sound_playPart (sound, tmin, tmax, our playCallback, me);
}

class_methods (TextTierEditor, FunctionEditor)
	class_method (createMenus)
	class_method (draw)
	class_method (click)
	class_method (key)
	class_method (play)
class_methods_end

TextTierEditor TextTierEditor_create (Widget parent, const char *title, TextTier tier, Sound sound) {
	TextTierEditor me = new (TextTierEditor);
	if (! me) return NULL;
	if (sound && ! (my sound.data = Data_copy (sound)))
		{ forget (me); return NULL; }
	if (! FunctionEditor_init (me, parent, title, tier))
		{ forget (sound); forget (me); return NULL; }
	return me;
}

/* End of file TextTierEditor.c */
