MODULE Edit; (*JG 26.11.91*)

	IMPORT Files, Display, Viewers, MenuViewers, Oberon, Fonts, Texts, TextFrames, Printer;

	CONST
		CR = 0DX; maxlen = 32;
		StandardMenu = "System.Close System.Copy System.Grow Edit.Search Edit.Store";

	VAR
		W: Texts.Writer;
		time: LONGINT;
		M: INTEGER;
		pat: ARRAY maxlen OF CHAR;
		d: ARRAY 256 OF INTEGER;

	PROCEDURE Max (i, j: LONGINT): LONGINT;
	BEGIN IF i >= j THEN RETURN i ELSE RETURN j END
	END Max;

	PROCEDURE Open*;
		VAR par: Oberon.ParList;
			T: Texts.Text; S: Texts.Scanner;
			V: Viewers.Viewer;
			X, Y: INTEGER;
			beg, end, time: LONGINT;
	BEGIN
		par := Oberon.Par;
		Texts.OpenScanner(S, par.text, par.pos); Texts.Scan(S);
		IF (S.class = Texts.Char) & (S.c = "^") OR (S.line # 0) THEN
			Oberon.GetSelection(T, beg, end, time);
			IF time >= 0 THEN Texts.OpenScanner(S, T, beg); Texts.Scan(S) END
		END;
		IF S.class = Texts.Name THEN
			Oberon.AllocateUserViewer(par.vwr.X, X, Y);
			V := MenuViewers.New(
				TextFrames.NewMenu(S.s, StandardMenu),
				TextFrames.NewText(TextFrames.Text(S.s), 0),
				TextFrames.menuH,
				X, Y)
		END
	END Open;

	PROCEDURE Show*;
		VAR par: Oberon.ParList;
			T, t: Texts.Text;
			R: Texts.Reader; S: Texts.Scanner;
			V: Viewers.Viewer;
			X, Y, n, i, j: INTEGER;
			pos, len, beg, end, time: LONGINT;
			buf: ARRAY 32 OF CHAR;
			name: ARRAY 35 OF CHAR;
			M: INTEGER;
			pat: ARRAY maxlen OF CHAR;
			d: ARRAY 256 OF INTEGER;

		PROCEDURE Forward (n: INTEGER);
			VAR m: INTEGER; j: INTEGER;
		BEGIN m := M - n;
			j := 0;
			WHILE j # m DO buf[j] := buf[n + j]; INC(j) END;
			WHILE j # M DO Texts.Read(R, buf[j]); INC(j) END
		END Forward;

	BEGIN
		par := Oberon.Par;
		Texts.OpenScanner(S, par.text, par.pos); Texts.Scan(S);
		IF (S.class = Texts.Char) & (S.c = "^") OR (S.line # 0) THEN
			Oberon.GetSelection(T, beg, end, time);
			IF time >= 0 THEN Texts.OpenScanner(S, T, beg); Texts.Scan(S) END
		END;
		IF S.class = Texts.Name THEN
		i := -1; j := 0;
		WHILE S.s[j] # 0X DO
			IF S.s[j] = "." THEN i := j END;
			name[j] := S.s[j]; j := j+1
		END;
		IF i < 0 THEN name[j] := "."; i := j END;
		name[i+1] := "M"; name[i+2] := "o"; name[i+3] := "d"; name[i+4] := 0X;
		t := TextFrames.Text(name);
		IF j > i THEN (*object name specified*)
			j := i+1; M := 0;
			WHILE (M # maxlen) & (S.s[j] # 0X) DO pat[M] := S.s[j]; j := j+1; M := M+1 END;
			j := 0;
			WHILE j # 256 DO d[j] := M; INC(j) END;
			j := 0;
			WHILE j # M - 1 DO d[ORD(pat[j])] := M - 1 - j; INC(j) END;
			pos := 0; len := t.len;
			Texts.OpenReader(R, t, pos);
			Forward(M); pos := pos + M;
			LOOP j := M;
				REPEAT DEC(j) UNTIL (j < 0) OR (buf[j] # pat[j]);
				IF (j < 0) OR (pos >= len) THEN EXIT END;
				n := d[ORD(buf[M-1])];
				Forward(n); pos := pos + n
			END
		ELSE pos := 0
		END;
		Oberon.AllocateUserViewer(par.vwr.X, X, Y);
		V := MenuViewers.New(
			TextFrames.NewMenu(name, StandardMenu),
			TextFrames.NewText(t, pos-200),
			TextFrames.menuH,
			X, Y)
		END
	END Show;

	PROCEDURE Store*;
		VAR par: Oberon.ParList;
			V: Viewers.Viewer;
			Text: TextFrames.Frame;
			T: Texts.Text; S: Texts.Scanner;
			f: Files.File;
			beg, end, time, len: LONGINT;

		PROCEDURE Backup (VAR name: ARRAY OF CHAR);
			VAR res, i: INTEGER; bak: ARRAY 32 OF CHAR;
		BEGIN i := 0;
			WHILE name[i] # 0X DO bak[i] := name[i]; INC(i) END;
			bak[i] := "."; bak[i+1] := "B"; bak[i+2] := "a"; bak[i+3] := "k"; bak[i+4] := 0X;
			Files.Rename(name, bak, res)
		END Backup;
		
	BEGIN
		Texts.WriteString(W, "Edit.Store ");
		par := Oberon.Par;
		IF par.frame = par.vwr.dsc THEN
			V := par.vwr; Texts.OpenScanner(S, V.dsc(TextFrames.Frame).text, 0)
		ELSE V := Oberon.MarkedViewer(); Texts.OpenScanner(S, par.text, par.pos)
		END;
		Texts.Scan(S);
		IF (S.class = Texts.Char) & (S.c = "^") THEN
			Oberon.GetSelection(T, beg, end, time);
			IF time >= 0 THEN Texts.OpenScanner(S, T, beg); Texts.Scan(S) END
		END;
		IF (S.class = Texts.Name) & (V.dsc # NIL) & (V.dsc.next IS TextFrames.Frame) THEN
			Text := V.dsc.next(TextFrames.Frame);
			TextFrames.Mark(Text, -1);
			Texts.WriteString(W, S.s); Texts.WriteLn(W);
			Texts.Append(Oberon.Log, W.buf);
			Backup(S.s);
			f := Files.New(S.s);
			Texts.Store(Text.text, f, 0, len);
			Files.Register(f);
			TextFrames.Mark(Text, 1)
		END
	END Store;

	PROCEDURE CopyFont*;
		VAR
			T: Texts.Text; R: Texts.Reader;
			V: Viewers.Viewer; F: Display.Frame;
			beg, end: LONGINT;
			X, Y: INTEGER;
			ch: CHAR;
	BEGIN
		V := Oberon.MarkedViewer(); F := V.dsc;
		X := Oberon.Pointer.X; Y := Oberon.Pointer.Y;
		LOOP
			IF F = NIL THEN EXIT END;
			IF (X >= F.X) & (X < F.X + F.W) & (Y >= F.Y) & (Y < F.Y + F.H) THEN
				IF F IS TextFrames.Frame THEN
					WITH F: TextFrames.Frame DO
						Texts.OpenReader(R, F.text, TextFrames.Pos(F, X, Y));
						Texts.Read(R, ch);
						Oberon.GetSelection(T, beg, end, time);
						IF time >= 0 THEN Texts.ChangeLooks(T, beg, end, {0}, R.fnt, 0, 0) END
					END
				END;
				EXIT
			END;
			F := F.next
		END
	END CopyFont;

	PROCEDURE ChangeFont*;
		VAR par: Oberon.ParList;
			T: Texts.Text; S: Texts.Scanner;
			beg, end: LONGINT;
	BEGIN
		Oberon.GetSelection(T, beg, end, time);
		IF time >= 0 THEN par := Oberon.Par;
			Texts.OpenScanner(S, par.text, par.pos); Texts.Scan(S);
			IF S.class = Texts.Name THEN
				Texts.ChangeLooks(T, beg, end, {0}, Fonts.This(S.s), 0, 0)
			END
		END
	END ChangeFont;

	PROCEDURE ChangeColor*;
		VAR par: Oberon.ParList;
			T: Texts.Text; S: Texts.Scanner;
			col: SHORTINT; ch: CHAR;
			beg, end, time: LONGINT;
	BEGIN par := Oberon.Par;
		Texts.OpenScanner(S, par.text, par.pos); Texts.Scan(S);
		IF S.class = Texts.Int THEN col := SHORT(SHORT(S.i))
		ELSIF (S.class = Texts.Char) & (S.c = "^") & (par.frame(TextFrames.Frame).sel > 0) THEN
			Texts.OpenReader(S, par.text, par.frame(TextFrames.Frame).selbeg.pos);
			Texts.Read(S, ch); col := S.col
		ELSE col := Oberon.CurCol
		END ;
		Oberon.GetSelection(T, beg, end, time);
		IF time >= 0 THEN Texts.ChangeLooks(T, beg, end, {1}, NIL, col, 0) END
	END ChangeColor;

	PROCEDURE ChangeOffset*;
		VAR par: Oberon.ParList;
			T: Texts.Text; S: Texts.Scanner;
			voff: SHORTINT; ch: CHAR;
			beg, end, time: LONGINT;
	BEGIN par := Oberon.Par;
		Texts.OpenScanner(S, par.text, par.pos); Texts.Scan(S);
		IF S.class = Texts.Int THEN voff := SHORT(SHORT(S.i))
		ELSIF (S.class = Texts.Char) & (S.c = "^") & (par.frame(TextFrames.Frame).sel > 0) THEN
			Texts.OpenReader(S, par.text, par.frame(TextFrames.Frame).selbeg.pos);
			Texts.Read(S, ch); voff := S.voff
		ELSE voff := Oberon.CurOff
		END ;
		Oberon.GetSelection(T, beg, end, time);
		IF time >= 0 THEN Texts.ChangeLooks(T, beg, end, {2}, NIL, voff, 0) END
	END ChangeOffset;

	PROCEDURE Search*;
		VAR V: Viewers.Viewer;
			Text: TextFrames.Frame;
			T: Texts.Text; R: Texts.Reader;
			pos, beg, end, prevTime, len: LONGINT; n, i, j: INTEGER;
			buf: ARRAY 32 OF CHAR;

		PROCEDURE Forward (n: INTEGER);
			VAR m: INTEGER; j: INTEGER;
		BEGIN m := M - n;
			j := 0;
			WHILE j # m DO buf[j] := buf[n + j]; INC(j) END;
			WHILE j # M DO Texts.Read(R, buf[j]); INC(j) END
		END Forward;

	BEGIN
		V := Oberon.Par.vwr;
		IF Oberon.Par.frame # V.dsc THEN V := Oberon.MarkedViewer() END;
		IF (V.dsc # NIL) & (V.dsc.next IS TextFrames.Frame) THEN
			Text := V.dsc.next(TextFrames.Frame);
			TextFrames.Mark(Text, -1);
			prevTime := time; Oberon.GetSelection(T, beg, end, time);
			IF time > prevTime THEN
				Texts.OpenReader(R, T, beg);
				i := 0; pos := beg;
				REPEAT Texts.Read(R, pat[i]); INC(i); INC(pos)
				UNTIL (i = maxlen) OR (pos = end);
				M := i;
				j := 0;
				WHILE j # 256 DO d[j] := M; INC(j) END;
				j := 0;
				WHILE j # M - 1 DO d[ORD(pat[j])] := M - 1 - j; INC(j) END
			END;
			IF Text.car > 0 THEN pos := Text.carloc.pos ELSE pos := 0 END;
			len := Text.text.len;
			Texts.OpenReader(R, Text.text, pos);
			Forward(M); pos := pos + M;
			LOOP j := M;
				REPEAT DEC(j) UNTIL (j < 0) OR (buf[j] # pat[j]);
				IF (j < 0) OR (pos >= len) THEN EXIT END;
				n := d[ORD(buf[M-1])];
				Forward(n); pos := pos + n
			END;
			IF j < 0 THEN
				TextFrames.RemoveSelection(Text);
				TextFrames.RemoveCaret(Text);
				Oberon.RemoveMarks(Text.X, Text.Y, Text.W, Text.H);
				TextFrames.Show(Text, pos - 200);
				Oberon.PassFocus(V);
				TextFrames.SetCaret(Text, pos)
			END;
			TextFrames.Mark(Text, 1)
		END
	END Search;

	PROCEDURE Locate*;
		VAR V: Viewers.Viewer;
			Text: TextFrames.Frame;
			T: Texts.Text; S: Texts.Scanner;
			beg, end, time: LONGINT;
	BEGIN
		V := Oberon.MarkedViewer();
		IF (V.dsc # NIL) & (V.dsc.next IS TextFrames.Frame) THEN
			Text := V.dsc.next(TextFrames.Frame);
			Oberon.GetSelection(T, beg, end, time);
			IF time >= 0 THEN
				Texts.OpenScanner(S, T, beg);
				REPEAT Texts.Scan(S) UNTIL (S.class >= Texts.Int); (*skip names*)
				IF S.class = Texts.Int THEN
					TextFrames.RemoveSelection(Text);
					TextFrames.RemoveCaret(Text);
					Oberon.RemoveMarks(Text.X, Text.Y, Text.W, Text.H);
					TextFrames.Show(Text, Max(0, S.i - 200));
					Oberon.PassFocus(V);
					TextFrames.SetCaret(Text, S.i)
				END
			END
		END
	END Locate;

	PROCEDURE Recall*;
		VAR V: Viewers.Viewer;
			Menu, Main: Display.Frame;
			buf: Texts.Buffer;
			pos: LONGINT;
	BEGIN V := Oberon.FocusViewer;
		IF V IS MenuViewers.Viewer THEN
			Menu := V.dsc; Main := V.dsc.next;
			IF (Main IS TextFrames.Frame) & (Main(TextFrames.Frame).car > 0) THEN
				WITH Main: TextFrames.Frame DO
					Texts.Recall(buf);
					pos := Main.carloc.pos + buf.len;
					Texts.Insert(Main.text, Main.carloc.pos, buf);
					TextFrames.SetCaret(Main, pos)
				END
			ELSIF (Menu IS TextFrames.Frame) & (Menu(TextFrames.Frame).car > 0) THEN
				WITH Menu: TextFrames.Frame DO
					Texts.Recall(buf);
					pos := Menu.carloc.pos + buf.len;
					Texts.Insert(Menu.text, Menu.carloc.pos, buf);
					TextFrames.SetCaret(Menu, pos)
				END
			END
		END
	END Recall;

	PROCEDURE Print*;

		CONST
			textX = 160; textY = 225; botY = 100;

		VAR
			par: Oberon.ParList;
			V: Viewers.Viewer;
			Menu, Text: TextFrames.Frame;
			T, source: Texts.Text;
			R: Texts.Reader; S: Texts.Scanner;
			fnt: Fonts.Font;
			id, ch: CHAR;
			pageno: SHORTINT; listing: BOOLEAN;
			nofcopies, len, lsp, Y, topY: INTEGER;
			beg, end, time: LONGINT;

		PROCEDURE SendHeader;
			VAR pno: ARRAY 4 OF CHAR;
		BEGIN Printer.String(textX, Printer.PageHeight-125, S.s, Fonts.Default.name);
			IF pageno DIV 10 = 0 THEN pno[0] := " " ELSE pno[0] := CHR(pageno DIV 10 + 30H) END ;
			pno[1] := CHR(pageno MOD 10 + 30H); pno[2] := 0X;
			Printer.String(Printer.PageWidth-236, Printer.PageHeight-125, pno, Fonts.Default.name)
		END SendHeader;

		PROCEDURE PrintUnit (source: Texts.Text; pos: LONGINT);
			VAR i: INTEGER; new: BOOLEAN;
				buf: ARRAY 200 OF CHAR;
		BEGIN Texts.WriteString(W, S.s);
			IF source.len # 0 THEN
				Texts.WriteString(W, " printing"); Texts.WriteInt(W, nofcopies, 3);
				Texts.Append(Oberon.Log, W.buf);
				lsp := Fonts.Default.height * 7 DIV 2; pageno := 0;
				SendHeader; Y := topY;
				Texts.OpenReader(R, source, pos);
				IF ~listing THEN
					REPEAT Texts.Read(R, ch);
						new := TRUE; fnt := R.fnt;
						WHILE ~R.eot & (ch # CR) DO
							i := 0;
							REPEAT buf[i] := ch; INC(i); Texts.Read(R, ch)
							UNTIL R.eot OR (ch = CR) OR (R.fnt # fnt);
							buf[i] := 0X;
							IF new THEN Printer.String(textX, Y, buf, fnt.name)
							ELSE Printer.ContString(buf, fnt.name)
							END;
							new := FALSE; fnt := R.fnt
						END;
						Y := Y - lsp;
						IF Y < botY THEN
							Printer.Page(nofcopies); INC(pageno); SendHeader; Y := topY
						END
					UNTIL R.eot
				ELSE lsp := 32;
					REPEAT Texts.Read(R, ch);
						WHILE ~R.eot & (ch # CR) DO
							i := 0;
							REPEAT buf[i] := ch; INC(i); Texts.Read(R, ch)
							UNTIL R.eot OR (ch = CR);
							buf[i] := 0X;
							Printer.String(textX, Y, buf, Fonts.Default.name)
						END;
						Y := Y - lsp;
						IF Y < botY THEN
							Printer.Page(nofcopies); INC(pageno); SendHeader; Y := topY
						END
					UNTIL R.eot
				END;
				IF Y < topY THEN Printer.Page(nofcopies) END
			ELSE Texts.WriteString(W, " not found")
			END;
			Texts.WriteLn(W);
			Texts.Append(Oberon.Log, W.buf)
		END PrintUnit;

		PROCEDURE Option;
			VAR ch: CHAR;
		BEGIN nofcopies := 1;
			IF S.nextCh = "/" THEN
				Texts.Read(S, ch);
				IF (ch >= "0") & (ch <= "9") THEN nofcopies := ORD(ch) - 30H END ;
				WHILE ch > " " DO Texts.Read(S, ch) END;
				S.nextCh := ch
			END
		END Option;

	BEGIN par := Oberon.Par;
		Texts.WriteString(W, "Edit.Print"); Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf);
		Texts.OpenScanner(S, par.text, par.pos); Texts.Scan(S);
		IF S.class = Texts.Name THEN
			Printer.Open(S.s, Oberon.User, Oberon.Password);
			IF Printer.res = 0 THEN
				topY := Printer.PageHeight - textY; Texts.Scan(S);
				IF (S.class = Texts.Char) & (S.c = "%") THEN
					listing := TRUE; Printer.UseListFont(Fonts.Default.name); Texts.Scan(S)
				ELSE listing := FALSE
				END;
				IF (S.class = Texts.Char) & (S.c = "*") THEN
					Option; V := Oberon.MarkedViewer();
					IF (V.dsc IS TextFrames.Frame) & (V.dsc.next IS TextFrames.Frame) THEN
						Menu := V.dsc(TextFrames.Frame); Text := V.dsc.next(TextFrames.Frame);
						Texts.OpenScanner(S, Menu.text, 0); Texts.Scan(S);
						TextFrames.Mark(Text, -1); PrintUnit(Text.text, 0); TextFrames.Mark(Text, 1)
					END
				ELSE
					WHILE S.class = Texts.Name DO
						Option; NEW(source); Texts.Open(source, S.s); PrintUnit(source, 0);
						Texts.Scan(S)
					END;
					IF (S.class = Texts.Char) & (S.c = "^") THEN Oberon.GetSelection(T, beg, end, time);
						IF time >= 0 THEN Texts.OpenScanner(S, T, beg); Texts.Scan(S);
							IF S.class = Texts.Name THEN
								Option; NEW(source); Texts.Open(source, S.s); PrintUnit(source, 0)
							END
						END
					END
				END;
				Printer.Close
			ELSE
				IF Printer.res = 1 THEN Texts.WriteString(W, " no printer")
					ELSIF Printer.res = 2 THEN Texts.WriteString(W, " no link")
					ELSIF Printer.res = 3 THEN Texts.WriteString(W, " printer not ready")
					ELSIF Printer.res = 4 THEN Texts.WriteString(W, " no permission")
				END;
				Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf)
			END
		ELSE Texts.WriteString(W, " no printer specified");
			Texts.WriteLn(W); Texts.Append(Oberon.Log, W.buf)
		END
	END Print;

	PROCEDURE InitPattern;
		VAR j: INTEGER;
	BEGIN
		pat[0] := " "; M := 1; time := -1; j := 0;
		WHILE j # 256 DO d[j] := M; INC(j) END
	END InitPattern;

BEGIN Texts.OpenWriter(W); InitPattern
END Edit.