MODULE Controls;
(**
	project	= "BlackBox"
	organization	= "www.oberon.ch"
	contributors	= "Oberon microsystems"
	version	= "System/Rsrc/About"
	copyright	= "System/Rsrc/About"
	license	= "Docu/BB-License"
	changes	= "
	- 20141027, center #19, full Unicode support for Component Pascal identifiers added
	- 20150521, center #51, support for ANYPTR and SYSTEM.PTR in LookupPath
	- 20160122, center #98, improvements of GUI controls
	- 20220525, MRW, put "E" to the f.KeyDown when "e" is typed
	- 20230116, k8, fixed `KeyDown()` calls according to the new signature
	- 20230130, dia, made content of readOnly fields be posible to select and copy
	"
	issues	= "
	- ...
	"

**)

	IMPORT
		Kernel, Dates, Dialog, Meta, Services, Stores, Views, Properties, 
		Strings, Fonts, Ports, Controllers, Windows, StdCFrames;

	CONST
		(** elements of Property.valid **)
		opt0* = 0; opt1* = 1; opt2* = 2; opt3* = 3; opt4* = 4;
		link* = 5; label* = 6; guard* = 7; notifier* = 8; level* = 9;

		default* = opt0; cancel* = opt1;
		left* = opt0; right* = opt1; multiLine* = opt2; password* = opt3;
		sorted* = opt0;
		haslines* = opt1; hasbuttons* = opt2; atroot* = opt3; foldericons* = opt4;

		minVersion = 0; maxBaseVersion = 4;
		pbVersion = 0; cbVersion = 0; rbVersion = 0; fldVersion = 0; 
		dfldVersion = 0; tfldVersion = 0; cfldVersion = 0;
		lbxVersion = 0; sbxVersion = 0; cbxVersion = 0; capVersion = 1; grpVersion = 0; 
		tfVersion = 0; 

		rdel = 07X; ldel = 08X;  tab = 09X; ltab = 0AX; lineChar = 0DX; esc = 01BX;
		arrowLeft = 1CX; arrowRight = 1DX; arrowUp = 1EX; arrowDown = 1FX;

		update = 2;	(* notify options *)
		listUpdate = 3;
		guardCheck = 4;
		flushCaches = 5;	(* re-map labels for flushed string resources, after a language change *)
		
		maxAdr = 8;

		(* unicode non-characters, known in HostMenus, HostCFrames, Controls *)
		apps = 0FDD0X;	(* 'menu key',  WinApi.VK_APPS, bypasses BB's popup mechanism for edit controls *)
		ctrlZ = 0FDD1X;	(* bypasses BB's undo mechanism for edit controls *)
		ctrlA = 0FDD2X;	(* send ctrl-A (select all) to edit controls *)

	TYPE
		Prop* = POINTER TO RECORD (Properties.Property)
			opt*: ARRAY 5 OF BOOLEAN;
			link*: Dialog.String;
			label*: Dialog.String;
			guard*: Dialog.String;
			notifier*: Dialog.String;
			level*: INTEGER
		END;
		
		Directory* = POINTER TO ABSTRACT RECORD END;

		Control* = POINTER TO ABSTRACT RECORD (Views.View)
			item-: Meta.Item;
			disabled-, undef-, readOnly-, customFont-: BOOLEAN;
			font-: Fonts.Font;
			label-: Dialog.String;
			prop-: Prop;
			adr: ARRAY maxAdr OF INTEGER;
			num: INTEGER;
			stamp: INTEGER;
			shortcut: CHAR;
			guardErr, notifyErr: BOOLEAN
		END;

		DefaultsPref* = RECORD (Properties.Preference)
			disabled*: BOOLEAN;	(** OUT, preset to ~c.item.Valid() *)
			undef*: BOOLEAN;	(** OUT, preset to FALSE *)
			readOnly*: BOOLEAN	(** OUT, preset to c.item.vis = readOnly *)
		END;

		PropPref* = RECORD (Properties.Preference)
			valid*: SET	(** OUT, preset to {link, label, guard, notifier, customFont} *)
		END;

		PushButton = POINTER TO RECORD (Control) END;

		CheckBox = POINTER TO RECORD (Control) END;

		RadioButton = POINTER TO RECORD (Control) END;

		Field = POINTER TO RECORD (Control)
			maxLen: INTEGER
		END;

		UpDownField = POINTER TO RECORD (Control)
			min, max, inc: INTEGER
		END;

		DateField = POINTER TO RECORD (Control) 
			selection: INTEGER	(* 0: no selection, 1..n-1: this part selected, -1: part n selected *)
		END;

		TimeField = POINTER TO RECORD (Control) 
			selection: INTEGER
		END;

		ColorField = POINTER TO RECORD (Control) END;

		ListBox = POINTER TO RECORD (Control) END;

		SelectionBox = POINTER TO RECORD (Control) END;

		ComboBox = POINTER TO RECORD (Control) END;

		Caption = POINTER TO RECORD (Control) END;

		Group = POINTER TO RECORD (Control) END;

		TreeControl = POINTER TO RECORD (Control) END;

		StdDirectory = POINTER TO RECORD (Directory) END;

		Op = POINTER TO RECORD (Stores.Operation)
			ctrl: Control;
			prop: Prop
		END;

		FontOp = POINTER TO RECORD (Stores.Operation)
			ctrl: Control;
			font: Fonts.Font;
			custom: BOOLEAN
		END;

		NotifyMsg = RECORD (Views.NotifyMsg)
			frame: Views.Frame;
			op, from, to: INTEGER
		END;

		UpdateCachesMsg = RECORD (Views.UpdateCachesMsg) END;

		SelectPtr = POINTER TO Dialog.Selection;
		
		ProcValue = RECORD (Meta.Value) p*: PROCEDURE END;
		SelectValue = RECORD (Meta.Value) p*: SelectPtr END;
		GuardProcVal = RECORD (Meta.Value) p*: Dialog.GuardProc END;
		NotifyProcValOld = RECORD (Meta.Value) p*: PROCEDURE (op, from, to: INTEGER) END;
		GuardProcPVal = RECORD (Meta.Value) p*: PROCEDURE(n: INTEGER; VAR p: Dialog.Par) END;
		NotifyProcPVal = RECORD (Meta.Value) p*: PROCEDURE(n, op, f, t: INTEGER) END;

		Param = RECORD from, to, i: INTEGER; n: Dialog.String END;
		
		TVParam = RECORD l: INTEGER; e: BOOLEAN; nodeIn, nodeOut: Dialog.TreeNode END; 

		Action = POINTER TO RECORD (Services.Action) 
			w: Windows.Window;
			resolution, cnt: INTEGER
		END;

		TrapCleaner = POINTER TO RECORD (Kernel.TrapCleaner) END;

	VAR
		dir-, stdDir-: Directory;
		par-: Control;
		stamp: INTEGER;
		action: Action;
		cleaner:  TrapCleaner;
		cleanerInstalled: INTEGER;


	(** Cleaner **)

	PROCEDURE (c: TrapCleaner) Cleanup;
	BEGIN
		par := NIL;
		cleanerInstalled := 0
	END Cleanup;


	PROCEDURE (c: Control) Update- (f: Views.Frame; op, from, to: INTEGER), NEW, EMPTY;
	PROCEDURE (c: Control) UpdateList- (f: Views.Frame), NEW, EMPTY;
	PROCEDURE (c: Control) CheckLink- (VAR ok: BOOLEAN), NEW, EMPTY;
	PROCEDURE (c: Control) HandlePropMsg2- (VAR p: Views.PropMessage), NEW, EMPTY;
	PROCEDURE (c: Control) HandleViewMsg2- (f: Views.Frame; VAR msg: Views.Message), NEW, EMPTY;
	PROCEDURE (c: Control) HandleCtrlMsg2- (f: Views.Frame; VAR msg: Views.CtrlMessage;
																	VAR focus: Views.View), NEW, EMPTY;
	PROCEDURE (c: Control) Externalize2- (VAR wr: Stores.Writer), NEW, EMPTY;
	PROCEDURE (c: Control) Internalize2- (VAR rd: Stores.Reader), NEW, EMPTY;


	(* auxiliary procedures *)

	PROCEDURE IsShortcut (ch: CHAR; c: Control): BOOLEAN;
	BEGIN
		RETURN Strings.Upper(ch) = c.shortcut
	END IsShortcut;

	PROCEDURE ExtractShortcut (c: Control);
		VAR label: Dialog.String; i: INTEGER; ch, sCh: CHAR;
	BEGIN
		Dialog.MapString(c.label, label);
		i := 0; ch := label[0]; sCh := "&";
		WHILE sCh = "&" DO
			WHILE (ch # 0X) & (ch # "&") DO INC(i); ch := label[i] END;
			IF ch = 0X THEN sCh := 0X
			ELSE INC(i); sCh := label[i]; INC(i); ch := label[i]
			END
		END;
		c.shortcut := Strings.Upper(sCh)
	END ExtractShortcut;

	PROCEDURE GetGuardProc (name: ARRAY OF CHAR; VAR i: Meta.Item; VAR err: BOOLEAN;
												VAR par: BOOLEAN; VAR n: INTEGER);
		VAR j, k, e: INTEGER; num: ARRAY 32 OF CHAR;
	BEGIN
		j := 0;
		WHILE (name[j] # 0X) & (name[j] # "(") DO INC(j) END;
		IF name[j] = "(" THEN
			INC(j); k := 0;
			WHILE (name[j] # 0X) & (name[j] # ")") DO num[k] := name[j]; INC(j); INC(k) END;
			IF (name[j] = ")") & (name[j+1] = 0X) THEN
				num[k] := 0X; Strings.StringToInt(num, n, e);
				IF e = 0 THEN
					name[j - k - 1] := 0X;
					Meta.LookupPath(name, i); par := TRUE
				ELSE
					IF ~err THEN
						Dialog.ShowParamMsg("#System:SyntaxErrorIn", name, "", "");
						err := TRUE
					END;
					Meta.Lookup("", i);
					RETURN
				END
			ELSE
				IF ~err THEN
					Dialog.ShowParamMsg("#System:SyntaxErrorIn", name, "", "");
					err := TRUE
				END;
				Meta.Lookup("", i);
				RETURN
			END
		ELSE
			Meta.LookupPath(name, i); par := FALSE
		END;
		IF (i.obj = Meta.procObj) OR (i.obj = Meta.varObj) & (i.typ = Meta.procTyp) THEN (*ok *)
		ELSE
			IF ~err THEN
				IF i.obj = Meta.undef THEN
					Dialog.ShowParamMsg("#System:NotFound", name, "", "")
				ELSE
					Dialog.ShowParamMsg("#System:HasWrongType", name, "", "")
				END;
				err := TRUE
			END;
			Meta.Lookup("", i)
		END
	END GetGuardProc;
	
	PROCEDURE CallGuard (c: Control);
		VAR ok, up: BOOLEAN; n: INTEGER; dpar: Dialog.Par; p: Control;
			v: GuardProcVal; vp: GuardProcPVal; i: Meta.Item; pref: DefaultsPref;
	BEGIN
		Controllers.SetCurrentPath(Controllers.targetPath);
		pref.disabled := ~c.item.Valid();
		pref.undef := FALSE;
		pref.readOnly := c.item.vis = Meta.readOnly;
		Views.HandlePropMsg(c, pref);
		c.disabled := pref.disabled;
		c.undef := pref.undef;
		c.readOnly := pref.readOnly;
		c.label := c.prop.label$;
		IF ~c.disabled & (c.prop.guard # "") & ~c.guardErr THEN
			IF cleanerInstalled = 0 THEN Kernel.PushTrapCleaner(cleaner) END;
			INC(cleanerInstalled);
			p := par; par := c;
			dpar.disabled := FALSE; dpar.undef := FALSE;
			dpar.readOnly := c.readOnly;
			dpar.checked := FALSE; dpar.label := c.label$;
			GetGuardProc(c.prop.guard, i, c.guardErr, up, n);
			IF i.obj # Meta.undef THEN
				IF up THEN	(* call with numeric parameter *)
					i.GetVal(vp, ok);
					IF ok THEN vp.p(n, dpar) END
				ELSE
					i.GetVal(v, ok);
					IF ok THEN v.p(dpar) END
				END;
				IF ok THEN
					c.disabled := dpar.disabled;
					c.undef := dpar.undef;
					IF dpar.readOnly THEN c.readOnly := TRUE END;
					IF dpar.label # c.label THEN c.label := dpar.label END
				ELSIF ~c.guardErr THEN
					Dialog.ShowParamMsg("#System:HasWrongType", c.prop.guard, "", "");
					c.guardErr := TRUE
				END
			END;
			par := p;
			DEC(cleanerInstalled);
			IF cleanerInstalled = 0 THEN Kernel.PopTrapCleaner(cleaner) END
		END;
		ExtractShortcut(c);
		Controllers.ResetCurrentPath()
	END CallGuard;

	PROCEDURE CallNotifier (c: Control; op, from, to: INTEGER);
		VAR ok, up: BOOLEAN; n: INTEGER; vold: NotifyProcValOld; vp: NotifyProcPVal;
			i: Meta.Item; p: Control;
	BEGIN
		IF c.prop.notifier # "" THEN
			IF cleanerInstalled = 0 THEN Kernel.PushTrapCleaner(cleaner) END;
			INC(cleanerInstalled);
			p := par; par := c;
			IF c.prop.notifier[0] = "!" THEN
				IF op = Dialog.pressed THEN
					c.prop.notifier[0] := " ";
					Dialog.ShowStatus(c.prop.notifier);
					c.prop.notifier[0] := "!"
				ELSIF op = Dialog.released THEN
					Dialog.ShowStatus("")
				END
			ELSE
				GetGuardProc(c.prop.notifier, i, c.notifyErr, up, n);
				IF i.obj # Meta.undef THEN
					IF up THEN	(* call with numeric parameter *)
						i.GetVal(vp, ok);
						IF ok THEN vp.p(n, op, from, to) END
					ELSE
						i.GetVal(vold, ok);
						IF ok THEN vold.p(op, from, to) END
					END;
					IF ~ok & ~c.notifyErr THEN
						Dialog.ShowParamMsg("#System:HasWrongType", c.prop.notifier, "", "");
						c.notifyErr := TRUE
					END
				END
			END;
			par := p;
			DEC(cleanerInstalled);
			IF cleanerInstalled = 0 THEN Kernel.PopTrapCleaner(cleaner) END
		END
	END CallNotifier;

	PROCEDURE DCHint (modifiers: SET): INTEGER;
	BEGIN
		IF Controllers.doubleClick IN modifiers THEN RETURN 1
		ELSE RETURN 0
		END
	END DCHint;

	PROCEDURE Notify* (c: Control; f: Views.Frame; op, from, to: INTEGER);
		VAR msg: NotifyMsg;
	BEGIN
		IF ~c.readOnly & ~ c.disabled THEN
			CallNotifier(c, op, from, to);
			IF op >= Dialog.changed THEN
				msg.id0 := c.item.adr; msg.id1 := msg.id0 + c.item.Size(); msg.frame := f;
				msg.op := op; msg.from := from; msg.to := to;
				msg.opts := {update, guardCheck};
				Views.Omnicast(msg)
			END
		END
	END Notify;

	PROCEDURE NotifyFlushCaches*;
		VAR msg: NotifyMsg;
	BEGIN
		msg.opts := {flushCaches}; msg.id0 := 0; msg.id1 := 0;
		Views.Omnicast(msg)
	END NotifyFlushCaches;
	
	PROCEDURE GetName (VAR path, name: ARRAY OF CHAR; VAR i: INTEGER);
		VAR j: INTEGER; ch: CHAR;
	BEGIN
		j := 0; ch := path[i];
		WHILE (j < LEN(name) - 1) & Strings.IsIdent(ch) DO
			name[j] := ch; INC(i); INC(j); ch := path[i]
		END;
		IF (ch = 0X) OR (ch = ".") OR (ch = "[") OR (ch = "^") THEN name[j] := 0X
		ELSE name[0] := 0X
		END
	END GetName;

	PROCEDURE LookupPath (path: ARRAY OF CHAR; VAR i: Meta.Item;
												VAR adr: ARRAY OF INTEGER; VAR num: INTEGER);
		VAR j, n: INTEGER; name: Meta.Name; ch: CHAR;
	BEGIN
		path[LEN(path) - 1] := 0X; j := 0; num := 0;
		GetName(path, name, j); Meta.Lookup(name, i);
		IF (i.obj = Meta.modObj) & (path[j] = ".") THEN
			INC(j); GetName(path, name, j);
			i.Lookup(name, i); ch := path[j]; INC(j);
			WHILE i.obj = Meta.varObj DO
				adr[num] := i.adr;
				IF num < LEN(adr) - 1 THEN INC(num) END;
				IF ch = 0X THEN RETURN 
				ELSIF i.typ IN {Meta.ptrTyp, Meta.anyPtrTyp, Meta.sysPtrTyp} THEN
					IF ch = "^" THEN ch := path[j]; INC(j) END;
					i.Deref(i)
				ELSIF (i.typ = Meta.recTyp) & (ch = ".") THEN
					GetName(path, name, j); i.Lookup(name, i);
					ch := path[j]; INC(j)
				ELSIF (i.typ = Meta.arrTyp) & (ch = "[") THEN
					ch := path[j]; INC(j); n := 0;
					WHILE (ch >= "0") & (ch <= "9") DO n := 10 * n + ORD(ch) - ORD("0"); ch := path[j]; INC(j) END;
					IF ch = "]" THEN ch := path[j]; INC(j); i.Index(n, i) ELSE Meta.Lookup("", i) END
				ELSE Meta.Lookup("", i)
				END
			END
		ELSE
			Meta.LookupPath(path, i); num := 0;
			IF i.obj = Meta.varObj THEN adr[0] := i.adr; num := 1
			ELSIF i.obj # Meta.procObj THEN Meta.Lookup("", i)
			END
		END
	END LookupPath;

	PROCEDURE Sort (VAR adr: ARRAY OF INTEGER; num: INTEGER);
		VAR i, j, p: INTEGER;
	BEGIN
		i := 1;
		WHILE i < num DO
			p := adr[i]; j := i;
			WHILE (j >= 1) & (adr[j - 1] > p) DO adr[j] := adr[j - 1]; DEC(j) END;
			adr[j] := p; INC(i)
		END
	END Sort;

	PROCEDURE GetTypeName (IN item: Meta.Item; OUT name: Meta.Name);
		VAR mod: Meta.Name;
	BEGIN
		IF (item.typ = Meta.recTyp) THEN
			item.GetTypeName(mod, name);
			IF (mod = "Dialog") OR (mod = "Dates") THEN (* ok *)
			ELSE name := ""
			END
		ELSE name := ""
		END
	END GetTypeName;

	PROCEDURE OpenLink* (c: Control; p: Prop);
		VAR ok: BOOLEAN;
	BEGIN
		ASSERT(c # NIL, 20); ASSERT(p # NIL, 21);
		c.num := 0;
		c.prop := Properties.CopyOf(p)(Prop);
		IF c.font = NIL THEN
			IF c.customFont THEN c.font := StdCFrames.defaultLightFont
			ELSE c.font := StdCFrames.defaultFont
			END
		END;
		c.guardErr := FALSE; c.notifyErr := FALSE;
		LookupPath(p.link, c.item, c.adr, c.num);
		IF c.item.obj = Meta.varObj THEN
			Sort(c.adr, c.num);
			ok := TRUE; c.CheckLink(ok);
			IF ~ok THEN
				Meta.Lookup("", c.item);
				Dialog.ShowParamMsg("#System:HasWrongType", p.link, "", "")
			END
		ELSE
			Meta.Lookup("", c.item); c.num := 0
		END;
		CallGuard(c);
		c.stamp := stamp
	END OpenLink;


	(** Prop **)

	PROCEDURE (p: Prop) IntersectWith* (q: Properties.Property; OUT equal: BOOLEAN);
		VAR valid: SET;
	BEGIN
		WITH q: Prop DO
			valid := p.valid * q.valid; equal := TRUE;
			IF p.link # q.link THEN EXCL(valid, link) END;
			IF p.label # q.label THEN EXCL(valid, label) END;
			IF p.guard # q.guard THEN EXCL(valid, guard) END;
			IF p.notifier # q.notifier THEN EXCL(valid, notifier) END;
			IF p.level # q.level THEN EXCL(valid, level) END;
			IF p.opt[0] # q.opt[0] THEN EXCL(valid, opt0) END;
			IF p.opt[1] # q.opt[1] THEN EXCL(valid, opt1) END;
			IF p.opt[2] # q.opt[2] THEN EXCL(valid, opt2) END;
			IF p.opt[3] # q.opt[3] THEN EXCL(valid, opt3) END;
			IF p.opt[4] # q.opt[4] THEN EXCL(valid, opt4) END;
			IF p.valid # valid THEN p.valid := valid; equal := FALSE END
		END
	END IntersectWith;


	(* Control *)

	PROCEDURE (c: Control) CopyFromSimpleView2- (source: Control), NEW, EMPTY;

	PROCEDURE (c: Control) CopyFromSimpleView- (source: Views.View);
	BEGIN
		WITH source: Control DO
			c.item := source.item;
			c.adr := source.adr;
			c.num := source.num;
			c.disabled := source.disabled;
			c.undef := source.undef;
			c.readOnly := source.readOnly;
			c.shortcut := source.shortcut;
			c.customFont := source.customFont;
			c.font := source.font;
			c.label := source.label$;
			c.prop := Properties.CopyOf(source.prop)(Prop);
			c.CopyFromSimpleView2(source)
		END
	END CopyFromSimpleView;

	PROCEDURE (c: Control) Internalize- (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER; x, def, canc, sort: BOOLEAN;
	BEGIN
		c.Internalize^(rd);
		IF rd.cancelled THEN RETURN END;
		rd.ReadVersion(minVersion, maxBaseVersion, thisVersion);
		IF rd.cancelled THEN RETURN END;
		NEW(c.prop);
		IF thisVersion >= 3 THEN
			rd.ReadString(c.prop.link);
			rd.ReadString(c.prop.label);
			rd.ReadString(c.prop.guard);
			rd.ReadString(c.prop.notifier);
			rd.ReadInt(c.prop.level);
			rd.ReadBool(c.customFont);
			rd.ReadBool(c.prop.opt[0]);
			rd.ReadBool(c.prop.opt[1]);
			rd.ReadBool(c.prop.opt[2]);
			rd.ReadBool(c.prop.opt[3]);
			rd.ReadBool(c.prop.opt[4]);
			IF c.customFont & (thisVersion = 4) THEN
				Views.ReadFont(rd, c.font)
			END
		ELSE
			rd.ReadXString(c.prop.link);
			rd.ReadXString(c.prop.label);
			rd.ReadXString(c.prop.guard);
			c.prop.notifier := "";
			c.prop.opt[2] := FALSE;
			c.prop.opt[3] := FALSE;
			c.prop.opt[4] := FALSE;
			sort := FALSE;
			IF thisVersion = 2 THEN
				rd.ReadXString(c.prop.notifier);
				rd.ReadBool(sort);
				rd.ReadBool(c.prop.opt[multiLine])
			ELSIF thisVersion = 1 THEN
				rd.ReadXString(c.prop.notifier);
				rd.ReadBool(sort)
			END;
			rd.ReadBool(x);	(* free, was sed for prop.element *)
			rd.ReadBool(def); 
			rd.ReadBool(canc);
			rd.ReadXInt(c.prop.level);
			rd.ReadBool(c.customFont);
			c.prop.opt[default] := def OR sort OR (c IS Field);
			c.prop.opt[cancel] := canc
		END;
		c.Internalize2(rd);
		OpenLink(c, c.prop)
	END Internalize;

	PROCEDURE (c: Control) Externalize- (VAR wr: Stores.Writer);
	BEGIN
		c.Externalize^(wr);
		wr.WriteVersion(maxBaseVersion);
		wr.WriteString(c.prop.link);
		wr.WriteString(c.prop.label);
		wr.WriteString(c.prop.guard);
		wr.WriteString(c.prop.notifier);
		wr.WriteInt(c.prop.level);
		wr.WriteBool(c.customFont);
		wr.WriteBool(c.prop.opt[0]);
		wr.WriteBool(c.prop.opt[1]);
		wr.WriteBool(c.prop.opt[2]);
		wr.WriteBool(c.prop.opt[3]);
		wr.WriteBool(c.prop.opt[4]);
		IF c.customFont THEN Views.WriteFont(wr, c.font) END;
		c.Externalize2(wr)
	END Externalize;

	PROCEDURE (c: Control) HandleViewMsg- (f: Views.Frame; VAR msg: Views.Message);
		VAR disabled, undef, readOnly, done, allDone: BOOLEAN; i: INTEGER; lbl: Dialog.String;
	BEGIN
		WITH msg: Views.NotifyMsg DO
			done := FALSE; allDone := FALSE;
			IF guardCheck IN msg.opts THEN
				(* should call c.Update for each frame but Views.Update only once *)
				WITH f: StdCFrames.Caption DO lbl := f.label$
				| f: StdCFrames.PushButton DO lbl := f.label$
				| f: StdCFrames.RadioButton DO lbl := f.label$
				| f: StdCFrames.CheckBox DO lbl := f.label$
				| f: StdCFrames.Group DO lbl := f.label$
				ELSE lbl := c.label$
				END;
				WITH f: StdCFrames.Frame DO
					disabled := f.disabled; undef := f.undef; readOnly := f.readOnly
				ELSE
					disabled := c.disabled; undef := c.undef; readOnly := c.readOnly
				END;
				CallGuard(c);
				IF (c.disabled # disabled) OR (c.undef # undef)
				OR (c.readOnly # readOnly) OR (c.label # lbl) THEN
					WITH f: StdCFrames.Frame DO
						IF f.noRedraw THEN
							f.disabled := c.disabled;
							f.undef := c.undef;
							f.readOnly := c.readOnly;
							c.Update(f, 0, 0, 0); done := TRUE
						ELSE Views.Update(c, Views.rebuildFrames); allDone := TRUE
						END
					ELSE Views.Update(c, Views.keepFrames); done := TRUE
					END
				END
			END;
			IF flushCaches IN msg.opts THEN
				Views.Update(c, Views.rebuildFrames)
			END;
			i := 0; WHILE (i < c.num) & (c.adr[i] < msg.id0) DO INC(i) END;
			IF (i < c.num) & (c.adr[i] < msg.id1) & ~allDone THEN
				IF (update IN msg.opts) & ~done THEN
					WITH msg: NotifyMsg DO
						IF msg.frame # f THEN	(* don't update origin frame *)
							c.Update(f, msg.op, msg.from, msg.to)
						END
					ELSE
						c.Update(f, 0, 0, 0)
					END
				END;
				IF listUpdate IN msg.opts THEN
					c.UpdateList(f)
				END
			END
		| msg: Views.UpdateCachesMsg DO
			IF c.stamp # stamp THEN
				OpenLink(c, c.prop);
				IF msg IS UpdateCachesMsg THEN
					Views.Update(c, Views.rebuildFrames)
				END
			END
		ELSE
		END;
		c.HandleViewMsg2(f, msg)
	END HandleViewMsg;

	PROCEDURE (c: Control) HandleCtrlMsg* (f: Views.Frame; VAR msg: Controllers.Message;
																							VAR focus: Views.View);
		VAR sp: Properties.SizeProp; p: Control; dcOk: BOOLEAN;
	BEGIN
		IF cleanerInstalled = 0 THEN Kernel.PushTrapCleaner(cleaner) END;
		INC(cleanerInstalled);
		p := par; par := c;
		WITH msg: Properties.PollPickMsg DO
			msg.dest := f
		| msg: Properties.PickMsg DO
			NEW(sp); sp.known := {Properties.width, Properties.height}; sp.valid := sp.known;
			c.context.GetSize(sp.width, sp.height);
			Properties.Insert(msg.prop, sp)
		| msg: Controllers.TrackMsg DO
			IF ~c.disabled THEN
				dcOk := TRUE;
				IF f IS StdCFrames.Frame THEN dcOk := f(StdCFrames.Frame).DblClickOk(msg.x, msg.y) END;
				IF (DCHint(msg.modifiers) = 1)  & dcOk THEN
					(* double click *)
					Notify(c, f, Dialog.pressed, 1, 0)
				ELSE
					Notify(c, f, Dialog.pressed, 0, 0)
				END
			END
		ELSE
		END;
		c.HandleCtrlMsg2(f, msg, focus);
		WITH msg: Controllers.TrackMsg DO
			IF ~c.disabled THEN
				Notify(c, f, Dialog.released, 0, 0)
			END
		ELSE
		END;
		par := p;
		DEC(cleanerInstalled);
		IF cleanerInstalled = 0 THEN Kernel.PopTrapCleaner(cleaner) END
	END HandleCtrlMsg;

	PROCEDURE (c: Control) HandlePropMsg- (VAR msg: Properties.Message);
		VAR fpref: Properties.FocusPref; stp: Properties.StdProp;
			cp: Prop; ppref: PropPref; op: Op; valid: SET; p: Properties.Property;
			fop: FontOp; face: Fonts.Typeface; size, weight: INTEGER; style: SET;
	BEGIN
		WITH msg: Properties.ControlPref DO
			IF (msg.char = lineChar) OR (msg.char = esc) THEN msg.accepts := FALSE END;
			IF ~c.disabled & ~c.readOnly & IsShortcut(msg.char, c) THEN
				fpref.hotFocus := FALSE; fpref.setFocus := FALSE; fpref.atLocation := FALSE;
				Views.HandlePropMsg(c, fpref);
				IF fpref.setFocus THEN msg.getFocus := TRUE END
			END
		| msg: Properties.PollMsg DO
			ppref.valid := {link, label, notifier, guard};
			Views.HandlePropMsg(c, ppref);
			cp := Properties.CopyOf(c.prop)(Prop);
			cp.valid := ppref.valid; cp.known := cp.valid; cp.readOnly := {};
			Properties.Insert(msg.prop, cp);
			NEW(stp);
			stp.valid := {Properties.typeface..Properties.weight};
			stp.known := stp.valid;
			IF c.customFont THEN stp.typeface := c.font.typeface$
			ELSE stp.typeface := Fonts.default
			END;
			stp.size := c.font.size; stp.style.val := c.font.style; stp.weight := c.font.weight;
			stp.style.mask := {Fonts.italic, Fonts.strikeout, Fonts.underline};
			Properties.Insert(msg.prop, stp)
		| msg: Properties.SetMsg DO
			p := msg.prop; op := NIL; fop := NIL;
			WHILE (p # NIL) & (op = NIL) DO
				WITH p: Prop DO
					ppref.valid := {link, label, notifier, guard};
					Views.HandlePropMsg(c, ppref);
					valid := p.valid * ppref.valid;
					IF valid # {} THEN
						NEW(op); 
						op.ctrl := c; 
						op.prop := Properties.CopyOf(p)(Prop); op.prop.valid := valid
					END
				| p: Properties.StdProp DO
					valid := p.valid * {Properties.typeface..Properties.weight};
					IF valid # {} THEN
						NEW(fop); fop.ctrl := c;
						face := c.font.typeface$; size := c.font.size; style := c.font.style; weight := c.font.weight;
						IF Properties.typeface IN p.valid THEN face := p.typeface$;
							IF face = Fonts.default THEN face := StdCFrames.defaultFont.typeface END
						END;
						IF Properties.size IN p.valid THEN size := p.size END;
						IF Properties.style IN p.valid THEN
							style := (p.style.val * p.style.mask) + (style - p.style.mask)
						END;
						IF Properties.weight IN p.valid THEN weight := p.weight END;
						fop.custom := TRUE;
						fop.font := Fonts.dir.This(face, size, style, weight);
						IF (fop.font.typeface = StdCFrames.defaultFont.typeface)
						& (fop.font.size = StdCFrames.defaultFont.size)
						& (fop.font.style = StdCFrames.defaultFont.style)
						& (fop.font.weight = StdCFrames.defaultFont.weight) THEN
							fop.custom := FALSE;
							fop.font := StdCFrames.defaultFont
						END
					END
				ELSE
				END;
				p := p.next
			END;
			IF op # NIL THEN Views.Do(c, "#System:SetProp", op) END;
			IF fop # NIL THEN Views.Do(c, "#System:SetProp", fop) END
		| msg: Properties.TypePref DO
			IF Services.Is(c, msg.type) THEN msg.view := c END
		ELSE
		END;
		c.HandlePropMsg2(msg)
	END HandlePropMsg;


	(* Op *)

	PROCEDURE (op: Op) Do;
		VAR c: Control; prop: Prop;
	BEGIN
		c := op.ctrl;
		prop := Properties.CopyOf(c.prop)(Prop);
		prop.valid := op.prop.valid;	(* fields to be restored *)
		IF link IN op.prop.valid THEN c.prop.link := op.prop.link END;
		IF label IN op.prop.valid THEN c.prop.label := op.prop.label END;
		IF guard IN op.prop.valid THEN c.prop.guard := op.prop.guard END;
		IF notifier IN op.prop.valid THEN c.prop.notifier := op.prop.notifier END;
		IF level IN op.prop.valid THEN c.prop.level := op.prop.level END;
		IF opt0 IN op.prop.valid THEN c.prop.opt[0] := op.prop.opt[0] END;
		IF opt1 IN op.prop.valid THEN c.prop.opt[1] := op.prop.opt[1] END;
		IF opt2 IN op.prop.valid THEN c.prop.opt[2] := op.prop.opt[2] END;
		IF opt3 IN op.prop.valid THEN c.prop.opt[3] := op.prop.opt[3] END;
		IF opt4 IN op.prop.valid THEN c.prop.opt[4] := op.prop.opt[4] END;
		IF c.prop.guard # prop.guard THEN c.guardErr := FALSE END;
		IF c.prop.notifier # prop.notifier THEN c.notifyErr := FALSE END;
		IF c.prop.link # prop.link THEN OpenLink(c, c.prop) ELSE CallGuard(c) END;
		op.prop := prop;
		Views.Update(c, Views.rebuildFrames)
	END Do;

	PROCEDURE (op: FontOp) Do;
		VAR c: Control; custom: BOOLEAN; font: Fonts.Font;
	BEGIN
		c := op.ctrl;
		custom := c.customFont; c.customFont := op.custom; op.custom := custom;
		font := c.font; c.font := op.font; op.font := font;
		Views.Update(c, Views.rebuildFrames)
	END Do;


	(* ------------------------- standard controls ------------------------- *)

	PROCEDURE CatchCtrlMsg (c: Control; f: Views.Frame; VAR msg: Controllers.Message;
																				VAR focus: Views.View);
		VAR w, h: INTEGER;
	BEGIN
		IF ~c.disabled THEN
			WITH f: StdCFrames.Frame DO
				WITH msg: Controllers.PollCursorMsg DO
					f.GetCursor(msg.x, msg.y, msg.modifiers, msg.cursor)
				| msg: Controllers.PollOpsMsg DO
					msg.valid := {Controllers.pasteChar}
				| msg: Controllers.TrackMsg DO
					f.MouseDown(msg.x, msg.y, msg.modifiers)
				| msg: Controllers.MarkMsg DO
					f.Mark(msg.show, msg.focus)
				|msg: Controllers.WheelMsg DO
					c.context.GetSize(w, h);
					IF (0 <= msg.x) & (msg.x < w) & (0 <= msg.y) & (msg.y < h) THEN
						f.WheelMove(msg.x, msg.y, msg.op, msg.nofLines, msg.done)
					END
				ELSE
				END
			END
		END
	END CatchCtrlMsg;
	

	(** Directory **)

	PROCEDURE (d: Directory) NewPushButton* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewCheckBox* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewRadioButton* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewField* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewUpDownField* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewDateField* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewTimeField* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewColorField* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewListBox* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewSelectionBox* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewComboBox* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewCaption* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewGroup* (p: Prop): Control, NEW, ABSTRACT;
	PROCEDURE (d: Directory) NewTreeControl* (p: Prop): Control, NEW, ABSTRACT;


	(* PushButton *)

	PROCEDURE Call (c: PushButton);
		VAR res: INTEGER; p: Control; ok: BOOLEAN; msg: Views.NotifyMsg;
	BEGIN
		IF c.item.Valid() & ((c.item.obj = Meta.procObj) OR (c.item.obj = Meta.varObj) & (c.item.typ = Meta.procTyp)) THEN
			IF cleanerInstalled = 0 THEN Kernel.PushTrapCleaner(cleaner) END;
			INC(cleanerInstalled);
			p := par; c.item.Call(ok); par := p;
			DEC(cleanerInstalled);
			IF cleanerInstalled = 0 THEN Kernel.PopTrapCleaner(cleaner) END;
			IF ~ok THEN Dialog.ShowMsg("#System:BehaviorNotAccessible") END
		ELSIF c.prop.link # "" THEN
			IF cleanerInstalled = 0 THEN Kernel.PushTrapCleaner(cleaner) END;
			INC(cleanerInstalled);
			p := par; par := c; Dialog.Call(c.prop.link, " ", res); par := p;
			DEC(cleanerInstalled);
			IF cleanerInstalled = 0 THEN Kernel.PopTrapCleaner(cleaner) END
		ELSE Dialog.ShowMsg("#System:NoBehaviorBound")
		END;
		msg.opts := {guardCheck};
		Views.Omnicast(msg)
	END Call;
	
	PROCEDURE Do (f: StdCFrames.PushButton);
	BEGIN
		Call(f.view(PushButton))
	END Do;

	PROCEDURE (c: PushButton) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, pbVersion, thisVersion)
	END Internalize2;

	PROCEDURE (c: PushButton) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(pbVersion)
	END Externalize2;

	PROCEDURE (c: PushButton) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.PushButton;
	BEGIN
		f := StdCFrames.dir.NewPushButton();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.label := c.label$;
		f.default := c.prop.opt[default];
		f.cancel := c.prop.opt[cancel];
		f.Do := Do;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: PushButton) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: PushButton) HandleCtrlMsg2 (f: Views.Frame; VAR msg: Controllers.Message;
																				VAR focus: Views.View);
	BEGIN
		IF ~c.disabled THEN
			WITH f: StdCFrames.Frame DO
				WITH msg: Controllers.EditMsg DO
					IF (msg.op = Controllers.pasteChar)
						& ((msg.char = lineChar)
							OR (msg.char = " ")
							OR (msg.char = esc) & c.prop.opt[cancel]
							OR IsShortcut(msg.char, c)) THEN f.KeyDown(msg.char, msg.modifiers) END
				ELSE
					CatchCtrlMsg(c, f, msg, focus)
				END
			END
		END
	END HandleCtrlMsg2;

	PROCEDURE (c: PushButton) HandlePropMsg2 (VAR msg: Properties.Message);
	BEGIN
		WITH msg: Properties.ControlPref DO
			msg.accepts := ~c.disabled & ((msg.char = lineChar) & c.prop.opt[default]
				OR (msg.char = esc) & c.prop.opt[cancel]
				OR IsShortcut(msg.char, c))
		| msg: Properties.FocusPref DO
			IF ~c.disabled & ~ c.readOnly THEN
				msg.hotFocus := TRUE; msg.setFocus := StdCFrames.setFocus
			END
		| msg: Properties.SizePref DO
			StdCFrames.dir.GetPushButtonSize(msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard, notifier, default, cancel}
		| msg: DefaultsPref DO
			IF c.prop.link # "" THEN msg.disabled := FALSE END
		ELSE
		END
	END HandlePropMsg2;
	
	PROCEDURE (c: PushButton) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		f(StdCFrames.PushButton).label := c.label$;
		f(StdCFrames.Frame).Update
	END Update;
	
	PROCEDURE (c: PushButton) CheckLink (VAR ok: BOOLEAN);
	BEGIN
		ok := c.item.typ = Meta.procTyp
	END CheckLink;


	(* CheckBox *)

	PROCEDURE GetCheckBox (f: StdCFrames.CheckBox; OUT x: BOOLEAN);
		VAR c: CheckBox;
	BEGIN
		x := FALSE;
		c := f.view(CheckBox);
		IF c.item.Valid() THEN
			IF c.item.typ = Meta.boolTyp THEN x := c.item.BoolVal()
			ELSIF c.item.typ = Meta.setTyp THEN x := c.prop.level IN c.item.SetVal()
			END
		END
	END GetCheckBox;

	PROCEDURE SetCheckBox (f: StdCFrames.CheckBox; x: BOOLEAN);
		VAR c: CheckBox; s: SET;
	BEGIN
		c := f.view(CheckBox);
		IF c.item.Valid() & ~c.readOnly THEN
			IF c.item.typ = Meta.boolTyp THEN
				c.item.PutBoolVal(x); Notify(c, f, Dialog.changed, 0, 0)
			ELSIF c.item.typ = Meta.setTyp THEN
				s := c.item.SetVal();
				IF x THEN INCL(s, c.prop.level) ELSE EXCL(s, c.prop.level) END;
				c.item.PutSetVal(s);
				IF x THEN Notify(c, f, Dialog.included, c.prop.level, c.prop.level)
				ELSE Notify(c, f, Dialog.excluded, c.prop.level, c.prop.level)
				END
			END
		END
	END SetCheckBox;

	PROCEDURE (c: CheckBox) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, cbVersion, thisVersion)
	END Internalize2;

	PROCEDURE (c: CheckBox) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(cbVersion)
	END Externalize2;

	PROCEDURE (c: CheckBox) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.CheckBox;
	BEGIN
		f :=  StdCFrames.dir.NewCheckBox();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.label := c.label$;
		f.Get := GetCheckBox;
		f.Set := SetCheckBox;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: CheckBox) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: CheckBox) HandleCtrlMsg2 (f: Views.Frame; VAR msg: Controllers.Message;
																		VAR focus: Views.View);
	BEGIN
		IF ~c.disabled & ~c.readOnly THEN
			WITH f: StdCFrames.Frame DO
				WITH msg: Controllers.EditMsg DO
					IF (msg.op = Controllers.pasteChar)
						& ((msg.char = " ") OR IsShortcut(msg.char, c)) THEN f.KeyDown(msg.char, msg.modifiers) END
				ELSE
					CatchCtrlMsg(c, f, msg, focus)
				END
			END
		END
	END HandleCtrlMsg2;

	PROCEDURE (c: CheckBox) HandlePropMsg2 (VAR msg: Properties.Message);
	BEGIN
		WITH msg: Properties.ControlPref DO
			IF ~c.disabled & ~c.readOnly THEN
				IF (msg.char = tab) OR (msg.char = ltab) THEN
					(* tabs set focus to first checkbox only *)
					IF (msg.focus # NIL) & (msg.focus IS CheckBox)
							& (msg.focus(CheckBox).item.adr = c.item.adr) THEN
						msg.getFocus := FALSE
					END
				ELSIF (msg.char >= arrowLeft) & (msg.char <= arrowDown) THEN
					(* arrows set focus to next checkbox bound to same variable *)
					msg.getFocus := StdCFrames.setFocus
						& (msg.focus # NIL)
						& (msg.focus IS CheckBox)
						& (msg.focus(CheckBox).item.adr = c.item.adr);
					msg.accepts := msg.getFocus & (msg.focus # c)
				ELSIF IsShortcut(msg.char, c) THEN
					msg.accepts := TRUE; msg.getFocus := StdCFrames.setFocus
				ELSIF msg.char # " " THEN
					msg.accepts := FALSE
				END
			END
		| msg: Properties.FocusPref DO
			IF ~c.disabled & ~c.readOnly THEN
				msg.hotFocus := TRUE; msg.setFocus := StdCFrames.setFocus
			END
		| msg: Properties.SizePref DO
			StdCFrames.dir.GetCheckBoxSize(msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard, notifier, level}
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: CheckBox) CheckLink (VAR ok: BOOLEAN);
	BEGIN
		ok := (c.item.typ = Meta.boolTyp) OR (c.item.typ = Meta.setTyp)
	END CheckLink;

	PROCEDURE (c: CheckBox) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		IF (op = 0) OR (c.item.typ = Meta.boolTyp) OR (c.prop.level = to) THEN
			f(StdCFrames.CheckBox).label := c.label$;
			f(StdCFrames.Frame).Update
		END
	END Update;
	

	(* RadioButton *)

	PROCEDURE GetRadioButton (f: StdCFrames.RadioButton; OUT x: BOOLEAN);
		VAR c: RadioButton;
	BEGIN
		x := FALSE;
		c := f.view(RadioButton);
		IF c.item.Valid() THEN
			IF c.item.typ = Meta.boolTyp THEN x := c.item.BoolVal() = (c.prop.level # 0)
			ELSE x := c.item.IntVal() = c.prop.level
			END
		END
	END GetRadioButton;

	PROCEDURE SetRadioButton (f: StdCFrames.RadioButton; x: BOOLEAN);
		VAR c: RadioButton; old: INTEGER;
	BEGIN
		IF x THEN
			c := f.view(RadioButton);
			IF c.item.Valid() & ~c.readOnly THEN
				IF c.item.typ = Meta.boolTyp THEN
					IF c.item.BoolVal() THEN old := 1 ELSE old := 0 END;
					IF c.prop.level # old THEN
						c.item.PutBoolVal(c.prop.level # 0);
						Notify(c, f, Dialog.changed, old, c.prop.level)
					END
				ELSE
					old := c.item.IntVal();
					IF c.prop.level # old THEN
						c.item.PutIntVal(c.prop.level);
						Notify(c, f, Dialog.changed, old, c.prop.level)
					END
				END
			END
		END
	END SetRadioButton;

	PROCEDURE (c: RadioButton) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, rbVersion, thisVersion)
	END Internalize2;

	PROCEDURE (c: RadioButton) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(rbVersion)
	END Externalize2;

	PROCEDURE (c: RadioButton) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.RadioButton;
	BEGIN
		f := StdCFrames.dir.NewRadioButton();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.label := c.label$;
		f.Get := GetRadioButton;
		f.Set := SetRadioButton;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: RadioButton) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: RadioButton) HandleCtrlMsg2 (f: Views.Frame; VAR msg: Controllers.Message;
																			VAR focus: Views.View);
	BEGIN
		IF ~c.disabled & ~c.readOnly THEN
			WITH f: StdCFrames.Frame DO
				WITH msg: Controllers.EditMsg DO
					IF (msg.op = Controllers.pasteChar)
						& ((msg.char <= " ") OR IsShortcut(msg.char, c)) THEN f.KeyDown(msg.char, msg.modifiers) END
				ELSE
					CatchCtrlMsg(c, f, msg, focus)
				END
			END
		END
	END HandleCtrlMsg2;

	PROCEDURE (c: RadioButton) HandlePropMsg2 (VAR msg: Properties.Message);
		VAR hot: BOOLEAN;
	BEGIN
		WITH msg: Properties.ControlPref DO
			IF ~c.disabled & ~c.readOnly THEN
				IF (msg.char = tab) OR (msg.char = ltab) THEN
					(* tabs set focus to active radio button only *)
					IF c.item.Valid() THEN
						IF c.item.typ = Meta.boolTyp THEN hot := c.item.BoolVal() = (c.prop.level # 0)
						ELSE hot := c.item.IntVal() = c.prop.level
						END
					ELSE hot := FALSE
					END;
					IF ~hot THEN msg.getFocus := FALSE END
				ELSIF (msg.char >= arrowLeft) & (msg.char <= arrowDown) THEN
					(* arrows set focus to next radio button bound to same variable *)
					msg.getFocus := StdCFrames.setFocus
						& (msg.focus # NIL) & (msg.focus IS RadioButton)
						& (msg.focus(RadioButton).item.adr = c.item.adr);
					msg.accepts := msg.getFocus & (msg.focus # c)
				ELSIF IsShortcut(msg.char, c) THEN
					msg.accepts := TRUE; msg.getFocus := StdCFrames.setFocus
				ELSIF msg.char # " " THEN
					msg.accepts := FALSE
				END
			END
		| msg: Properties.FocusPref DO
			IF ~c.disabled & ~c.readOnly THEN
				msg.hotFocus := TRUE; msg.setFocus := StdCFrames.setFocus
 			END
		| msg: Properties.SizePref DO
			StdCFrames.dir.GetRadioButtonSize(msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard, notifier, level}
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: RadioButton) CheckLink (VAR ok: BOOLEAN);
		VAR name: Meta.Name;
	BEGIN
		GetTypeName(c.item, name);
		IF name = "List" THEN c.item.Lookup("index", c.item) END;
		ok := (c.item.typ >= Meta.byteTyp) & (c.item.typ <= Meta.intTyp) OR (c.item.typ = Meta.boolTyp)
	END CheckLink;

	PROCEDURE (c: RadioButton) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		IF (op = 0) OR (c.prop.level = to) OR (c.prop.level = from) THEN
			f(StdCFrames.RadioButton).label := c.label$;
			f(StdCFrames.Frame).Update
		END
	END Update;
	

	(* Field *)

	PROCEDURE LongToString (x: LONGINT; OUT s: ARRAY OF CHAR);
		VAR d: ARRAY 24 OF CHAR; i, j: INTEGER;
	BEGIN
		IF x = MIN(LONGINT) THEN
			s := "-9223372036854775808"
		ELSE
			i := 0; j := 0;
			IF x < 0 THEN s[0] := "-"; i := 1; x := -x END;
			REPEAT d[j] := CHR(x MOD 10 + ORD("0")); INC(j); x := x DIV 10 UNTIL x = 0;
			WHILE j > 0 DO DEC(j); s[i] := d[j]; INC(i) END;
			s[i] := 0X
		END
	END LongToString;

	PROCEDURE StringToLong (IN s: ARRAY OF CHAR; OUT x: LONGINT; OUT res: INTEGER);
		VAR i, sign, d: INTEGER;
	BEGIN
		i := 0; sign := 1; x := 0; res := 0;
		WHILE s[i] = " " DO INC(i) END;
		IF s[i] = "-" THEN sign := -1; INC(i) END;
		WHILE s[i] = " " DO INC(i) END;
		IF s[i] = 0X THEN res := 2 END;
		WHILE (s[i] >= "0") & (s[i] <= "9") DO
			d := ORD(s[i]) - ORD("0"); INC(i);
			IF x <= (MAX(LONGINT) - d) DIV 10 THEN x := 10 * x + d
			ELSE res := 1
			END
		END;
		x := x * sign;
		IF s[i] # 0X THEN res := 2 END
	END StringToLong;

	PROCEDURE FixToInt (fix: ARRAY OF CHAR; OUT int: ARRAY OF CHAR; scale: INTEGER);
		VAR i, j: INTEGER;
	BEGIN
		IF scale > 24 THEN scale := 24 ELSIF scale < 0 THEN scale := 0 END;
		i := 0; j := 0;
		WHILE (fix[i] # ".") & (fix[i] # 0X) DO int[j] := fix[i]; INC(i); INC(j) END;
		IF fix[i] = "." THEN INC(i) END;
		WHILE (scale > 0) & (fix[i] >= "0") & (fix[i] <= "9") DO int[j] := fix[i]; INC(i); INC(j); DEC(scale) END;
		WHILE scale > 0 DO int[j] := "0"; INC(j); DEC(scale) END;
		int[j] := 0X
	END FixToInt;

	PROCEDURE IntToFix (int: ARRAY OF CHAR; OUT fix: ARRAY OF CHAR; scale: INTEGER);
		VAR i, j, n: INTEGER;
	BEGIN
		IF scale > 24 THEN scale := 24 ELSIF scale < 0 THEN scale := 0 END;
		n := LEN(int$); i := 0; j := 0;
		WHILE int[i] < "0" DO fix[j] := int[i]; INC(i); INC(j); DEC(n) END;
		IF n > scale THEN
			WHILE n > scale DO fix[j] := int[i]; INC(i); INC(j); DEC(n) END
		ELSE
			fix[j] := "0"; INC(j)
		END;
		fix[j] := "."; INC(j);
		WHILE n < scale DO fix[j] := "0"; INC(j); DEC(scale) END;
		WHILE n > 0 DO  fix[j] := int[i]; INC(i); INC(j); DEC(n) END;
		fix[j] := 0X
	END IntToFix;

	PROCEDURE GetField (f: StdCFrames.Field; OUT x: ARRAY OF CHAR);
		VAR c: Field; ok: BOOLEAN; b, v: Meta.Item; mod, name: Meta.Name;
	BEGIN
		x := "";
		c := f.view(Field);
		IF c.item.Valid() THEN
			IF c.item.typ = Meta.arrTyp THEN
				c.item.GetStringVal(x, ok)
			ELSIF c.item.typ IN {Meta.byteTyp, Meta.sIntTyp, Meta.intTyp} THEN
				Strings.IntToString(c.item.IntVal(), x);
				IF c.prop.level > 0 THEN IntToFix(x, x, c.prop.level) END
			ELSIF c.item.typ = Meta.longTyp THEN
				LongToString(c.item.LongVal(), x);
				IF c.prop.level > 0 THEN IntToFix(x, x, c.prop.level) END
			ELSIF c.item.typ = Meta.sRealTyp THEN
				IF c.prop.level <= 0 THEN
					Strings.RealToStringForm(c.item.RealVal(), 7, 0, c.prop.level, " ", x)
				ELSE
					Strings.RealToStringForm(c.item.RealVal(), c.prop.level, 0, 1, " ", x)
				END
			ELSIF c.item.typ = Meta.realTyp THEN
				IF c.prop.level <= 0 THEN
					Strings.RealToStringForm(c.item.RealVal(), 16, 0, c.prop.level, " ", x)
				ELSE
					Strings.RealToStringForm(c.item.RealVal(), c.prop.level, 0, 1, " ", x)
				END
			ELSIF c.item.typ = Meta.recTyp THEN
				c.item.GetTypeName(mod, name);
				IF mod = "Dialog" THEN
					IF name = "Currency" THEN
						c.item.Lookup("val", v); c.item.Lookup("scale", b);
						LongToString(v.LongVal(), x); IntToFix(x, x, b.IntVal())
					ELSE (* Combo *)
						c.item.Lookup("item", v); (* Combo *)
						IF v.typ = Meta.arrTyp THEN v.GetStringVal(x, ok) END
					END
				END
			END
		ELSE
			x := c.label$
		END
	END GetField;

	PROCEDURE SetField (f: StdCFrames.Field; IN x: ARRAY OF CHAR);
		VAR c: Field; ok: BOOLEAN; i, res, old: INTEGER; r, or: REAL; b, v: Meta.Item;
			mod, name: Meta.Name; long, long0: LONGINT;
			s: ARRAY 1024 OF CHAR;
	BEGIN
		c := f.view(Field);
		IF c.item.Valid() & ~c.readOnly THEN
			CASE c.item.typ OF
			| Meta.arrTyp:
				c.item.GetStringVal(s, ok);
				IF ~ok OR (s$ # x$) THEN
					c.item.PutStringVal(x, ok);
					IF ok THEN Notify(c, f, Dialog.changed, 0, 0) ELSE Dialog.Beep; f.Update END
				END
			| Meta.byteTyp:
				IF x = "" THEN i := 0; res := 0
				ELSIF c.prop.level > 0 THEN FixToInt(x, s, c.prop.level); Strings.StringToInt(s, i, res)
				ELSE Strings.StringToInt(x, i, res)
				END;
				IF (res = 0) & (i >= MIN(BYTE)) & (i <= MAX(BYTE)) THEN
					old := c.item.IntVal();
					IF i # old THEN c.item.PutIntVal(i); Notify(c, f, Dialog.changed, old, i) END
				ELSIF x # "-" THEN
					Dialog.Beep; f.Update
				END
			| Meta.sIntTyp:
				IF x = "" THEN i := 0; res := 0
				ELSIF c.prop.level > 0 THEN FixToInt(x, s, c.prop.level); Strings.StringToInt(s, i, res)
				ELSE Strings.StringToInt(x, i, res)
				END;
				IF (res = 0) & (i >= MIN(SHORTINT)) & (i <= MAX(SHORTINT)) THEN
					old := c.item.IntVal();
					IF i # old THEN c.item.PutIntVal(i); Notify(c, f, Dialog.changed, old, i) END
				ELSIF x # "-" THEN
					Dialog.Beep; f.Update
				END
			| Meta.intTyp:
				IF x = "" THEN i := 0; res := 0
				ELSIF c.prop.level > 0 THEN FixToInt(x, s, c.prop.level); Strings.StringToInt(s, i, res)
				ELSE Strings.StringToInt(x, i, res)
				END;
				IF res = 0 THEN
					old := c.item.IntVal();
					IF i # old THEN c.item.PutIntVal(i); Notify(c, f, Dialog.changed, old, i) END
				ELSIF x # "-" THEN
					Dialog.Beep; f.Update
				END
			| Meta.longTyp:
				IF x = "" THEN long := 0; res := 0
				ELSE FixToInt(x, s, c.prop.level); StringToLong(s, long, res)
				END;
				IF res = 0 THEN
					long0 := c.item.LongVal();
					IF long # long0 THEN c.item.PutLongVal(long); Notify(c, f, Dialog.changed, 0, 0) END
				ELSIF x # "-" THEN
					Dialog.Beep; f.Update
				END
			| Meta.sRealTyp:
				IF (x = "") OR (x = "-") THEN r := 0; res := 0 ELSE Strings.StringToReal(x, r, res) END;
				IF (res = 0) & (r >= MIN(SHORTREAL)) & (r <= MAX(SHORTREAL)) THEN
					or := c.item.RealVal();
					IF r # or THEN c.item.PutRealVal(r); Notify(c, f, Dialog.changed, 0, 0) END
				ELSIF x # "-" THEN
					Dialog.Beep; f.Update
				END
			| Meta.realTyp:
				IF (x = "") OR (x = "-") THEN r := 0; res := 0 ELSE Strings.StringToReal(x, r, res) END;
				IF res = 0 THEN
					or := c.item.RealVal();
					IF r # or THEN c.item.PutRealVal(r); Notify(c, f, Dialog.changed, 0, 0) END
				ELSIF x # "-" THEN
					Dialog.Beep; f.Update
				END
			| Meta.recTyp:
				c.item.GetTypeName(mod, name);
				IF mod = "Dialog" THEN
					IF name = "Currency" THEN
						c.item.Lookup("val", v); c.item.Lookup("scale", b);
						IF x = "" THEN long := 0; res := 0
						ELSE FixToInt(x, s, b.IntVal()); StringToLong(s, long, res)
						END;
						IF res = 0 THEN
							long0 := v.LongVal();
							IF long # long0 THEN v.PutLongVal(long); Notify(c, f, Dialog.changed, 0, 0) END
						ELSIF x # "-" THEN
							Dialog.Beep; f.Update
						END
					ELSE	(* name = "Combo" *)
						c.item.Lookup("item", v);
						IF v.typ = Meta.arrTyp THEN
							v.GetStringVal(s, ok);
							IF ~ok OR (s$ # x$) THEN
								v.PutStringVal(x, ok);
								IF ok THEN Notify(c, f, Dialog.changed, 0, 0) ELSE Dialog.Beep; f.Update END
							END
						END
					END
				END
			END
		END
	END SetField;

	PROCEDURE EqualField (f: StdCFrames.Field; IN s1, s2: ARRAY OF CHAR): BOOLEAN;
		VAR c: Field; i1, i2, res1, res2: INTEGER; r1, r2: REAL; l1, l2: LONGINT;
			mod, name: Meta.Name; t1, t2: ARRAY 64 OF CHAR; b: Meta.Item;
	BEGIN
		c := f.view(Field);
		CASE c.item.typ OF
		| Meta.arrTyp:
			RETURN s1 = s2
		| Meta.byteTyp, Meta.sIntTyp, Meta.intTyp:
			IF c.prop.level > 0 THEN
				FixToInt(s1, t1, c.prop.level); Strings.StringToInt(t1, i1, res1);
				FixToInt(s2, t2, c.prop.level); Strings.StringToInt(t2, i2, res2)
			ELSE
				Strings.StringToInt(s1, i1, res1);
				Strings.StringToInt(s2, i2, res2)
			END;
			RETURN (res1 = 0) & (res2 = 0) & (i1 = i2) 
		| Meta.longTyp:
			IF c.prop.level > 0 THEN
				FixToInt(s1, t1, c.prop.level); StringToLong(t1, l1, res1);
				FixToInt(s2, t2, c.prop.level); StringToLong(t2, l2, res2)
			ELSE
				StringToLong(s1, l1, res1);
				StringToLong(s2, l2, res2)
			END;
			RETURN (res1 = 0) & (res2 = 0) & (l1 = l2) 
		| Meta.sRealTyp, Meta.realTyp:
			Strings.StringToReal(s1, r1, res1);
			Strings.StringToReal(s2, r2, res2);
			RETURN (res1 = 0) & (res2 = 0) & (r1 = r2) 
		| Meta.recTyp:
			c.item.GetTypeName(mod, name);
			IF mod = "Dialog" THEN
				IF name = "Currency" THEN
					c.item.Lookup("scale", b); i1 := b.IntVal();
					FixToInt(s1, t1, i1); StringToLong(t1, l1, res1);
					FixToInt(s2, t2, i1); StringToLong(t2, l2, res2);
					RETURN (res1 = 0) & (res2 = 0) & (l1 =l2)
				ELSE (* name = "Combo" *)
					RETURN s1 = s2
				END
			END
		ELSE RETURN s1 = s2
		END
	END EqualField;

	PROCEDURE (c: Field) CopyFromSimpleView2 (source: Control);
	BEGIN
		WITH source: Field DO c.maxLen := source.maxLen END
	END CopyFromSimpleView2;

	PROCEDURE (c: Field) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, fldVersion, thisVersion)
	END Internalize2;

	PROCEDURE (c: Field) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(fldVersion)
	END Externalize2;

	PROCEDURE (c: Field) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.Field;
	BEGIN
		f := StdCFrames.dir.NewField();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.maxLen := c.maxLen;
		f.left := c.prop.opt[left];
		f.right := c.prop.opt[right];
		f.multiLine := c.prop.opt[multiLine];
		f.password := c.prop.opt[password];
		f.Get := GetField;
		f.Set := SetField;
		f.Equal := EqualField;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: Field) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: Field) HandleCtrlMsg2 (f: Views.Frame; VAR msg: Controllers.Message;
																		VAR focus: Views.View);
		VAR ch: CHAR; mod, name: Meta.Name; from, to: INTEGER;
	BEGIN
		WITH f: StdCFrames.Field DO
			IF ~c.disabled (* & ~c.readOnly  *) THEN
				WITH msg: Controllers.PollOpsMsg DO
					f.GetSelection(from, to);
					msg.selectable := (f.Length() > 0) & (f.Length() # to - from);
					IF f.readOnly THEN
						msg.valid := {Controllers.copy}
					ELSIF (to > from) & ~f.password THEN
						msg.valid := {Controllers.pasteChar, Controllers.cut, Controllers.copy, Controllers.paste}
					ELSE
						msg.valid := {Controllers.pasteChar, Controllers.paste}
					END
				| msg: Controllers.TickMsg DO
					IF ~ f.readOnly THEN f.Idle END
				| msg: Controllers.EditMsg DO
					IF (msg.op = Controllers.pasteChar) & ~ f.readOnly THEN
						ch := msg.char;
						IF (ch = ldel) OR (ch = rdel) OR (ch >= 10X) & (ch <= 1FX)
							OR ("0" <= ch) & (ch <= "9") OR (ch = "+") OR (ch = "-")
							OR (c.item.typ = Meta.arrTyp)
							OR (c.item.typ IN {Meta.sRealTyp, Meta.realTyp}) & ((ch = ".") OR (ch = "E"))
							OR (c.prop.level > 0) & (ch = ".")
							OR (ch = apps) OR (ch = ctrlZ) OR (ch = ctrlA) THEN
							f.KeyDown(ch, msg.modifiers)
						ELSIF (c.item.typ IN {Meta.realTyp, Meta.sRealTyp}) & (ch = "e") THEN
							f.KeyDown("E", msg.modifiers)
						ELSIF c.item.typ = Meta.recTyp THEN
							c.item.GetTypeName(mod, name);
							IF (mod = "Dialog") & (name = "Combo") OR (ch = ".") THEN
								f.KeyDown(ch, msg.modifiers)
							ELSE Dialog.Beep
							END
						ELSE Dialog.Beep
						END
					ELSIF f.readOnly & (msg.op = Controllers.copy) THEN
						f.Edit(msg.op, msg.view, msg.w, msg.h, msg.isSingle, msg.clipboard)
					ELSE
						f.Edit(msg.op, msg.view, msg.w, msg.h, msg.isSingle, msg.clipboard)
					END
				| msg: Controllers.SelectMsg DO
					IF msg.set THEN f.Select(0, MAX(INTEGER))
					ELSE f.Select(-1, -1)
					END
				| msg: Controllers.MarkMsg DO
					f.Mark(msg.show, msg.focus);
					IF ~msg.show & msg.focus THEN f.Update END;
					IF msg.show & msg.focus THEN f.Select(0, MAX(INTEGER)) END
				| msg: Controllers.TrackMsg DO
					f.MouseDown(msg.x, msg.y, msg.modifiers)
				ELSE
					CatchCtrlMsg(c, f, msg, focus)
				END
			(* ELSIF ~c.disabled THEN
				WITH msg: Controllers.TrackMsg DO
					f.MouseDown(msg.x, msg.y, msg.modifiers)
				ELSE
				END *)
			END
		END
	END HandleCtrlMsg2;

	PROCEDURE (c: Field) HandlePropMsg2 (VAR msg: Properties.Message);
	BEGIN
		WITH msg: Properties.ControlPref DO
			IF msg.char = lineChar THEN msg.accepts := c.prop.opt[multiLine] & (msg.focus = c)
			ELSIF msg.char = esc THEN msg.accepts := FALSE
			END;
			IF ~c.disabled (* & ~c.readOnly *) & IsShortcut(msg.char, c) THEN msg.getFocus := TRUE END
		| msg: Properties.FocusPref DO
			IF ~c.disabled (* & ~c.readOnly *)  THEN
				msg.setFocus := TRUE
			ELSIF~c.disabled THEN
				msg.hotFocus := TRUE
			END
		| msg: Properties.SizePref DO
			StdCFrames.dir.GetFieldSize(c.maxLen, msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard, level, notifier, left, right, multiLine, password}
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: Field) CheckLink (VAR ok: BOOLEAN);
		VAR t: INTEGER; name: Meta.Name;
	BEGIN
		GetTypeName(c.item, name); t := c.item.typ;
		IF (t = Meta.arrTyp) & (c.item.BaseTyp() = Meta.charTyp) THEN c.maxLen := SHORT(c.item.Len() - 1)
		ELSIF t = Meta.byteTyp THEN c.maxLen := 6
		ELSIF t = Meta.sIntTyp THEN c.maxLen := 9
		ELSIF t = Meta.intTyp THEN c.maxLen := 13
		ELSIF t = Meta.longTyp THEN c.maxLen := 24
		ELSIF t = Meta.sRealTyp THEN c.maxLen := 16
		ELSIF t = Meta.realTyp THEN c.maxLen := 24
		ELSIF name = "Combo" THEN c.maxLen := 64
		ELSIF name = "Currency" THEN c.maxLen := 16
		ELSE ok := FALSE
		END
	END CheckLink;

	PROCEDURE (c: Field) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		f(StdCFrames.Frame).Update
	END Update;
	

	(* UpDownField *)

	PROCEDURE GetUpDownField (f: StdCFrames.UpDownField; OUT val: INTEGER);
		VAR c: UpDownField;
	BEGIN
		val := 0;
		c := f.view(UpDownField);
		IF c.item.Valid() THEN val := c.item.IntVal() END
	END GetUpDownField;

	PROCEDURE SetUpDownField (f: StdCFrames.UpDownField; val: INTEGER);
		VAR c: UpDownField; old: INTEGER;
	BEGIN
		c := f.view(UpDownField);
		IF c.item.Valid() & ~c.readOnly THEN
			IF (val >= c.min) & (val <= c.max) THEN
				old := c.item.IntVal();
				IF old # val THEN c.item.PutIntVal(val); Notify(c, f, Dialog.changed, old, val) END
			ELSE Dialog.Beep; f.Update
			END
		END
	END SetUpDownField;
	
	PROCEDURE (c: UpDownField) CopyFromSimpleView2 (source: Control);
	BEGIN
		WITH source: UpDownField DO
			c.min := source.min;
			c.max := source.max;
			c.inc := source.inc
		END
	END CopyFromSimpleView2;

	PROCEDURE (c: UpDownField) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, fldVersion, thisVersion)
	END Internalize2;

	PROCEDURE (c: UpDownField) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(fldVersion)
	END Externalize2;

	PROCEDURE (c: UpDownField) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.UpDownField;
	BEGIN
		f := StdCFrames.dir.NewUpDownField();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.min := c.min;
		f.max := c.max;
		f.inc := c.inc;
		f.Get := GetUpDownField;
		f.Set := SetUpDownField;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: UpDownField) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: UpDownField) HandleCtrlMsg2 (f: Views.Frame; VAR msg: Controllers.Message;
																		VAR focus: Views.View);
		VAR ch: CHAR; from, to, i: INTEGER; s: ARRAY 16 OF CHAR;
	BEGIN
		IF ~c.disabled & ~c.readOnly THEN
			WITH f: StdCFrames.UpDownField DO
				WITH msg: Controllers.PollOpsMsg DO
					f.GetSelection(from, to);
					IF c.item.Valid() THEN f.Get(f, i); Strings.IntToString(i, s) ELSE s := "" END;
					msg.selectable := (LEN(s$) > 0) & (LEN(s$) # to - from);
					IF to > from THEN
						msg.valid := {Controllers.pasteChar, Controllers.cut, Controllers.copy, Controllers.paste}
					ELSE msg.valid := {Controllers.pasteChar, Controllers.paste}
					END
				| msg: Controllers.TickMsg DO
					f.Idle
				| msg: Controllers.EditMsg DO
					IF msg.op = Controllers.pasteChar THEN
						ch := msg.char;
						IF (ch = ldel) OR (ch = rdel) OR (ch >= 10X) & (ch <= 1FX)
							OR ("0" <= ch) & (ch <= "9") OR (ch = "+") OR (ch = "-")
							OR (c.item.typ = Meta.arrTyp)
							OR (ch = apps) OR (ch = ctrlZ) OR (ch = ctrlA)
							THEN f.KeyDown(ch, msg.modifiers)
						ELSE Dialog.Beep
						END
					ELSE
						f.Edit(msg.op, msg.view, msg.w, msg.h, msg.isSingle, msg.clipboard)
					END
				| msg: Controllers.SelectMsg DO
					IF msg.set THEN f.Select(0, MAX(INTEGER))
					ELSE f.Select(-1, -1)
					END
				| msg: Controllers.MarkMsg DO
					f.Mark(msg.show, msg.focus);
					IF msg.show & msg.focus THEN f.Select(0, MAX(INTEGER)) END
				ELSE
					CatchCtrlMsg(c, f, msg, focus)
				END
			END
		END
	END HandleCtrlMsg2;

	PROCEDURE (c: UpDownField) HandlePropMsg2 (VAR msg: Properties.Message);
		VAR m: INTEGER; n: INTEGER;
	BEGIN
		WITH msg: Properties.ControlPref DO
			IF (msg.char = lineChar) OR (msg.char = esc) THEN msg.accepts := FALSE END;
			IF ~c.disabled & ~c.readOnly & IsShortcut(msg.char, c) THEN msg.getFocus := TRUE END
		| msg: Properties.FocusPref DO
			IF ~c.disabled & ~c.readOnly THEN
				msg.setFocus := TRUE
			END
		| msg: Properties.SizePref DO
			m := -c.min;
			IF c.max > m THEN m := c.max END;
			n := 3;
			WHILE m > 99 DO INC(n); m := m DIV 10 END;
			StdCFrames.dir.GetUpDownFieldSize(n, msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard, notifier}
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: UpDownField) CheckLink (VAR ok: BOOLEAN);
	BEGIN
		IF c.item.typ = Meta.byteTyp THEN c.min := MIN(BYTE); c.max := MAX(BYTE)
		ELSIF c.item.typ = Meta.sIntTyp THEN c.min := MIN(SHORTINT); c.max := MAX(SHORTINT)
		ELSIF c.item.typ = Meta.intTyp THEN c.min := MIN(INTEGER); c.max := MAX(INTEGER)
		ELSE ok := FALSE
		END;
		c.inc := 1
	END CheckLink;

	PROCEDURE (c: UpDownField) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		f(StdCFrames.Frame).Update
	END Update;
	

	(* DateField *)

	PROCEDURE GetDateField (f: StdCFrames.DateField; OUT date: Dates.Date);
		VAR c: DateField; v: Meta.Item;
	BEGIN
		date.year := 1; date.month := 1; date.day := 1;
		c := f.view(DateField);
		IF c.item.Valid() THEN
			c.item.Lookup("year", v); IF v.typ = Meta.intTyp THEN date.year := SHORT(v.IntVal()) END;
			c.item.Lookup("month", v); IF v.typ = Meta.intTyp THEN date.month := SHORT(v.IntVal()) END;
			c.item.Lookup("day", v); IF v.typ = Meta.intTyp THEN date.day := SHORT(v.IntVal()) END
		END
	END GetDateField;
	
	PROCEDURE SetDateField(f:  StdCFrames.DateField; IN date: Dates.Date);
		VAR c: DateField; v: Meta.Item;
	BEGIN
		c := f.view(DateField);
		IF c.item.Valid() & ~c.readOnly THEN
			c.item.Lookup("year", v); IF v.typ = Meta.intTyp THEN v.PutIntVal(date.year) END;
			c.item.Lookup("month", v); IF v.typ = Meta.intTyp THEN v.PutIntVal(date.month) END;
			c.item.Lookup("day", v); IF v.typ = Meta.intTyp THEN v.PutIntVal(date.day) END;
			Notify(c, f, Dialog.changed, 0, 0)
		END
	END SetDateField;
	
	PROCEDURE GetDateFieldSelection (f: StdCFrames.DateField; OUT sel: INTEGER);
	BEGIN
		sel := f.view(DateField).selection
	END GetDateFieldSelection;
	
	PROCEDURE SetDateFieldSelection (f: StdCFrames.DateField; sel: INTEGER);
	BEGIN
		f.view(DateField).selection := sel
	END SetDateFieldSelection;
	
	PROCEDURE (c: DateField) CopyFromSimpleView2 (source: Control);
	BEGIN
		WITH source: DateField DO c.selection := source.selection END
	END CopyFromSimpleView2;

	PROCEDURE (c: DateField) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, dfldVersion, thisVersion);
		c.selection := 0
	END Internalize2;

	PROCEDURE (c: DateField) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(dfldVersion)
	END Externalize2;

	PROCEDURE (c: DateField) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.DateField;
	BEGIN
		f := StdCFrames.dir.NewDateField();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.Get := GetDateField;
		f.Set := SetDateField;
		f.GetSel := GetDateFieldSelection;
		f.SetSel := SetDateFieldSelection;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: DateField) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: DateField) HandleCtrlMsg2 (f: Views.Frame; VAR msg: Controllers.Message;
																		VAR focus: Views.View);
	BEGIN
		IF ~c.disabled & ~c.readOnly THEN
			WITH f: StdCFrames.DateField DO
				WITH msg: Controllers.PollOpsMsg DO
					msg.valid := {Controllers.pasteChar, Controllers.copy}
				| msg: Controllers.EditMsg DO
					IF msg.op = Controllers.pasteChar THEN
						f.KeyDown(msg.char, msg.modifiers)
					ELSE
						f.Edit(msg.op, msg.view, msg.w, msg.h, msg.isSingle, msg.clipboard)
					END
				| msg: Controllers.TickMsg DO
					IF f.mark THEN
						IF c.selection = 0 THEN c.selection := 1; Views.Update(c, Views.keepFrames) END
					END
				ELSE
					CatchCtrlMsg(c, f, msg, focus)
				END
			END
		END
	END HandleCtrlMsg2;

	PROCEDURE (c: DateField) HandlePropMsg2 (VAR msg: Properties.Message);
	BEGIN
		WITH msg: Properties.ControlPref DO
			IF (msg.char = lineChar) OR (msg.char = esc) THEN
				msg.accepts := FALSE
			ELSIF (msg.char = tab) OR (msg.char = ltab) THEN
				msg.accepts := ((msg.focus # c) & (~c.disabled & ~c.readOnly)) OR 
					(msg.focus = c) & ((msg.char = tab) & (c.selection # -1) OR (msg.char = ltab) & (c.selection # 1));
				msg.getFocus := msg.accepts
			END;
			IF ~c.disabled & ~c.readOnly & IsShortcut(msg.char, c) THEN msg.getFocus := TRUE END
		| msg: Properties.FocusPref DO
			IF ~c.disabled & ~c.readOnly THEN
				msg.setFocus := TRUE
			END
		| msg: Properties.SizePref DO
			StdCFrames.dir.GetDateFieldSize(msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard, notifier}
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: DateField) CheckLink (VAR ok: BOOLEAN);
		VAR name: Meta.Name;
	BEGIN
		GetTypeName(c.item, name);
		ok := name = "Date"
	END CheckLink;

	PROCEDURE (c: DateField) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		f(StdCFrames.Frame).Update
	END Update;
	

	(* TimeField *)

	PROCEDURE GetTimeField (f: StdCFrames.TimeField; OUT time: Dates.Time);
		VAR c: TimeField; v: Meta.Item;
	BEGIN
		time.hour := 0; time.minute := 0; time.second := 0;
		c := f.view(TimeField);
		IF c.item.Valid() THEN
			c.item.Lookup("hour", v); IF v.typ = Meta.intTyp THEN time.hour := SHORT(v.IntVal()) END;
			c.item.Lookup("minute", v); IF v.typ = Meta.intTyp THEN time.minute := SHORT(v.IntVal()) END;
			c.item.Lookup("second", v); IF v.typ = Meta.intTyp THEN time.second := SHORT(v.IntVal()) END
		END
	END GetTimeField;
	
	PROCEDURE SetTimeField(f:  StdCFrames.TimeField; IN date: Dates.Time);
		VAR c: TimeField; v: Meta.Item;
	BEGIN
		c := f.view(TimeField);
		IF c.item.Valid() & ~c.readOnly THEN
			c.item.Lookup("hour", v); IF v.typ = Meta.intTyp THEN v.PutIntVal(date.hour) END;
			c.item.Lookup("minute", v); IF v.typ = Meta.intTyp THEN v.PutIntVal(date.minute) END;
			c.item.Lookup("second", v); IF v.typ = Meta.intTyp THEN v.PutIntVal(date.second) END;
			Notify(c, f, Dialog.changed, 0, 0)
		END
	END SetTimeField;
	
	PROCEDURE GetTimeFieldSelection (f: StdCFrames.TimeField; OUT sel: INTEGER);
	BEGIN
		sel := f.view(TimeField).selection
	END GetTimeFieldSelection;
	
	PROCEDURE SetTimeFieldSelection (f: StdCFrames.TimeField; sel: INTEGER);
	BEGIN
		f.view(TimeField).selection := sel
	END SetTimeFieldSelection;
	
	PROCEDURE (c: TimeField) CopyFromSimpleView2 (source: Control);
	BEGIN
		WITH source: TimeField DO c.selection := source.selection END
	END CopyFromSimpleView2;

	PROCEDURE (c: TimeField) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, tfldVersion, thisVersion);
		c.selection := 0
	END Internalize2;

	PROCEDURE (c: TimeField) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(tfldVersion)
	END Externalize2;

	PROCEDURE (c: TimeField) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.TimeField;
	BEGIN
		f := StdCFrames.dir.NewTimeField();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.Get := GetTimeField;
		f.Set := SetTimeField;
		f.GetSel := GetTimeFieldSelection;
		f.SetSel := SetTimeFieldSelection;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: TimeField) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: TimeField) HandleCtrlMsg2 (f: Views.Frame; VAR msg: Controllers.Message;
																		VAR focus: Views.View);
	BEGIN
		IF ~c.disabled & ~c.readOnly THEN
			WITH f: StdCFrames.TimeField DO
				WITH msg: Controllers.PollOpsMsg DO
					msg.valid := {Controllers.pasteChar, Controllers.copy}
				| msg: Controllers.EditMsg DO
					IF msg.op = Controllers.pasteChar THEN
						f.KeyDown(msg.char, msg.modifiers)
					ELSE
						f.Edit(msg.op, msg.view, msg.w, msg.h, msg.isSingle, msg.clipboard)
					END
				| msg: Controllers.TickMsg DO
					IF f.mark THEN
						IF c.selection = 0 THEN c.selection := 1; Views.Update(c, Views.keepFrames) END
					END
				ELSE
					CatchCtrlMsg(c, f, msg, focus)
				END
			END
		END
	END HandleCtrlMsg2;

	PROCEDURE (c: TimeField) HandlePropMsg2 (VAR msg: Properties.Message);
	BEGIN
		WITH msg: Properties.ControlPref DO
			IF (msg.char = lineChar) OR (msg.char = esc) THEN
				msg.accepts := FALSE
			ELSIF (msg.char = tab) OR (msg.char = ltab) THEN
				msg.accepts := (msg.focus # c) OR 
					((msg.char = tab) & (c.selection # -1)) OR ((msg.char = ltab) & (c.selection # 1))
			END;
			IF ~c.disabled & ~c.readOnly & IsShortcut(msg.char, c) THEN msg.getFocus := TRUE END
		| msg: Properties.FocusPref DO
			IF ~c.disabled & ~c.readOnly THEN
				msg.setFocus := TRUE
			END
		| msg: Properties.SizePref DO
			StdCFrames.dir.GetTimeFieldSize(msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard, notifier}
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: TimeField) CheckLink (VAR ok: BOOLEAN);
		VAR name: Meta.Name;
	BEGIN
		GetTypeName(c.item, name);
		ok := name = "Time"
	END CheckLink;

	PROCEDURE (c: TimeField) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		f(StdCFrames.Frame).Update
	END Update;
	

	(* ColorField *)

	PROCEDURE GetColorField (f: StdCFrames.ColorField; OUT col: INTEGER);
		VAR c: ColorField; v: Meta.Item;
	BEGIN
		col := Ports.defaultColor;
		c := f.view(ColorField);
		IF c.item.Valid() THEN
			IF c.item.typ = Meta.intTyp THEN
				col := c.item.IntVal()
			ELSE
				c.item.Lookup("val", v); IF v.typ = Meta.intTyp THEN col := v.IntVal() END
			END
		END
	END GetColorField;
	
	PROCEDURE SetColorField(f:  StdCFrames.ColorField; col: INTEGER);
		VAR c: ColorField; v: Meta.Item; old: INTEGER;
	BEGIN
		c := f.view(ColorField);
		IF c.item.Valid() & ~c.readOnly THEN
			IF c.item.typ = Meta.intTyp THEN
				old := c.item.IntVal();
				IF old # col THEN c.item.PutIntVal(col); Notify(c, f, Dialog.changed, old, col) END
			ELSE
				c.item.Lookup("val", v); 
				IF v.typ = Meta.intTyp THEN 
					old := v.IntVal();
					IF old # col THEN v.PutIntVal(col); Notify(c, f, Dialog.changed, old, col) END
				END
			END
		END
	END SetColorField;
	
	PROCEDURE (c: ColorField) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, cfldVersion, thisVersion)
	END Internalize2;

	PROCEDURE (c: ColorField) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(cfldVersion)
	END Externalize2;

	PROCEDURE (c: ColorField) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.ColorField;
	BEGIN
		f := StdCFrames.dir.NewColorField();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.Get := GetColorField;
		f.Set := SetColorField;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: ColorField) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: ColorField) HandleCtrlMsg2 (f: Views.Frame; VAR msg: Controllers.Message;
																		VAR focus: Views.View);
	BEGIN
		IF ~c.disabled & ~c.readOnly THEN
			WITH f: StdCFrames.ColorField DO
				WITH msg: Controllers.EditMsg DO
					IF msg.op = Controllers.pasteChar THEN
						f.KeyDown(msg.char, msg.modifiers)
					ELSE
						f.Edit(msg.op, msg.view, msg.w, msg.h, msg.isSingle, msg.clipboard)
					END
				ELSE
					CatchCtrlMsg(c, f, msg, focus)
				END
			END
		END
	END HandleCtrlMsg2;

	PROCEDURE (c: ColorField) HandlePropMsg2 (VAR msg: Properties.Message);
	BEGIN
		WITH msg: Properties.ControlPref DO
			msg.accepts := ~c.disabled & ~c.readOnly & IsShortcut(msg.char, c)
		| msg: Properties.FocusPref DO
			IF ~c.disabled & ~c.readOnly THEN
				msg.hotFocus := TRUE; msg.setFocus := StdCFrames.setFocus
			END
		| msg: Properties.SizePref DO
			StdCFrames.dir.GetColorFieldSize(msg.w, msg.h)
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: ColorField) CheckLink (VAR ok: BOOLEAN);
		VAR name: Meta.Name;
	BEGIN
		GetTypeName(c.item, name);
		ok := (name = "Color") OR (c.item.typ = Meta.intTyp)
	END CheckLink;

	PROCEDURE (c: ColorField) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		f(StdCFrames.Frame).Update
	END Update;
	

	(* ListBox *)

	PROCEDURE GetListBox (f: StdCFrames.ListBox; OUT i: INTEGER);
		VAR c: ListBox; v: Meta.Item;
	BEGIN
		i := -1;
		c := f.view(ListBox);
		IF c.item.Valid() THEN
			c.item.Lookup("index", v);
			IF v.typ = Meta.intTyp THEN i := v.IntVal() END
		END
	END GetListBox;

	PROCEDURE SetListBox (f: StdCFrames.ListBox; i: INTEGER);
		VAR c: ListBox; v: Meta.Item; old: INTEGER;
	BEGIN
		c := f.view(ListBox);
		IF c.item.Valid() & ~c.readOnly THEN
			c.item.Lookup("index", v);
			IF v.typ = Meta.intTyp THEN
				old := v.IntVal();
				IF i # old THEN v.PutIntVal(i); Notify(c, f, Dialog.changed, old, i) END
			END
		END
	END SetListBox;
	
	PROCEDURE GetFName (VAR rec, par: ANYREC);
	BEGIN
		WITH par: Param DO
			WITH rec: Dialog.List DO rec.GetItem(par.i, par.n)
			| rec: Dialog.Selection DO rec.GetItem(par.i, par.n)
			| rec: Dialog.Combo DO rec.GetItem(par.i, par.n)
			ELSE par.n := ""
			END
		END
	END GetFName;
	
	PROCEDURE GetListName (f: StdCFrames.ListBox; i: INTEGER; VAR name: ARRAY OF CHAR);
		VAR c: ListBox; par: Param;
	BEGIN
		par.n := "";
		c := f.view(ListBox);
		IF c.item.Valid() THEN
			par.i := i;
			c.item.CallWith(GetFName, par)
		END;
		name := par.n$
	END GetListName;

	PROCEDURE (c: ListBox) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, lbxVersion, thisVersion)
	END Internalize2;

	PROCEDURE (c: ListBox) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(lbxVersion)
	END Externalize2;

	PROCEDURE (c: ListBox) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.ListBox;
	BEGIN
		f := StdCFrames.dir.NewListBox();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.sorted := c.prop.opt[sorted];
		f.Get := GetListBox;
		f.Set := SetListBox;
		f.GetName := GetListName;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: ListBox) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: ListBox) HandleCtrlMsg2 (f: Views.Frame; VAR msg: Controllers.Message;
																		VAR focus: Views.View);
	BEGIN
		WITH f: StdCFrames.ListBox DO
			IF ~c.disabled & ~c.readOnly THEN
				WITH msg: Controllers.EditMsg DO
					IF msg.op = Controllers.pasteChar THEN f.KeyDown(msg.char, msg.modifiers) END
				ELSE
					CatchCtrlMsg(c, f, msg, focus)
				END
			ELSIF ~c.disabled THEN
				WITH msg: Controllers.TrackMsg DO
					f.MouseDown(msg.x, msg.y, msg.modifiers)
				ELSE
				END
			END
		END
	END HandleCtrlMsg2;

	PROCEDURE (c: ListBox) HandlePropMsg2 (VAR msg: Properties.Message);
	BEGIN
		WITH msg: Properties.ControlPref DO
			IF (msg.char = lineChar) OR (msg.char = esc) THEN msg.accepts := FALSE END;
			IF ~c.disabled & ~c.readOnly & IsShortcut(msg.char, c) THEN msg.getFocus := TRUE END
		| msg: Properties.FocusPref DO
			IF ~c.disabled & ~c.readOnly THEN
				msg.setFocus := TRUE
			ELSIF~c.disabled THEN
				msg.hotFocus := TRUE
			END
		| msg: Properties.SizePref DO
			StdCFrames.dir.GetListBoxSize(msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard, notifier, sorted}
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: ListBox) CheckLink (VAR ok: BOOLEAN);
		VAR name: Meta.Name;
	BEGIN
		GetTypeName(c.item, name);
		ok := name = "List"
	END CheckLink;

	PROCEDURE (c: ListBox) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		f(StdCFrames.Frame).Update
	END Update;
	
	PROCEDURE (c: ListBox) UpdateList (f: Views.Frame);
	BEGIN
		f(StdCFrames.Frame).UpdateList
	END UpdateList;
	

	(* SelectionBox *)

	PROCEDURE InLargeSet (VAR rec, par: ANYREC);
	BEGIN
		WITH par: Param DO
			WITH rec: Dialog.Selection DO
				IF rec.In(par.i) THEN par.i := 1 ELSE par.i := 0 END
			ELSE par.i := 0
			END
		END
	END InLargeSet;
	
	PROCEDURE GetSelectionBox (f: StdCFrames.SelectionBox; i: INTEGER; OUT in: BOOLEAN);
		VAR c: SelectionBox; lv: SelectValue; par: Param;
	BEGIN
		in := FALSE;
		c := f.view(SelectionBox);
		IF c.item.Valid() THEN
			IF c.item.Is(lv) THEN
				par.i := i;
				c.item.CallWith(InLargeSet, par);
				in := par.i # 0
			END
		END
	END GetSelectionBox;

	PROCEDURE InclLargeSet (VAR rec, par: ANYREC);
	BEGIN
		WITH par: Param DO
			WITH rec: Dialog.Selection DO
				IF (par.from # par.to) OR ~rec.In(par.from) THEN
					rec.Incl(par.from, par.to); par.i := 1
				ELSE par.i := 0
				END
			ELSE par.i := 0
			END
		END
	END InclLargeSet;
	
	PROCEDURE InclSelectionBox (f: StdCFrames.SelectionBox; from, to: INTEGER);
		VAR c: SelectionBox; lv: SelectValue; par: Param;
	BEGIN
		c := f.view(SelectionBox);
		IF c.item.Valid() & ~c.readOnly THEN
			IF c.item.Is(lv) THEN
				par.from := from; par.to := to;
				c.item.CallWith(InclLargeSet, par);
				IF par.i # 0 THEN Notify(c, f, Dialog.included, from, to) END
			END
		END
	END InclSelectionBox;
	
	PROCEDURE ExclLargeSet (VAR rec, par: ANYREC);
	BEGIN
		WITH par: Param DO
			WITH rec: Dialog.Selection DO
				IF (par.from # par.to) OR rec.In(par.from) THEN
					rec.Excl(par.from, par.to); par.i := 1
				ELSE par.i := 0
				END
			ELSE par.i := 0
			END
		END
	END ExclLargeSet;
	
	PROCEDURE ExclSelectionBox (f: StdCFrames.SelectionBox; from, to: INTEGER);
		VAR c: SelectionBox; lv: SelectValue; par: Param;
	BEGIN
		c := f.view(SelectionBox);
		IF c.item.Valid() & ~c.readOnly THEN
			IF c.item.Is(lv) THEN
				par.from := from; par.to := to;
				c.item.CallWith(ExclLargeSet, par);
				IF par.i # 0 THEN Notify(c, f, Dialog.excluded, from, to) END
			END
		END
	END ExclSelectionBox;
	
	PROCEDURE SetSelectionBox (f: StdCFrames.SelectionBox; from, to: INTEGER);
		VAR c: SelectionBox; lv: SelectValue; par: Param;
	BEGIN
		c := f.view(SelectionBox);
		IF c.item.Valid() & ~c.readOnly THEN
			IF c.item.Is(lv) THEN
				par.from := 0; par.to := MAX(INTEGER);
				c.item.CallWith(ExclLargeSet, par);
				par.from := from; par.to := to;
				c.item.CallWith(InclLargeSet, par);
				Notify(c, f, Dialog.set, from, to)
			END
		END
	END SetSelectionBox;
	
	PROCEDURE GetSelName (f: StdCFrames.SelectionBox; i: INTEGER; VAR name: ARRAY OF CHAR);
		VAR c: SelectionBox; par: Param;
	BEGIN
		par.n := "";
		c := f.view(SelectionBox);
		IF c.item.Valid() THEN
			par.i := i;
			c.item.CallWith(GetFName, par)
		END;
		name := par.n$
	END GetSelName;

	PROCEDURE (c: SelectionBox) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, sbxVersion, thisVersion)
	END Internalize2;

	PROCEDURE (c: SelectionBox) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(sbxVersion)
	END Externalize2;

	PROCEDURE (c: SelectionBox) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.SelectionBox;
	BEGIN
		f := StdCFrames.dir.NewSelectionBox();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.sorted := c.prop.opt[sorted];
		f.Get := GetSelectionBox;
		f.Incl := InclSelectionBox;
		f.Excl := ExclSelectionBox;
		f.Set := SetSelectionBox;
		f.GetName := GetSelName;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: SelectionBox) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: SelectionBox) HandleCtrlMsg2 (f: Views.Frame; VAR msg: Controllers.Message;
																				VAR focus: Views.View);
	BEGIN
		WITH f: StdCFrames.SelectionBox DO
			IF ~c.disabled & ~c.readOnly THEN
				WITH msg: Controllers.EditMsg DO
					IF msg.op = Controllers.pasteChar THEN f.KeyDown(msg.char, msg.modifiers) END
				| msg: Controllers.SelectMsg DO
					IF msg.set THEN f.Select(0, MAX(INTEGER))
					ELSE f.Select(-1, -1)
					END
				ELSE
					CatchCtrlMsg(c, f, msg, focus)
				END
			ELSIF ~c.disabled THEN
				WITH msg: Controllers.TrackMsg DO
					f.MouseDown(msg.x, msg.y, msg.modifiers)
				ELSE
				END
			END
		END
	END HandleCtrlMsg2;

	PROCEDURE (c: SelectionBox) HandlePropMsg2 (VAR msg: Properties.Message);
	BEGIN
		WITH msg: Properties.ControlPref DO
			IF (msg.char = lineChar) OR (msg.char = esc) THEN msg.accepts := FALSE END;
			IF ~c.disabled & ~c.readOnly & IsShortcut(msg.char, c) OR msg.getFocus THEN
				msg.getFocus := StdCFrames.setFocus
			END
		| msg: Properties.FocusPref DO
			IF ~c.disabled & ~c.readOnly THEN
				msg.setFocus := TRUE
			ELSIF~c.disabled THEN
				msg.hotFocus := TRUE
			END
		| msg: Properties.SizePref DO
			StdCFrames.dir.GetSelectionBoxSize(msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard, notifier, sorted}
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: SelectionBox) CheckLink (VAR ok: BOOLEAN);
		VAR name: Meta.Name;
	BEGIN
		GetTypeName(c.item, name);
		ok := name = "Selection"
	END CheckLink;

	PROCEDURE (c: SelectionBox) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		IF (op >= Dialog.included) & (op <= Dialog.set) THEN
			f(StdCFrames.SelectionBox).UpdateRange(op, from, to)
		ELSE
			f(StdCFrames.Frame).Update
		END
	END Update;
	
	PROCEDURE (c: SelectionBox) UpdateList (f: Views.Frame);
	BEGIN
		f(StdCFrames.Frame).UpdateList
	END UpdateList;
	

	(* ComboBox *)

	PROCEDURE GetComboBox (f: StdCFrames.ComboBox; OUT x: ARRAY OF CHAR);
		VAR c: ComboBox; ok: BOOLEAN; v: Meta.Item;
	BEGIN
		x := "";
		c := f.view(ComboBox);
		IF c.item.Valid() THEN
			c.item.Lookup("item", v);
			IF v.typ = Meta.arrTyp THEN v.GetStringVal(x, ok) END
		END
	END GetComboBox;

	PROCEDURE SetComboBox (f: StdCFrames.ComboBox; IN x: ARRAY OF CHAR);
		VAR c: ComboBox; ok: BOOLEAN; v: Meta.Item; s: ARRAY 1024 OF CHAR;
	BEGIN
		c := f.view(ComboBox);
		IF c.item.Valid() & ~c.readOnly THEN
			c.item.Lookup("item", v);
			IF v.typ = Meta.arrTyp THEN
				v.GetStringVal(s, ok);
				IF ~ok OR (s$ # x$) THEN
					v.PutStringVal(x, ok);
					IF ok THEN Notify(c, f, Dialog.changed, 0, 0) END
				END
			END
		END
	END SetComboBox;

	PROCEDURE GetComboName (f: StdCFrames.ComboBox; i: INTEGER; VAR name: ARRAY OF CHAR);
		VAR c: ComboBox; par: Param;
	BEGIN
		par.n := "";
		c := f.view(ComboBox);
		IF c.item.Valid() THEN
			par.i := i;
			c.item.CallWith(GetFName, par)
		END;
		name := par.n$
	END GetComboName;

	PROCEDURE (c: ComboBox) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, cbxVersion, thisVersion)
	END Internalize2;

	PROCEDURE (c: ComboBox) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(cbxVersion)
	END Externalize2;

	PROCEDURE (c: ComboBox) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.ComboBox;
	BEGIN
		f := StdCFrames.dir.NewComboBox();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.sorted := c.prop.opt[sorted];
		f.Get := GetComboBox;
		f.Set := SetComboBox;
		f.GetName := GetComboName;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: ComboBox) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: ComboBox) HandleCtrlMsg2 (f: Views.Frame; VAR msg: Controllers.Message;
																			VAR focus: Views.View);
		VAR from, to: INTEGER;
	BEGIN
		WITH f: StdCFrames.ComboBox DO
			IF ~c.disabled & ~c.readOnly THEN
				WITH msg: Controllers.PollOpsMsg DO
					f.GetSelection(from, to);
					msg.selectable := (f.Length() > 0) & (f.Length() # to - from);
					IF to > from THEN
						msg.valid := {Controllers.pasteChar, Controllers.cut, Controllers.copy, Controllers.paste}
					ELSE msg.valid := {Controllers.pasteChar, Controllers.paste}
					END
				| msg: Controllers.TickMsg DO
					f.Idle
				| msg: Controllers.EditMsg DO
					IF msg.op = Controllers.pasteChar THEN
						f.KeyDown(msg.char, msg.modifiers)
					ELSE
						f.Edit(msg.op, msg.view, msg.w, msg.h, msg.isSingle, msg.clipboard)
					END
				| msg: Controllers.SelectMsg DO
					IF msg.set THEN f.Select(0, MAX(INTEGER))
					ELSE f.Select(-1, -1)
					END
				| msg: Controllers.MarkMsg DO
					f.Mark(msg.show, msg.focus);
					IF msg.show & msg.focus THEN f.Select(0, MAX(INTEGER)) END
				| msg: Controllers.TrackMsg DO
					f.MouseDown(msg.x, msg.y, msg.modifiers)
				ELSE
					CatchCtrlMsg(c, f, msg, focus)
				END
			END
		END
	END HandleCtrlMsg2;

	PROCEDURE (c: ComboBox) HandlePropMsg2 (VAR msg: Properties.Message);
	BEGIN
		WITH msg: Properties.ControlPref DO
			IF (msg.char = lineChar) OR (msg.char = esc) THEN msg.accepts := FALSE END;
			IF ~c.disabled & ~c.readOnly & IsShortcut(msg.char, c) THEN msg.getFocus := TRUE END
		| msg: Properties.FocusPref DO
			IF ~c.disabled & ~c.readOnly THEN
				msg.setFocus := TRUE
			END
		| msg: Properties.SizePref DO
			StdCFrames.dir.GetComboBoxSize(msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard, notifier, sorted}
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: ComboBox) CheckLink (VAR ok: BOOLEAN);
		VAR name: Meta.Name;
	BEGIN
		GetTypeName(c.item, name);
		ok := name = "Combo"
	END CheckLink;

	PROCEDURE (c: ComboBox) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		f(StdCFrames.Frame).Update
	END Update;
	
	PROCEDURE (c: ComboBox) UpdateList (f: Views.Frame);
	BEGIN
		f(StdCFrames.Frame).UpdateList
	END UpdateList;
	

	(* Caption *)

	PROCEDURE (c: Caption) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, capVersion, thisVersion);
		IF thisVersion < 1 THEN c.prop.opt[left] := TRUE END
	END Internalize2;

	PROCEDURE (c: Caption) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		(* Save old version for captions that are compatible with the old version *)
		IF c.prop.opt[left] THEN wr.WriteVersion(0) ELSE wr.WriteVersion(capVersion) END
	END Externalize2;

	PROCEDURE (c: Caption) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.Caption;
	BEGIN
		f := StdCFrames.dir.NewCaption();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.label := c.label$;
		f.left := c.prop.opt[left];
		f.right := c.prop.opt[right];
		frame := f
	END GetNewFrame;

	PROCEDURE (c: Caption) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: Caption) HandlePropMsg2 (VAR msg: Properties.Message);
	BEGIN
		WITH msg: Properties.SizePref DO
			StdCFrames.dir.GetCaptionSize(msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard, left, right}
		| msg: DefaultsPref DO
			IF c.prop.link = "" THEN msg.disabled := FALSE END
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: Caption) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		f(StdCFrames.Caption).label := c.label$;
		f(StdCFrames.Frame).Update
	END Update;
	

	(* Group *)

	PROCEDURE (c: Group) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, grpVersion, thisVersion)
	END Internalize2;

	PROCEDURE (c: Group) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(grpVersion)
	END Externalize2;

	PROCEDURE (c: Group) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.Group;
	BEGIN
		f := StdCFrames.dir.NewGroup();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.label := c.label$;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: Group) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;

	PROCEDURE (c: Group) HandlePropMsg2 (VAR msg: Properties.Message);
	BEGIN
		WITH msg: Properties.SizePref DO
			StdCFrames.dir.GetGroupSize(msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard}
		| msg: DefaultsPref DO
			IF c.prop.link = "" THEN msg.disabled := FALSE END
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: Group) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		f(StdCFrames.Group).label := c.label$;
		f(StdCFrames.Frame).Update
	END Update;
	
	
	(* TreeControl *)
	
	PROCEDURE (c: TreeControl) Internalize2 (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		rd.ReadVersion(minVersion, tfVersion, thisVersion)
	END Internalize2;

	PROCEDURE (c: TreeControl) Externalize2 (VAR wr: Stores.Writer);
	BEGIN
		wr.WriteVersion(tfVersion)
	END Externalize2;

	PROCEDURE TVNofNodesF (VAR rec, par: ANYREC);
	BEGIN
		WITH par: TVParam DO 
			WITH rec: Dialog.Tree DO par.l := rec.NofNodes()
			ELSE par.l := 0
			END
		END
	END TVNofNodesF;
	
	PROCEDURE TVNofNodes (f: StdCFrames.TreeFrame): INTEGER;
		VAR c: TreeControl; par: TVParam;
	BEGIN
		c := f.view(TreeControl); par.l := 0;
		IF c.item.Valid() THEN c.item.CallWith(TVNofNodesF, par) END;
		RETURN par.l
	END TVNofNodes;
	
	PROCEDURE TVChildF (VAR rec, par: ANYREC);
	BEGIN
		WITH par: TVParam DO 
			WITH rec: Dialog.Tree DO par.nodeOut := rec.Child(par.nodeIn, Dialog.firstPos)
			ELSE par.nodeOut := NIL
			END
		END
	END TVChildF;
	
	PROCEDURE TVChild (f: StdCFrames.TreeFrame; node: Dialog.TreeNode): Dialog.TreeNode;
		VAR c: TreeControl; par: TVParam;
	BEGIN
		c := f.view(TreeControl); par.nodeIn := node; par.nodeOut := NIL;
		IF c.item.Valid() THEN c.item.CallWith(TVChildF, par) END;
		RETURN par.nodeOut
	END TVChild;
	
	PROCEDURE TVParentF (VAR rec, par: ANYREC);
	BEGIN
		WITH par: TVParam DO 
			WITH rec: Dialog.Tree DO par.nodeOut := rec.Parent(par.nodeIn)
			ELSE par.nodeOut := NIL
			END
		END
	END TVParentF;
	
	PROCEDURE TVParent (f: StdCFrames.TreeFrame; node: Dialog.TreeNode): Dialog.TreeNode;
		VAR c: TreeControl; par: TVParam;
	BEGIN
		c := f.view(TreeControl); par.nodeIn := node; par.nodeOut := NIL;
		IF c.item.Valid() THEN c.item.CallWith(TVParentF, par) END;
		RETURN par.nodeOut
	END TVParent;
	
	PROCEDURE TVNextF (VAR rec, par: ANYREC);
	BEGIN
		WITH par: TVParam DO 
			WITH rec: Dialog.Tree DO par.nodeOut := rec.Next(par.nodeIn)
			ELSE par.nodeOut := NIL
			END
		END
	END TVNextF;
	
	PROCEDURE TVNext (f: StdCFrames.TreeFrame; node: Dialog.TreeNode): Dialog.TreeNode;
		VAR c: TreeControl; par: TVParam;
	BEGIN
		c := f.view(TreeControl); par.nodeIn := node; par.nodeOut := NIL;
		IF c.item.Valid() THEN c.item.CallWith(TVNextF, par) END;
		RETURN par.nodeOut
	END TVNext;
	
	PROCEDURE TVSelectF (VAR rec, par: ANYREC);
	BEGIN
		WITH par: TVParam DO 
			WITH rec: Dialog.Tree DO rec.Select(par.nodeIn) END
		END
	END TVSelectF;
	
	PROCEDURE TVSelect (f: StdCFrames.TreeFrame; node: Dialog.TreeNode);
		VAR c: TreeControl; par: TVParam;
	BEGIN
		c := f.view(TreeControl); par.nodeIn := node;
		IF c.item.Valid() THEN 
			c.item.CallWith(TVSelectF, par);
			Notify(c, f, Dialog.changed, 0, 0)
		END
	END TVSelect;
	
	PROCEDURE TVSelectedF (VAR rec, par: ANYREC);
	BEGIN
		WITH par: TVParam DO 
			WITH rec: Dialog.Tree DO par.nodeOut := rec.Selected()
			ELSE par.nodeOut := NIL
			END
		END
	END TVSelectedF;
	
	PROCEDURE TVSelected (f: StdCFrames.TreeFrame): Dialog.TreeNode;
		VAR c: TreeControl; par: TVParam;
	BEGIN
		c := f.view(TreeControl); par.nodeOut := NIL;
		IF c.item.Valid() THEN c.item.CallWith(TVSelectedF, par) END;
		RETURN par.nodeOut
	END TVSelected;
	
	PROCEDURE TVSetExpansionF (VAR rec, par: ANYREC);
	BEGIN
		WITH par: TVParam DO 
			par.nodeIn.SetExpansion(par.e)
		END
	END TVSetExpansionF;
	
	PROCEDURE TVSetExpansion (f: StdCFrames.TreeFrame; tn: Dialog.TreeNode; expanded: BOOLEAN);
		VAR c: TreeControl; par: TVParam;
	BEGIN
		c := f.view(TreeControl); par.e := expanded; par.nodeIn := tn;
		IF c.item.Valid() THEN c.item.CallWith(TVSetExpansionF, par) END
	END TVSetExpansion;

	PROCEDURE (c: TreeControl) GetNewFrame (VAR frame: Views.Frame);
		VAR f: StdCFrames.TreeFrame;
	BEGIN
		f := StdCFrames.dir.NewTreeFrame();
		f.disabled := c.disabled;
		f.undef := c.undef;
		f.readOnly := c.readOnly;
		f.font := c.font;
		f.sorted := c.prop.opt[sorted];
		f.haslines := c.prop.opt[haslines];
		f.hasbuttons := c.prop.opt[hasbuttons];
		f.atroot := c.prop.opt[atroot];
		f.foldericons := c.prop.opt[foldericons];
		f.NofNodes := TVNofNodes;
		f.Child := TVChild;
		f.Parent := TVParent;
		f.Next := TVNext;
		f.Select := TVSelect;
		f.Selected := TVSelected;
		f.SetExpansion := TVSetExpansion;
		frame := f
	END GetNewFrame;

	PROCEDURE (c: TreeControl) UpdateList (f: Views.Frame);
	BEGIN
		f(StdCFrames.Frame).UpdateList()
	END UpdateList;
	
	PROCEDURE (c: TreeControl) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		WITH f: StdCFrames.Frame DO f.Restore(l, t, r, b) END
	END Restore;
	
	PROCEDURE (c: TreeControl) HandleCtrlMsg2 (f: Views.Frame; VAR msg: Controllers.Message;
																			VAR focus: Views.View);
	BEGIN
		WITH f: StdCFrames.TreeFrame DO
			IF ~c.disabled & ~c.readOnly THEN
				WITH msg: Controllers.EditMsg DO
					IF (msg.op = Controllers.pasteChar) THEN 
						f.KeyDown(msg.char, msg.modifiers)
					END
				ELSE
					CatchCtrlMsg(c, f, msg, focus)
				END
			ELSIF ~c.disabled THEN
				WITH msg: Controllers.TrackMsg DO
					f.MouseDown(msg.x, msg.y, msg.modifiers)
				ELSE
				END
			END
		END
	END HandleCtrlMsg2;

	PROCEDURE (c: TreeControl) HandlePropMsg2 (VAR msg: Properties.Message);
	BEGIN			
		WITH msg: Properties.ControlPref DO
			IF (msg.char = lineChar) OR (msg.char = esc) THEN msg.accepts := FALSE END;
			IF ~c.disabled & ~c.readOnly & IsShortcut(msg.char, c) OR msg.getFocus THEN
				msg.getFocus := StdCFrames.setFocus
			END
		| msg: Properties.FocusPref DO
			IF ~c.disabled & ~c.readOnly THEN
				msg.setFocus := TRUE
			ELSIF~c.disabled THEN
				msg.hotFocus := TRUE
			END
		| msg: Properties.SizePref DO
			StdCFrames.dir.GetTreeFrameSize(msg.w, msg.h)
		| msg: PropPref DO
			msg.valid := {link, label, guard, notifier, sorted, haslines, hasbuttons, atroot, foldericons}
		| msg: Properties.ResizePref DO
			msg.horFitToWin := TRUE; msg.verFitToWin := TRUE
		ELSE
		END
	END HandlePropMsg2;

	PROCEDURE (c: TreeControl) CheckLink (VAR ok: BOOLEAN);
		VAR name: Meta.Name;
	BEGIN
		GetTypeName(c.item, name);
		ok := name = "Tree"
	END CheckLink;

	PROCEDURE (c: TreeControl) Update (f: Views.Frame; op, from, to: INTEGER);
	BEGIN
		f(StdCFrames.Frame).Update
	END Update;
	

	(* StdDirectory *)

	PROCEDURE (d: StdDirectory) NewPushButton (p: Prop): Control;
		VAR c: PushButton;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewPushButton;

	PROCEDURE (d: StdDirectory) NewCheckBox (p: Prop): Control;
		VAR c: CheckBox;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewCheckBox;

	PROCEDURE (d: StdDirectory) NewRadioButton (p: Prop): Control;
		VAR c: RadioButton;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewRadioButton;

	PROCEDURE (d: StdDirectory) NewField (p: Prop): Control;
		VAR c: Field;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewField;
	
	PROCEDURE (d: StdDirectory) NewUpDownField (p: Prop): Control;
		VAR c: UpDownField;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewUpDownField;
	
	PROCEDURE (d: StdDirectory) NewDateField (p: Prop): Control;
		VAR c: DateField;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewDateField;

	PROCEDURE (d: StdDirectory) NewTimeField (p: Prop): Control;
		VAR c: TimeField;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewTimeField;
	
	PROCEDURE (d: StdDirectory) NewColorField (p: Prop): Control;
		VAR c: ColorField;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewColorField;

	PROCEDURE (d: StdDirectory) NewListBox (p: Prop): Control;
		VAR c: ListBox;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewListBox;

	PROCEDURE (d: StdDirectory) NewSelectionBox (p: Prop): Control;
		VAR c: SelectionBox;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewSelectionBox;

	PROCEDURE (d: StdDirectory) NewComboBox (p: Prop): Control;
		VAR c: ComboBox;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewComboBox;

	PROCEDURE (d: StdDirectory) NewCaption (p: Prop): Control;
		VAR c: Caption;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewCaption;

	PROCEDURE (d: StdDirectory) NewGroup (p: Prop): Control;
		VAR c: Group;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewGroup;

	PROCEDURE (d: StdDirectory) NewTreeControl (p: Prop): Control;
		VAR c: TreeControl;
	BEGIN
		NEW(c); OpenLink(c, p); RETURN c
	END NewTreeControl;

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

	PROCEDURE InitProp (VAR p: Prop);
	BEGIN
		NEW(p);
		p.link := ""; p.label := ""; p.guard := ""; p.notifier := "";
		p.level := 0;
		p.opt[0] := FALSE; p.opt[1] := FALSE;
		p.opt[2] := FALSE; p.opt[3] := FALSE;
		p.opt[4] := FALSE
	END InitProp;

	PROCEDURE DepositPushButton*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		p.label := "#System:untitled";
		Views.Deposit(dir.NewPushButton(p))
	END DepositPushButton;

	PROCEDURE DepositCheckBox*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		p.label := "#System:untitled";
		Views.Deposit(dir.NewCheckBox(p))
	END DepositCheckBox;

	PROCEDURE DepositRadioButton*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		p.label := "#System:untitled";
		Views.Deposit(dir.NewRadioButton(p))
	END DepositRadioButton;

	PROCEDURE DepositField*;
		VAR p: Prop;
	BEGIN
		InitProp(p); p.opt[left] := TRUE;
		Views.Deposit(dir.NewField(p))
	END DepositField;
	
	PROCEDURE DepositUpDownField*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		Views.Deposit(dir.NewUpDownField(p))
	END DepositUpDownField;
	
	PROCEDURE DepositDateField*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		Views.Deposit(dir.NewDateField(p))
	END DepositDateField;

	PROCEDURE DepositTimeField*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		Views.Deposit(dir.NewTimeField(p))
	END DepositTimeField;

	PROCEDURE DepositColorField*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		Views.Deposit(dir.NewColorField(p))
	END DepositColorField;

	PROCEDURE DepositListBox*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		Views.Deposit(dir.NewListBox(p))
	END DepositListBox;

	PROCEDURE DepositSelectionBox*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		Views.Deposit(dir.NewSelectionBox(p))
	END DepositSelectionBox;

	PROCEDURE DepositComboBox*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		Views.Deposit(dir.NewComboBox(p))
	END DepositComboBox;

	PROCEDURE DepositCancelButton*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		p.link := "StdCmds.CloseDialog"; p.label := "#System:Cancel"; p.opt[cancel] := TRUE;
		Views.Deposit(dir.NewPushButton(p))
	END DepositCancelButton;

	PROCEDURE DepositCaption*;
		VAR p: Prop;
	BEGIN
		InitProp(p); p.opt[left] := TRUE;
		p.label := "#System:Caption";
		Views.Deposit(dir.NewCaption(p))
	END DepositCaption;

	PROCEDURE DepositGroup*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		p.label := "#System:Caption";
		Views.Deposit(dir.NewGroup(p))
	END DepositGroup;
	
	PROCEDURE DepositTreeControl*;
		VAR p: Prop;
	BEGIN
		InitProp(p);
		p.opt[haslines] := TRUE; p.opt[hasbuttons] := TRUE; p.opt[atroot] := TRUE; p.opt[foldericons] := TRUE;
		Views.Deposit(dir.NewTreeControl(p))
	END DepositTreeControl;

	PROCEDURE Relink*;
		VAR msg: UpdateCachesMsg;
	BEGIN
		INC(stamp);
		Views.Omnicast(msg)
	END Relink;


	PROCEDURE Init;
		VAR d: StdDirectory;
	BEGIN
		par := NIL; stamp := 0;
		NEW(d); stdDir := d; dir := d;
		NEW(cleaner); cleanerInstalled := 0
	END Init;


	(* check guards action *)

	PROCEDURE (a: Action) Do;
		VAR msg: Views.NotifyMsg;
	BEGIN
		IF Windows.dir # NIL THEN
			IF a.w # NIL THEN
				INC(a.cnt);
				msg.id0 := 0; msg.id1 := 0; msg.opts := {guardCheck};
				IF a.w.seq # NIL THEN a.w.seq.Handle(msg) END;
				a.w := Windows.dir.Next(a.w);
				WHILE (a.w # NIL) & a.w.sub DO a.w := Windows.dir.Next(a.w) END
			ELSE
				IF a.cnt = 0 THEN a.resolution := Services.resolution
				ELSE a.resolution := Services.resolution DIV a.cnt DIV 2
				END;
				a.cnt := 0;
				a.w := Windows.dir.First();
				WHILE (a.w # NIL) & a.w.sub DO a.w := Windows.dir.Next(a.w) END
			END
		END;
		Services.DoLater(a, Services.Ticks() + a.resolution)
	END Do;

BEGIN
	Init;
	NEW(action); action.w := NIL; action.cnt := 0; Services.DoLater(action, Services.now)
CLOSE
	Services.RemoveAction(action)
END Controls.
