Std/Mod/Folds.odc

MODULE StdFolds;
(**
	project	= "BlackBox"
	organization	= "www.oberon.ch"
	contributors	= "Oberon microsystems"
	version	= "System/Rsrc/About"
	copyright	= "System/Rsrc/About"
	license	= "Docu/BB-License"
	changes	= "
	- YYYYMMDD, nn, ...
	"
	issues	= "
	- ...
	"

**)

	IMPORT
		Domains := Stores, Ports, Stores, Containers, Models, Views, Controllers, Fonts,
		Properties,Controls,
		TextModels, TextViews, TextControllers, TextSetters,
		Dialog, Services;

	CONST
		expanded* = FALSE; collapsed* = TRUE;
		minVersion = 0; currentVersion = 0;

		collapseFoldKey = "#Std:Collapse Fold";
		expandFoldKey = "#Std:Expand Fold";
		zoomInKey = "#Std:Zoom In";
		zoomOutKey = "#Std:Zoom Out";
		expandFoldsKey = "#Std:Expand Folds";
		collapseFoldsKey = "#Std:Collapse Folds";
		insertFoldKey = "#Std:Insert Fold";
		setLabelKey = "#Std:Set Label";


	TYPE
		Label* = ARRAY 32 OF CHAR;
		
		Fold* = POINTER TO RECORD (Views.View)
			leftSide-: BOOLEAN;
			collapsed-: BOOLEAN;
			label-: Label; (* valid iff leftSide *)
			hidden: TextModels.Model (* valid iff leftSide; NIL if no hidden text *)
		END;

		Directory* = POINTER TO ABSTRACT RECORD END;

		StdDirectory = POINTER TO RECORD (Directory) END;

		FlipOp = POINTER TO RECORD (Domains.Operation)
			text: TextModels.Model; (* containing text *)
			leftpos, rightpos: INTEGER (* position of left and right Fold *)
		END;
		
		SetLabelOp = POINTER TO RECORD (Domains.Operation)
			text: TextModels.Model; (* containing text *)
			pos: INTEGER; (* position of fold in text *)
			oldlabel: Label
		END;
		
		Action = POINTER TO RECORD (Services.Action) END;

		
	VAR
		dir-, stdDir-: Directory;

		foldData*: RECORD
			nested*: BOOLEAN;
			all*: BOOLEAN;
			findLabel*: Label;
			newLabel*: Label
		END;

		iconFont: Fonts.Typeface;
		leftExp, rightExp, leftColl, rightColl: ARRAY 8 OF SHORTCHAR;
		coloredBackg: BOOLEAN;
		action: Action;
		fingerprint: INTEGER;	(* for the property inspector *)

		PROCEDURE (d: Directory) New* (collapsed: BOOLEAN; label: Label;
																	hiddenText: TextModels.Model): Fold, NEW, ABSTRACT;


	PROCEDURE GetPair (fold: Fold; VAR l, r: Fold);
		VAR c: Models.Context; text: TextModels.Model; rd: TextModels.Reader; v: Views.View;
			nest: INTEGER;
	BEGIN
		c := fold.context; l := NIL; r := NIL;
		WITH c: TextModels.Context DO
			text := c.ThisModel(); rd := text.NewReader(NIL);
			IF fold.leftSide THEN l := fold;
				rd.SetPos(c.Pos()+1); nest := 1;
				REPEAT rd.ReadView(v);
					IF (v # NIL) & (v IS Fold) THEN
						IF v(Fold).leftSide THEN INC(nest) ELSE DEC(nest) END
					END
				UNTIL (v = NIL) OR (nest = 0);
				IF v # NIL THEN r := v(Fold) ELSE r := NIL END
			ELSE r := fold;
				rd.SetPos(c.Pos()); nest := 1;
				REPEAT rd.ReadPrevView(v);
					IF (v # NIL) & (v IS Fold) THEN
						IF ~v(Fold).leftSide THEN INC(nest) ELSE DEC(nest) END
					END
				UNTIL (v = NIL) OR (nest = 0);
				IF v # NIL THEN l := v(Fold) ELSE l := NIL END
			END
		ELSE (* fold not embedded in a text *)
		END;
		ASSERT((l = NIL) OR l.leftSide & (l.hidden # NIL), 100);
		ASSERT((r = NIL) OR ~r.leftSide & (r.hidden = NIL), 101)
	END GetPair;

	PROCEDURE (fold: Fold) HiddenText* (): TextModels.Model, NEW;
		VAR l, r: Fold;
	BEGIN
		IF fold.leftSide THEN RETURN fold.hidden
		ELSE GetPair(fold, l, r);
			IF l # NIL THEN RETURN l.hidden ELSE RETURN NIL END
		END
	END HiddenText;

	PROCEDURE (fold: Fold) MatchingFold* (): Fold, NEW;
		VAR l, r: Fold;
	BEGIN
		GetPair(fold, l, r);
		IF l # NIL THEN
			IF fold = l THEN RETURN r ELSE RETURN l END
		ELSE RETURN NIL
		END
	END MatchingFold;

	PROCEDURE GetIcon (fold: Fold; VAR icon: ARRAY OF SHORTCHAR);
	BEGIN
		IF fold.leftSide THEN
			IF fold.collapsed THEN icon := leftColl$ ELSE icon := leftExp$ END
		ELSE
			 IF fold.collapsed THEN icon := rightColl$ ELSE icon := rightExp$ END
		END
	END GetIcon;

	PROCEDURE CalcSize (f: Fold; VAR w, h: INTEGER);
		VAR icon: ARRAY 8 OF SHORTCHAR; c: Models.Context; a: TextModels.Attributes; font: Fonts.Font;
			asc, dsc, fw: INTEGER;
	BEGIN
		GetIcon(f, icon);
		c := f.context;
		IF (c # NIL) & (c IS TextModels.Context) THEN
			a := c(TextModels.Context).Attr();
			font := Fonts.dir.This(iconFont, a.font.size, {}, Fonts.normal)
		ELSE font := Fonts.dir.Default()
		END;
		w := font.SStringWidth(icon);
		font.GetBounds(asc, dsc, fw);
		h := asc + dsc
	END CalcSize;

	PROCEDURE Update (f: Fold);
		VAR w, h: INTEGER;
	BEGIN
		CalcSize(f, w, h);
		f.context.SetSize(w, h);
		Views.Update(f, Views.keepFrames)
	END Update;

	PROCEDURE FlipPair (l, r: Fold);
		VAR text, hidden: TextModels.Model; cl, cr: Models.Context;
			lpos, rpos: INTEGER;
	BEGIN
		IF (l # NIL) & (r # NIL) THEN
			ASSERT(l.leftSide, 100);
			ASSERT(~r.leftSide, 101);
			ASSERT(l.hidden # NIL, 102);
			ASSERT(r.hidden = NIL, 103);
			cl := l.context; cr := r.context;
			text := cl(TextModels.Context).ThisModel();
			lpos := cl(TextModels.Context).Pos() + 1; rpos := cr(TextModels.Context).Pos();
			ASSERT(lpos <= rpos, 104);
			hidden := TextModels.CloneOf(text); 
			hidden.Insert(0, text, lpos, rpos);
			text.Insert(lpos, l.hidden, 0, l.hidden.Length());
			l.hidden := hidden; Stores.Join(l, hidden);
			l.collapsed := ~l.collapsed;
			r.collapsed := l.collapsed;
			Update(l); Update(r);
			TextControllers.SetCaret(text, lpos)
		END
	END FlipPair;

	PROCEDURE (op: FlipOp) Do;
		VAR rd: TextModels.Reader; left, right: Views.View;
	BEGIN
		rd := op.text.NewReader(NIL);
		rd.SetPos(op.leftpos); rd.ReadView(left);
		rd.SetPos(op.rightpos); rd.ReadView(right);
		FlipPair(left(Fold), right(Fold));
		op.leftpos := left.context(TextModels.Context).Pos();
		op.rightpos := right.context(TextModels.Context).Pos()
	END Do;

	PROCEDURE (op: SetLabelOp) Do;
		VAR rd: TextModels.Reader; fold: Views.View; left, right: Fold; lab: Label;
	BEGIN
		rd := op.text.NewReader(NIL);
		rd.SetPos(op.pos); rd.ReadView(fold);
		WITH fold: Fold DO
			GetPair(fold, left, right);
			IF left # NIL THEN
				lab := fold.label; left.label := op.oldlabel; op.oldlabel := lab;
				right.label := left.label
			END
		END
	END Do;

	PROCEDURE SetProp (fold: Fold; p : Properties.Property);
		VAR op: SetLabelOp; left, right: Fold;
	BEGIN
		WHILE p # NIL DO
			WITH p: Controls.Prop DO
				IF (Controls.label IN p.valid) & (p.label # fold.label) THEN
					GetPair(fold, left, right);
					IF left # NIL THEN
						NEW(op); op.oldlabel := p.label$;
						op.text := fold.context(TextModels.Context).ThisModel();
						op.pos := fold.context(TextModels.Context).Pos();
						Views.Do(fold, setLabelKey, op)
					END
				END
			ELSE
			END;
			p := p.next
		END
	END SetProp;

	PROCEDURE (fold: Fold) Flip*, NEW;
		VAR op: FlipOp; left, right: Fold;
	BEGIN
		ASSERT(fold # NIL, 20);
		NEW(op);
		GetPair(fold, left, right);
		IF (left # NIL) & (right # NIL) THEN
			op.text := fold.context(TextModels.Context).ThisModel();
			op.leftpos := left.context(TextModels.Context).Pos();
			op.rightpos := right.context(TextModels.Context).Pos();
			Views.BeginModification(Views.clean, fold);
			IF ~left.collapsed THEN Views.Do(fold, collapseFoldKey, op)
			ELSE Views.Do(fold, expandFoldKey, op)
			END;
			Views.EndModification(Views.clean, fold)
		END
	END Flip;

	PROCEDURE ReadNext (rd: TextModels.Reader; VAR fold: Fold);
		VAR v: Views.View;
	BEGIN
		REPEAT rd.ReadView(v) UNTIL rd.eot OR (v IS Fold);
		IF ~rd.eot THEN fold := v(Fold) ELSE fold := NIL END
	END ReadNext;

	PROCEDURE (fold: Fold) FlipNested*, NEW;
		VAR text: TextModels.Model; rd: TextModels.Reader; l, r: Fold; level: INTEGER;
			op: Domains.Operation;
	BEGIN
		ASSERT(fold # NIL, 20);
		GetPair(fold, l, r);
		IF (l # NIL) & (l.context # NIL) & (l.context IS TextModels.Context) THEN
			text := l.context(TextModels.Context).ThisModel();
			Models.BeginModification(Models.clean, text);
			rd := text.NewReader(NIL);
			rd.SetPos(l.context(TextModels.Context).Pos());
			IF l.collapsed THEN
				Models.BeginScript(text, expandFoldsKey, op);
				ReadNext(rd, fold); level := 1;
				WHILE (fold # NIL) & (level > 0) DO
					IF fold.leftSide & fold.collapsed THEN fold.Flip END;
					ReadNext(rd, fold);
					IF fold.leftSide THEN INC(level) ELSE DEC(level) END
				END
			ELSE (* l.state = expanded *)
				Models.BeginScript(text, collapseFoldsKey, op);
				level := 0;
				REPEAT ReadNext(rd, fold);
					IF fold.leftSide THEN INC(level) ELSE DEC(level) END;
					IF (fold # NIL) & ~fold.leftSide & ~fold.collapsed THEN
						fold.Flip;
						rd.SetPos(fold.context(TextModels.Context).Pos()+1)
					END
				UNTIL (fold = NIL) OR (level = 0)
			END;
			Models.EndScript(text, op);
			Models.EndModification(Models.clean, text)
		END
	END FlipNested;

	PROCEDURE (fold: Fold) HandlePropMsg- (VAR msg: Properties.Message);
		VAR prop: Controls.Prop; c: Models.Context; a: TextModels.Attributes; asc, w: INTEGER;
	BEGIN
		WITH msg: Properties.SizePref DO
			CalcSize(fold, msg.w, msg.h)
		| msg: Properties.ResizePref DO
			msg.fixed := TRUE
		| msg: Properties.FocusPref DO msg.hotFocus := TRUE
		| msg: Properties.PollMsg DO NEW(prop);
			prop.known := {Controls.label}; prop.valid := {Controls.label}; prop.readOnly := {};
			prop.label := fold.label$;
			msg.prop := prop
		| msg: Properties.SetMsg DO SetProp(fold, msg.prop)
		| msg: TextSetters.Pref DO c := fold.context;
			IF (c # NIL) & (c IS TextModels.Context) THEN
				a := c(TextModels.Context).Attr();
				a.font.GetBounds(asc, msg.dsc, w)
			END
		ELSE
		END
	END HandlePropMsg;

	PROCEDURE Track (fold: Fold; f: Views.Frame; x, y: INTEGER; buttons: SET; VAR hit: BOOLEAN);
		VAR a: TextModels.Attributes; font: Fonts.Font; c: Models.Context;
			w, h, asc, dsc, fw: INTEGER; isDown, in, in0: BOOLEAN; modifiers: SET;
	BEGIN
		c := fold.context; hit := FALSE;
		WITH c: TextModels.Context DO
			a := c.Attr(); font := a.font;
			c.GetSize(w, h); in0 := FALSE;
			in := (0 <= x) & (x < w) & (0 <= y) & (y < h);
			REPEAT
				IF in # in0 THEN
					f.MarkRect(0, 0, w, h, Ports.fill, Ports.hilite, FALSE); in0 := in
				END;
				f.Input(x, y, modifiers, isDown);
				in := (0 <= x) & (x < w) & (0 <= y) & (y < h)
			UNTIL ~isDown;
			IF in0 THEN hit := TRUE;
				font.GetBounds(asc, dsc, fw);
				f.MarkRect(0, 0, w, asc + dsc, Ports.fill, Ports.hilite, FALSE)
			END
		ELSE
		END
	END Track;

		PROCEDURE (fold: Fold) HandleCtrlMsg* (f: Views.Frame; VAR msg: Views.CtrlMessage;
																							VAR focus: Views.View);
			VAR hit: BOOLEAN; pos: INTEGER; l, r: Fold;
				context: TextModels.Context; text: TextModels.Model;
		BEGIN
			WITH msg: Controllers.TrackMsg DO
				IF fold.context IS TextModels.Context THEN
					Track(fold, f, msg.x, msg.y, msg.modifiers, hit);
					IF hit THEN
						IF Controllers.modify IN msg.modifiers THEN
							fold.FlipNested
						ELSE
							fold.Flip;
							context := fold.context(TextModels.Context);
							text := context.ThisModel();
							IF TextViews.FocusText() = text THEN
								GetPair(fold, l, r);
								pos := context.Pos();
								IF fold = l THEN
									TextControllers.SetCaret(text, pos + 1)
								ELSE
									TextControllers.SetCaret(text, pos)
								END;
								TextViews.ShowRange(text, pos, pos + 1, TRUE)
							END
						END
					END
				END
			| msg: Controllers.PollCursorMsg DO
				msg.cursor := Ports.refCursor
			ELSE
			END
		END HandleCtrlMsg;

	PROCEDURE (fold: Fold) Restore* (f: Views.Frame; l, t, r, b: INTEGER);
		VAR a: TextModels.Attributes; color: Ports.Color; c: Models.Context; font: Fonts.Font;
			icon: ARRAY 8 OF SHORTCHAR; w, h: INTEGER; asc, dsc, fw: INTEGER;
	BEGIN
		GetIcon(fold, icon); c := fold.context;
		IF (c # NIL) & (c IS TextModels.Context) THEN
			a := fold.context(TextModels.Context).Attr();
			font := Fonts.dir.This(iconFont, a.font.size, {}, Fonts.normal);
			color := a.color
		ELSE font := Fonts.dir.Default(); color := Ports.black
		END;
		IF coloredBackg THEN
			fold.context.GetSize(w, h);
			f.DrawRect(f.l, f.dot, f.r, h-f.dot, Ports.fill, Ports.grey50);
			color := Ports.white
		END;
		font.GetBounds(asc, dsc, fw);
		f.DrawSString(0, asc, color, icon, font)
	END Restore;

	PROCEDURE (fold: Fold) CopyFromSimpleView- (source: Views.View);
	BEGIN
		(* fold.CopyFrom^(source); *)
		WITH source: Fold DO
			ASSERT(source.leftSide = (source.hidden # NIL), 100);
			fold.leftSide := source.leftSide;
			fold.collapsed := source.collapsed;
			fold.label := source.label;
			IF source.hidden # NIL THEN
				fold.hidden := TextModels.CloneOf(source.hidden); Stores.Join(fold.hidden, fold);
				fold.hidden.InsertCopy(0, source.hidden, 0, source.hidden.Length())
			END
		END
	END CopyFromSimpleView;

	PROCEDURE (fold: Fold) Internalize- (VAR rd: Stores.Reader);
		VAR version: INTEGER; store: Stores.Store; xint: INTEGER;
	BEGIN
		fold.Internalize^(rd);
		IF rd.cancelled THEN RETURN END;
		rd.ReadVersion(minVersion, currentVersion, version);
		IF rd.cancelled THEN RETURN END;
		rd.ReadXInt(xint);fold.leftSide := xint = 0;
		rd.ReadXInt(xint); fold.collapsed := xint = 0;
		rd.ReadXString(fold.label);
		rd.ReadStore(store);
		IF store # NIL THEN fold.hidden := store(TextModels.Model); Stores.Join(fold.hidden, fold)
		ELSE fold.hidden := NIL
		END;
		fold.leftSide := store # NIL
	END Internalize;

	PROCEDURE (fold: Fold) Externalize- (VAR wr: Stores.Writer);
		VAR xint: INTEGER;
	BEGIN
		fold.Externalize^(wr);
		wr.WriteVersion(currentVersion);
		IF fold.hidden # NIL THEN xint := 0 ELSE xint := 1 END;
		wr.WriteXInt(xint);
		IF fold.collapsed THEN xint := 0 ELSE xint := 1 END;
		wr.WriteXInt(xint);
		wr.WriteXString(fold.label);
		wr.WriteStore(fold.hidden)
	END Externalize;

	(* --------------------- expanding and collapsing in focus text ------------------------ *)

	PROCEDURE ExpandFolds* (text: TextModels.Model; nested: BOOLEAN; IN label: ARRAY OF CHAR);
		VAR op: Domains.Operation; fold, l, r: Fold; rd: TextModels.Reader;
	BEGIN
		ASSERT(text # NIL, 20);
		Models.BeginModification(Models.clean, text);
		IF nested THEN Models.BeginScript(text, expandFoldsKey, op)
		ELSE Models.BeginScript(text, zoomInKey, op)
		END;
		rd := text.NewReader(NIL); rd.SetPos(0);
		ReadNext(rd, fold);
		WHILE ~rd.eot DO
			IF fold.leftSide & fold.collapsed THEN
				IF (label = "") OR (label = fold.label) THEN
					fold.Flip;
					IF ~nested THEN 
						GetPair(fold, l, r);
						rd.SetPos(r.context(TextModels.Context).Pos())
					END
				END
			END;
			ReadNext(rd, fold)
		END;
		Models.EndScript(text, op);
		Models.EndModification(Models.clean, text)
	END ExpandFolds;

	PROCEDURE CollapseFolds* (text: TextModels.Model; nested: BOOLEAN; IN label: ARRAY OF CHAR);
		VAR op: Domains.Operation; fold, r, l: Fold; rd: TextModels.Reader;
	BEGIN
		ASSERT(text # NIL, 20);
		Models.BeginModification(Models.clean, text);
		IF nested THEN Models.BeginScript(text, collapseFoldsKey, op)
		ELSE Models.BeginScript(text, zoomOutKey, op)
		END;
		rd := text.NewReader(NIL); rd.SetPos(0);
		ReadNext(rd, fold);
		WHILE ~rd.eot DO
			IF ~fold.leftSide & ~fold.collapsed THEN
				GetPair(fold, l, r);
				IF (label = "") OR (label = l.label) THEN
					fold.Flip;
					GetPair(l, l, r);
					rd.SetPos(r.context(TextModels.Context).Pos()+1);
					IF ~nested THEN REPEAT ReadNext(rd, fold) UNTIL rd.eot OR fold.leftSide
					ELSE ReadNext(rd, fold)
					END
				ELSE ReadNext(rd, fold)
				END
			ELSE ReadNext(rd, fold)
			END
		END;
		Models.EndScript(text, op);
		Models.EndModification(Models.clean, text)
	END CollapseFolds;

	PROCEDURE ZoomIn*;
		VAR text: TextModels.Model;
	BEGIN
		text := TextViews.FocusText();
		IF text # NIL THEN ExpandFolds(text, FALSE, "") END
	END ZoomIn;

	PROCEDURE ZoomOut*;
		VAR text: TextModels.Model;
	BEGIN
		text := TextViews.FocusText();
		IF text # NIL THEN CollapseFolds(text, FALSE, "") END
	END ZoomOut;

	PROCEDURE Expand*;
		VAR text: TextModels.Model;
	BEGIN
		text := TextViews.FocusText();
		IF text # NIL THEN ExpandFolds(text, TRUE, "") END
	END Expand;

	PROCEDURE Collapse*;
		VAR text: TextModels.Model;
	BEGIN
		text := TextViews.FocusText();
		IF text # NIL THEN CollapseFolds(text, TRUE, "") END
	END Collapse;

	(* ---------------------- foldData dialogbox --------------------------- *)

	PROCEDURE FindLabelGuard* (VAR par: Dialog.Par);
	BEGIN
		par.disabled := (TextViews.Focus() = NIL) OR foldData.all
	END FindLabelGuard;
		
	PROCEDURE SetLabelGuard* ( VAR p : Dialog.Par );
		VAR v: Views.View;
	BEGIN
		Controllers.SetCurrentPath(Controllers.targetPath);
		v := Containers.FocusSingleton();
		p.disabled := (v = NIL) OR ~(v IS Fold) OR ~v(Fold).leftSide;
		Controllers.ResetCurrentPath()
	END SetLabelGuard;

	PROCEDURE ExpandLabel*;
		VAR text: TextModels.Model;
	BEGIN
		IF foldData.all & (foldData.findLabel # "") THEN
			foldData.findLabel := ""; Dialog.Update(foldData)
		END;
		text := TextViews.FocusText();
		IF text # NIL THEN
			IF ~foldData.all THEN ExpandFolds(text, foldData.nested, foldData.findLabel)
			ELSE ExpandFolds(text, foldData.nested, "")
			END
		END
	END ExpandLabel;

	PROCEDURE CollapseLabel*;
		VAR text: TextModels.Model;
	BEGIN
		IF foldData.all & (foldData.findLabel # "") THEN
			foldData.findLabel := ""; Dialog.Update(foldData)
		END;
		text := TextViews.FocusText();
		IF text # NIL THEN
			IF ~foldData.all THEN CollapseFolds(text, foldData.nested, foldData.findLabel)
			ELSE CollapseFolds(text, foldData.nested, "")
			END
		END
	END CollapseLabel;

	PROCEDURE FindFold(first: BOOLEAN);
	VAR c : TextControllers.Controller; r: TextModels.Reader; 
		v : Views.View; pos, i : INTEGER;
	BEGIN
		c := TextControllers.Focus();
		IF c # NIL THEN
			IF first THEN pos := 0 
			ELSE
				pos := c.CaretPos();
				IF pos = TextControllers.none THEN
					c.GetSelection(i, pos);
					IF pos = i THEN pos := 0 ELSE INC(pos) END;
					pos := MIN(pos, c.text.Length()-1)
				END
			END;
			r := c.text.NewReader(NIL); r.SetPos(pos);
			REPEAT r.ReadView(v)
			UNTIL r.eot OR ((v IS Fold) & v(Fold).leftSide) & (foldData.all OR (v(Fold).label$ = foldData.findLabel$));
			IF r.eot THEN
				c.SetCaret(0); Dialog.Beep
			ELSE
				pos := r.Pos();
				c.view.ShowRange(pos-1, pos, FALSE);
				c.SetSelection(pos-1, pos);
				IF LEN(v(Fold).label) > 0 THEN
					foldData.newLabel := v(Fold).label
				END;
				Dialog.Update(foldData)
			END
		ELSE
			Dialog.Beep
		END
	END FindFold;

	PROCEDURE FindNextFold*;
	BEGIN
		FindFold(FALSE)
	END FindNextFold;
	
	PROCEDURE FindFirstFold*;
	BEGIN
		FindFold(TRUE)
	END FindFirstFold;
	
	PROCEDURE SetLabel*;
		VAR v: Views.View;
	BEGIN
		Controllers.SetCurrentPath(Controllers.targetPath);
		v := Containers.FocusSingleton();
		IF (v # NIL) & (v IS Fold) & (LEN(foldData.newLabel) > 0) THEN
			v(Fold).label := foldData.newLabel
		ELSE
			Dialog.Beep
		END;
		Controllers.ResetCurrentPath()
	END SetLabel;

	PROCEDURE (a: Action) Do;
		VAR v: Views.View; fp: INTEGER;
	BEGIN
		Controllers.SetCurrentPath(Controllers.targetPath);
		v := Containers.FocusSingleton();
		IF (v = NIL) OR ~(v IS Fold) THEN 
			fingerprint := 0;
			foldData.newLabel := ""
		ELSE 
			fp := Services.AdrOf(v);
			IF fp # fingerprint THEN 
				foldData.newLabel := v(Fold).label;
				fingerprint := fp;
				Dialog.Update(foldData)
			END
		END;
		Controllers.ResetCurrentPath();
		Services.DoLater(action, Services.Ticks() + Services.resolution DIV 2)
	END Do;

	(* ------------------------ inserting folds ------------------------ *)
		
	PROCEDURE Overlaps* (text: TextModels.Model; beg, end: INTEGER): BOOLEAN;
		VAR n, level: INTEGER; rd: TextModels.Reader; v: Views.View;
	BEGIN
		ASSERT(text # NIL, 20);
		ASSERT((beg >= 0) & (end <= text.Length()) & (beg <= end), 21);
		rd := text.NewReader(NIL); rd.SetPos(beg);
		n := 0; level := 0;
		REPEAT rd.ReadView(v);
			IF ~rd.eot & (rd.Pos() <= end) THEN
				WITH v: Fold DO INC(n);
					IF v.leftSide THEN INC(level) ELSE DEC(level) END
				ELSE
				END
			END
		UNTIL rd.eot OR (level < 0) OR (rd.Pos() >= end);
		RETURN (level # 0) OR ODD(n)
	END Overlaps;

	PROCEDURE InsertionAttr (text: TextModels.Model; pos: INTEGER): TextModels.Attributes;
		VAR rd: TextModels.Reader; ch: CHAR;
	BEGIN
		rd := text.NewReader(NIL);
		rd.SetPos(pos); rd.ReadChar(ch);
		RETURN rd.attr
	END InsertionAttr;

	PROCEDURE Insert* (text: TextModels.Model; label: Label; beg, end: INTEGER; collapsed: BOOLEAN);
		VAR w: TextModels.Writer; fold: Fold; insop: Domains.Operation; a: TextModels.Attributes;
	BEGIN
		ASSERT(text # NIL, 20);
		ASSERT((beg >= 0) & (end <= text.Length()) & (beg <= end), 21);
		a := InsertionAttr(text, beg);
		w := text.NewWriter(NIL); w.SetPos(beg);
		IF a # NIL THEN w.SetAttr(a) END;
		NEW(fold);
		fold.leftSide := TRUE; fold.collapsed := collapsed;
		fold.hidden := TextModels.CloneOf(text); Stores.Join(fold, fold.hidden);
		fold.label := label$;
		Models.BeginScript(text, insertFoldKey, insop);
		w.WriteView(fold, 0, 0);
		w.SetPos(end+1);
		a := InsertionAttr(text, end+1);
		IF a # NIL THEN w.SetAttr(a) END;
		NEW(fold);
		fold.leftSide := FALSE; fold.collapsed := collapsed;
		fold.hidden := NIL; fold.label := "";
		w.WriteView(fold, 0, 0);
		Models.EndScript(text, insop)
	END Insert;

	PROCEDURE CreateGuard* (VAR par: Dialog.Par);
		VAR c: TextControllers.Controller; beg, end: INTEGER;
	BEGIN c := TextControllers.Focus();
		IF (c # NIL) &  ~(Containers.noCaret IN c.opts) THEN
			IF c.HasSelection() THEN c.GetSelection(beg, end);
				IF Overlaps(c.text, beg, end) THEN par.disabled := TRUE END
			END
		ELSE par.disabled := TRUE
		END
	END CreateGuard;

	PROCEDURE Create* (state: INTEGER);	(* menu cmd parameters don't accept Booleans *)
		VAR c: TextControllers.Controller; beg, end: INTEGER; collapsed: BOOLEAN;
	BEGIN
		collapsed := state = 0;
		c := TextControllers.Focus();
		IF (c # NIL) & ~(Containers.noCaret IN c.opts) THEN
			IF c.HasSelection() THEN c.GetSelection(beg, end);
				IF ~Overlaps(c.text, beg, end) THEN Insert(c.text, "", beg, end, collapsed) END
			ELSE beg := c.CaretPos(); Insert(c.text, "", beg, beg, collapsed)
			END
		END
	END Create;

	PROCEDURE InitIcons;
		VAR font: Fonts.Font;

		PROCEDURE DefaultAppearance;
		BEGIN
			font := Fonts.dir.Default(); iconFont := font.typeface$;
			leftExp := ">"; rightExp := "<";
			leftColl := "=>"; rightColl := "<=";
			coloredBackg := TRUE
		END DefaultAppearance;

	BEGIN
		IF Dialog.platform = Dialog.linux THEN (* Linux *)
			DefaultAppearance; 
			coloredBackg := FALSE
		ELSIF Dialog.platform DIV 10 = 1 THEN (* Windows *)
			iconFont := "Wingdings";
			font := Fonts.dir.This(iconFont, 10*Fonts.point (*arbitrary*), {}, Fonts.normal);
			IF font.IsAlien() THEN DefaultAppearance
			ELSE
				leftExp[0] := SHORT(CHR(240)); leftExp[1] := 0X;
				rightExp[0] := SHORT(CHR(239)); rightExp[1] := 0X;
				leftColl[0] := SHORT(CHR(232)); leftColl[1] := 0X;
				rightColl[0] := SHORT(CHR(231)); rightColl[1] := 0X;
				coloredBackg := FALSE
			END
		ELSIF Dialog.platform DIV 10 = 2 THEN (* Mac *)
			iconFont := "Chicago";
			font := Fonts.dir.This(iconFont, 10*Fonts.point (*arbitrary*), {}, Fonts.normal);
			IF font.IsAlien() THEN DefaultAppearance
			ELSE
				leftExp := ">"; rightExp := "<";
				leftColl := ""; rightColl := "";
				coloredBackg := TRUE
			END
		ELSE
			DefaultAppearance
		END
	END InitIcons;

	PROCEDURE (d: StdDirectory) New (collapsed: BOOLEAN; label: Label;
																hiddenText: TextModels.Model): Fold;
		VAR fold: Fold;
	BEGIN
		NEW(fold); fold.leftSide := hiddenText # NIL; fold.collapsed := collapsed;
		fold.label := label; fold.hidden := hiddenText; 
		IF hiddenText # NIL THEN Stores.Join(fold, fold.hidden) END;
		RETURN fold
	END  New;

	PROCEDURE SetDir* (d: Directory);
	BEGIN
		ASSERT(d # NIL, 20);
		dir := d
	END SetDir;

	PROCEDURE InitMod;
		VAR d: StdDirectory;
	BEGIN
		foldData.all := TRUE; foldData.nested := FALSE; foldData.findLabel := ""; foldData.newLabel := "";
		NEW(d); dir := d; stdDir := d;
		InitIcons;
		NEW(action); Services.DoLater(action, Services.now);
	END InitMod;

BEGIN
	InitMod
END StdFolds.