MODULE WinCFrames;
(**
	project	= "BlackBox"
	organization	= "www.oberon.ch"
	contributors	= "Oberon microsystems"
	version	= "System/Rsrc/About"
	copyright	= "System/Rsrc/About"
	license	= "Docu/BB-License"
	changes	= "
	- 20060407, mf, ListBox.DblClickOk: case f.sorted handled
	- 20070131, bh, Unicode support
	- 20070209, bh, Euro handling removed
	- 20151203, center #90, adding horizontal scroll bar to list boxes
	- 20160122, center #98, improvements of GUI controls
	- 20160704, center #116, calling Dialog.Update in the notifier of a selection box does not work as expected
	- 20170702, center #164 Backward compatibilty with UpDn Controls
	"
	issues	= "
	- ...
	"

**)

	(* ColorField, DateField, TimeField, UpDownField *)

	IMPORT
		SYSTEM, WinApi, WinCtl,
		Kernel, Strings, Dates, Fonts, Ports, Views,
		Controllers, Dialog,
		
		WinDialog, 
		HostFonts := WinFonts,
		HostRegistry := WinRegistry,
		HostPorts := WinPorts,
		StdCFrames;

	CONST
		ENTER = 0DX; ESC = 1BX; DEL = 07X; BS = 08X; TAB = 09X; LTAB = 0AX;
		AL = 1CX; AR = 1DX; AU = 1EX; AD = 1FX;
		PL = 10X; PR = 11X; PU = 12X; PD = 13X;
		DL = 14X; DR = 15X; DU = 16X; DD = 17X;
		dlgWindowExtra = 30;
		dropDownHeight = 30 * Ports.point;
		upDownWidth = 11 * Ports.point;
		numColors = 7;
		scrollRange = 16384;

		(* 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
		Procedure = PROCEDURE;

		Info = RECORD
			wnd, ctrl, ud: WinApi.HANDLE;
			x1, y1, w1, h1, x2, y2, w2, h2: INTEGER;
			hasFocus, dropDown, readOnly, undef: BOOLEAN;
			bkgnd: Ports.Color
		END;

		PushButton = POINTER TO RECORD (StdCFrames.PushButton)
			i: Info
		END;

		CheckBox = POINTER TO RECORD (StdCFrames.CheckBox)
			i: Info
		END;

		RadioButton = POINTER TO RECORD (StdCFrames.RadioButton)
			i: Info
		END;

		ScrollBar = POINTER TO RECORD (StdCFrames.ScrollBar)
			i: Info
		END;

		Field = POINTER TO RECORD (StdCFrames.Field)
			i: Info;
			isUpdate: BOOLEAN;
			del: BOOLEAN (* needed know when an empty field is allowed. not perfect. *)
		END;

		UpDownField = POINTER TO RECORD (StdCFrames.UpDownField)
			i: Info;
			val: INTEGER;
			isUpdate: BOOLEAN
		END;

		DateField = POINTER TO RECORD (StdCFrames.DateField)
			i: Info;
			isUpdate: BOOLEAN;
			cnt, val: INTEGER	(* input state: val = current val, cnt = number of key strokes *)
		END;

		TimeField = POINTER TO RECORD (StdCFrames.TimeField)
			i: Info;
			isUpdate: BOOLEAN;
			cur: INTEGER
		END;

		ColorField = POINTER TO RECORD (StdCFrames.ColorField)
			i: Info;
			color: Ports.Color
		END;

		ListBox = POINTER TO RECORD (StdCFrames.ListBox)
			i: Info
		END;

		SelectionBox = POINTER TO RECORD (StdCFrames.SelectionBox)
			i: Info;
			num: INTEGER;
			delayUpdate, needsUpdate: BOOLEAN
		END;

		ComboBox = POINTER TO RECORD (StdCFrames.ComboBox)
			i: Info;
			edit: WinApi.HANDLE
		END;

		Caption = POINTER TO RECORD (StdCFrames.Caption)
			i: Info
		END;

		Group = POINTER TO RECORD (StdCFrames.Group)
			i: Info
		END;

		TFInfo = RECORD
			tn: Dialog.TreeNode;
			wndAdr: INTEGER
		END;

		TreeFrame = POINTER TO RECORD (StdCFrames.TreeFrame)
			i: Info;
			inUpdateList: BOOLEAN;
			treeArray: POINTER TO ARRAY OF TFInfo;
			curindex, selIndex: INTEGER;
			folderimg, openimg, leafimg: INTEGER;
			himl: WinCtl.Ptr_IMAGELIST
		END;

		Directory = POINTER TO RECORD (StdCFrames.Directory) END;

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

		BrushCache = POINTER TO RECORD
			next: BrushCache;
			col: Ports.Color;
			brush: WinApi.HANDLE
		END;

	VAR
		instance: WinApi.HANDLE;
		SubclassCtl: PROCEDURE(wnd: WinApi.HANDLE);
		CtlColor: PROCEDURE(msg, wPar, lPar: INTEGER): INTEGER;
		scW, scH: INTEGER;	(* screen width and height *)
		updateAllowed: BOOLEAN;
		colors: ARRAY numColors OF Ports.Color;
		(* date format *)
		dateSep: CHAR;	(* separator character *)
		yearPart, monthPart, dayPart: INTEGER;	(* first = 1, last = 3 *)
		del1, del2: INTEGER;	(* position of separators *)
		(* time format *)
		timeSep: CHAR;	(* separator character *)
		lastPart: INTEGER;	(* 3 for 24h format, 4 for 12h format *)
		desig: ARRAY 2, 8 OF CHAR;	(* AM, PM strings *)
		inHandleMouse*: BOOLEAN; (* to disable the main window handler in HostMenus *)
		brushes: BrushCache;
		

	(* auxiliary procedures *)

	PROCEDURE WheelToArrow (f: StdCFrames.Frame; op: INTEGER; VAR done: BOOLEAN);
	BEGIN
		IF op IN {Controllers.incLine, Controllers.incPage} THEN
			f.KeyDown(AD, {})
		ELSE
			f.KeyDown(AU, {})
		END;
		done := TRUE
	END WheelToArrow;

	PROCEDURE GetSize (f: StdCFrames.Frame; VAR x, y, w, h: INTEGER);
		VAR u, x0, y0: INTEGER;
	BEGIN
		u := f.unit; f.view.context.GetSize(w, h); x0 := f.gx; y0 := f.gy;
		x := x0 DIV u; y := y0 DIV u;
		w := (x0 + w) DIV u - x; h := (y0 + h) DIV u - y;
		IF (SubclassCtl # NIL)
			& ((f IS Field) OR (f IS UpDownField) OR (f IS TimeField) OR (f IS DateField)
				OR (f IS ColorField) OR (f IS ListBox) OR (f IS SelectionBox) OR (f IS ComboBox)) THEN
			INC(x, 1); INC(y, 1); DEC(w, 2); DEC(h, 2)	(* space for 3d border *)
		END
	END GetSize;

	PROCEDURE Adapt (f: StdCFrames.Frame; VAR i: Info);
		VAR res, x, y, w, h, cx, cy, cw, ch, udw, n, h0, h1: INTEGER; r: HostPorts.Rider;
	BEGIN
		IF i.wnd # 0 THEN
			r := f.rider(HostPorts.Rider);
			IF r.port.wnd # 0 THEN	(* port is a window *)
				GetSize(f, x, y, w, h);
				IF i.dropDown THEN
					n := WinApi.SendMessageW(i.ctrl, WinApi.CB_GETCOUNT, 0, 0);
					h0 := WinApi.SendMessageW(i.ctrl, WinApi.CB_GETITEMHEIGHT, -1, 0);
					h1 := WinApi.SendMessageW(i.ctrl, WinApi.CB_GETITEMHEIGHT, 0, 0);
					IF n < 1 THEN n := 1 ELSIF n > 8 THEN n := 8 END;
					h := h0 + h1 * n + 8
				END;
				cx := r.l; cy := r.t; cw := r.r - r.l; ch := r.b - r.t;
				IF (cx # i.x1) OR (cy # i.y1) OR (cw # i.w1) OR (ch # i.h1) THEN
					i.x1 := cx; i.y1 := cy; i.w1 := cw; i.h1 := ch;
					res := WinApi.MoveWindow(i.wnd, cx, cy, cw, ch, 0)
				END;
				DEC(x, cx); DEC(y, cy);
				IF (x # i.x2) OR (y # i.y2) OR (w # i.w2) OR (h # i.h2) THEN
					i.x2 := x; i.y2 := y; i.w2 := w; i.h2 := h;
					IF i.ud # 0 THEN
						udw := upDownWidth DIV f.unit;
						res := WinApi.MoveWindow(i.ud, x + w - udw, y + 1, udw, h - 2, 0);
						DEC(w, udw);
						IF SubclassCtl # NIL THEN DEC(w) END
					END;
					res := WinApi.MoveWindow(i.ctrl, x, y, w, h, 0)
				END
			END
		END
	END Adapt;

	PROCEDURE BkGndColor (f: Views.Frame): Ports.Color;
		VAR bgnd: Ports.Color; g: Views.Frame;
	BEGIN
		g := f;
		REPEAT
			g := Views.HostOf(g);
			bgnd := Views.transparent;
			g.view.GetBackground(bgnd)
		UNTIL bgnd # Views.transparent;
		RETURN bgnd
	END BkGndColor;

	PROCEDURE Open (f: StdCFrames.Frame; class, name: WinApi.PtrWSTR;
																style, ex: SET; VAR i: Info);
		VAR res, x, y, w, h, cx, cy, cw, ch, udw: INTEGER; p: HostPorts.Port; s: SET; r: Ports.Rider;
			icex: WinCtl.INITCOMMONCONTROLSEX;
	BEGIN
		f.noRedraw := TRUE;
		r := f.rider; GetSize(f, x, y, w, h);
		r.GetRect(cx, cy, cw, ch); cw := cw - cx; ch := ch - cy;
		p := f.rider(HostPorts.Rider).port;
		IF p.wnd # 0 THEN	(* port is a window *)
			i.bkgnd := BkGndColor(f);
			s := {27, 30};	(* disabled, child *)
			i.wnd := WinApi.CreateWindowExW({}, "Oberon Ctrl", "", s, cx, cy, cw, ch, p.wnd, 0, instance, 0);
			IF i.wnd # 0 THEN
				DEC(x, cx); DEC(y, cy);
				res := WinApi.SetWindowLongW(i.wnd, dlgWindowExtra, SYSTEM.VAL(INTEGER, f));
				i.ctrl := WinApi.CreateWindowExW(ex, class, name, style, x, y, w, h, i.wnd, 0, instance, 0);
				IF i.ctrl # 0 THEN
					IF (SubclassCtl # NIL) & ~(f IS PushButton) THEN SubclassCtl(i.ctrl) END;
					IF f.font # NIL THEN
						res := WinApi.SendMessageW(i.ctrl, WinApi.WM_SETFONT, f.font(HostFonts.Font).dev.id, 0)
					END;
					IF (f IS UpDownField) OR (f IS TimeField) OR (f IS DateField) THEN
						style := {23, 28, 30} + WinCtl.UDS_ALIGNRIGHT + WinCtl.UDS_ARROWKEYS;
						udw := upDownWidth DIV f.unit;
						i.ud := WinCtl.CreateUpDownControl(style, x + w - udw, y, udw, h, i.wnd, 0, instance, 0, 1, 0, 0)
						(*
						icex.dwSize := SIZE(WinCtl.INITCOMMONCONTROLSEX); icex.dwICC := WinCtl.ICC_UPDOWN_CLASS;
						res := WinCtl.InitCommonControlsEx(icex);
						i.ud :=  WinApi.CreateWindowExW (
							{}, WinCtl.UPDOWN_CLASSW, "", style,
							 x + w - udw, y, udw, h,
							(*0, 0, 0, 0, (*Set to zero to automatically size to fit the buddy window.*)*)
							i.wnd, 0, instance, 0);
						res := WinApi.SendMessageW(i.ud,  WinCtl.UDM_SETBUDDY, i.ctrl, 0);
						res := WinApi.SendMessageW(i.ud,  WinApi.WM_USER+111(*UDM_SETRANGE32*), 0, 1);
						res := WinApi.SendMessageW(i.ud,  WinCtl.UDM_SETPOS, 0, 0);
						alternative for "obsoleted" CreateUpDownControl*)
					END;
					Adapt(f, i);
					res := WinApi.ShowWindow(i.wnd, 1);
					res := WinApi.ShowWindow(i.ctrl, 1);
					IF i.ud # 0 THEN res := WinApi.ShowWindow(i.ud, 1) END
				ELSE res := WinApi.DestroyWindow(i.wnd); i.wnd := 0
				END
			ELSE i.ctrl := 0
			END
		ELSE	(* port is a printer -> define temp windows *)
			i.wnd := 0; i.ctrl := 0
(*
			s := {27, 31};	(* disabled, popup *)
			i.wnd := WinApi.CreateWindowExW({}, "Oberon Ctrl", "", s, scW - cw, scH - ch, cw, ch, 0, 0, instance, 0);
			res := WinApi.SetWindowLongW(i.wnd, dlgWindowExtra, SYSTEM.VAL(LONGINT, f));
			i.ctrl := WinApi.CreateWindowExW({}, class, name, style, x - cx, y - cy, w, h, i.wnd, 0, instance, 0)
*)
		END
	END Open;

	PROCEDURE Paint (f: StdCFrames.Frame; VAR i: Info);
		VAR res, u: INTEGER; r: HostPorts.Rider;
	BEGIN
		r := f.rider(HostPorts.Rider); u := f.unit;
		r.port.CloseBuffer;
		updateAllowed := TRUE;
		res := WinApi.InvalidateRect(i.wnd, NIL, 0);
		res := WinApi.InvalidateRect(i.ctrl, NIL, 0);
		res := WinApi.UpdateWindow(i.wnd);
		res := WinApi.UpdateWindow(i.ctrl);
		IF i.ud # 0 THEN res := WinApi.UpdateWindow(i.ud) END;
		updateAllowed := FALSE
	END Paint;

	PROCEDURE PaintRect (f: StdCFrames.Frame; VAR i: Info; l, t, r, b: INTEGER);
		VAR res, u: INTEGER; ri: HostPorts.Rider; rect: WinApi.RECT;
	BEGIN
		ri := f.rider(HostPorts.Rider); u := f.unit;
		ri.port.CloseBuffer;
		updateAllowed := TRUE;
		rect.left := l DIV f.unit; rect.top := t DIV f.unit; rect.right := r DIV f.unit; rect.bottom := b DIV f.unit;
		rect.left := rect.left; rect.top := rect.top; rect.right := rect.right; rect.bottom := rect.bottom ;
		(* round up if possible *)
		IF rect.left > i.x1 THEN rect.left := rect.left - 1 END;
		IF rect.top > i.y1 THEN rect.top := rect.top - 1 END;
		IF rect.right < i.x1 + i.w1 THEN rect.right := rect.right + 1 END;
		IF rect.bottom < i.y1 + i.h1 THEN rect.bottom := rect.bottom + 1 END;
		res := WinApi.InvalidateRect(i.wnd, rect, 0);
		res := WinApi.InvalidateRect(i.ctrl, rect, 0);
		res := WinApi.UpdateWindow(i.wnd);
		res := WinApi.UpdateWindow(i.ctrl);
		IF i.ud # 0 THEN res := WinApi.UpdateWindow(i.ud) END;
		updateAllowed := FALSE
	END PaintRect;

	PROCEDURE ExtractLine (IN s: ARRAY OF CHAR; beg: INTEGER; fnt: Fonts.Font; maxW: INTEGER;
		OUT line: Dialog.String; OUT lineW, end: INTEGER
	);
		VAR i, len, w: INTEGER; ch: CHAR;
	BEGIN
		len := LEN(s$);
		ASSERT(beg >= 0, 20); ASSERT(beg < LEN(s$), 21); ASSERT(maxW > 0, 22); ASSERT(fnt # NIL, 23);
		i := 0; end := beg; lineW := 0; w := 0;
		REPEAT
			ch := s[i + beg];
			IF ch <= " " THEN end := i + beg; lineW := w END;
			line[i] := ch; INC(i); line[i] := 0X; w := fnt.StringWidth(line);
			IF (w <= maxW) & ((ch = "-") OR (ch = "_")) THEN end := i + beg; lineW := w END
		UNTIL (i + beg = len) OR (w > maxW);
		IF w <= maxW THEN end := beg + i; line[end - beg] := 0X; lineW := w
		ELSIF end # beg THEN line[end - beg] := 0X
		ELSE end := beg + i - 1; line[i - 1] := 0X; lineW := fnt.StringWidth(line)
		END;
		ASSERT(end > beg, 80)
	END ExtractLine;

	PROCEDURE PrintString (f: Views.Frame; w: INTEGER;
		IN string: ARRAY OF CHAR; fnt: Fonts.Font; col: Ports.Color;
		x0, y0: INTEGER; left, right, multiLine: BOOLEAN
	);
		VAR x, sw, asc, dsc, fw, beg, end, len, n: INTEGER; s: Dialog.String;
	BEGIN
		fnt.GetBounds(asc, dsc, fw); sw := fnt.StringWidth(string);
		w := MAX(fw, w - 2 * x0); (* ensures that always at least one character fits *)
		beg := 0; len := LEN(string$); n := 0;
		WHILE beg < len DO
			IF multiLine THEN ExtractLine(string, beg, fnt, w, s, sw, end)
			ELSE s := string$; sw := fnt.StringWidth(s); end := len
			END;
			IF ~left & ~right THEN x := (w - sw) DIV 2 ELSIF right & ~left THEN x := w - sw ELSE x := 0 END;
			f.DrawString(x0 + x, n * (asc + dsc) + y0, col, s, fnt);
			beg := end; INC(n);
			IF (beg < len) & (string[beg] = " ") THEN INC(beg) END
		END
	END PrintString;

	PROCEDURE Print (f: StdCFrames.Frame; d, x, y: INTEGER; label: ARRAY OF CHAR);
		VAR w, h, sw, a, asc, dsc, fw: INTEGER; font: Fonts.Font; on: BOOLEAN;
	BEGIN
		IF f.font # NIL THEN font := f.font ELSE font := HostFonts.dlgFont END;
		f.view.context.GetSize(w, h);
		font.GetBounds(asc, dsc, fw);
		IF x < 0 THEN sw := font.StringWidth(label); x := (w - sw) DIV 2 END;
		IF y < 0 THEN y := (h + asc - dsc) DIV 2
		ELSIF y = 0 THEN y := asc
		END;
		a := (h - 8 * Ports.point) DIV 2;
		IF d = -1 THEN
			f.DrawRect(2 * Ports.point, a, 10 * Ports.point, a + 8 * Ports.point, f.dot, Ports.defaultColor);
			IF (f IS CheckBox) & (f(CheckBox).Get # NIL) THEN
				f(CheckBox).Get(f(CheckBox), on);
				IF on THEN
					f.DrawLine(
						2 * Ports.point + 2 * f.dot, a + 4 * Ports.point, 5 * Ports.point, a + 8 * Ports.point - 2 * f.dot,
						2 * Ports.point, Ports.defaultColor
						);
					f.DrawLine(
						5 * Ports.point, a + 8 * Ports.point - 2 * f.dot, 10 * Ports.point - 2 * f.dot, a + 2 * f.dot,
						2 * Ports.point, Ports.defaultColor
						)
				END
			END
		ELSIF d = -2 THEN f.DrawOval(2 * Ports.point, a, 10 * Ports.point, a + 8 * Ports.point, f.dot, Ports.defaultColor);
			IF f(RadioButton).Get # NIL THEN f(RadioButton).Get(f(RadioButton), on);
				IF on THEN f.DrawOval(2*Ports.point + 2*f.dot, a+2*f.dot,
											10*Ports.point - 2*f.dot, a + 8*Ports.point-2*f.dot, Ports.fill, Ports.black)
				END
			END
		ELSIF d = -3 THEN
			f.DrawRect(0, MIN(asc - f.dot, h), w, MAX(asc - f.dot, h), f.dot, Ports.defaultColor);
			f.DrawRect(x, y - asc, x + font.StringWidth(label), y + dsc, Ports.fill, Ports.background)
		ELSIF d > 0 THEN f.DrawRect(0, 0, w, h, d, Ports.defaultColor)
		END;
(*
		WITH f: Field DO
			IF f.right & ~f.left THEN x := w - x - font.SStringWidth(label)
			ELSIF ~f.right & ~f.left THEN x := (w DIV 2) - (font.SStringWidth(label) DIV 2)
			END
		| f: Caption DO
			IF f.right & ~f.left THEN x := w - x - font.SStringWidth(label)
			ELSIF ~f.right & ~f.left THEN x := (w DIV 2) - (font.SStringWidth(label) DIV 2)
			END
		ELSE
		END;
		f.DrawSString(x, y, Ports.defaultColor, label, font)
*)
		WITH f: Field DO
			PrintString(f, w, label, font, Ports.defaultColor, x, y, f.left, f.right, f.multiLine)
		| f: Caption DO
			PrintString(f, w, label, font, Ports.defaultColor, x, y, f.left, f.right, TRUE)
		ELSE
			PrintString(f, w, label, font, Ports.defaultColor, x, y, TRUE, FALSE, FALSE)
		END
	END Print;

	PROCEDURE IsEdit (wnd: WinApi.HANDLE): BOOLEAN;
		VAR res: INTEGER; nm: ARRAY 32 OF CHAR;
	BEGIN
		res := WinApi.GetClassNameW(wnd, nm, LEN(nm));
		IF res > 0 THEN Strings.ToUpper(nm, nm)				
		ELSE nm := ""
		END;
		RETURN nm = "EDIT"
	END IsEdit;

	PROCEDURE SendRightClick (edit: WinApi.HANDLE);
		VAR res, n, pos, x, y: INTEGER;
	BEGIN
		res := WinApi.SendMessageW(edit, WinApi.EM_GETSEL, 0, SYSTEM.ADR(n));
		IF n > 0 THEN DEC(n) END;
		pos := WinApi.SendMessageW(edit, WinApi.EM_POSFROMCHAR, n, 0);
		x := (pos MOD 65536) + 8; y := (pos DIV 65536) + 8;
		pos :=  x + 65536*y;
		res := WinApi.SendMessageW(edit, WinApi.WM_RBUTTONDOWN, 0, pos);
		res := WinApi.SendMessageW(edit, WinApi.WM_RBUTTONUP, 0, pos )
	END SendRightClick;

	PROCEDURE SendKey (ch: CHAR; wnd: WinApi.HANDLE);
		VAR res, code: INTEGER;
	BEGIN
		CASE ch OF
		| 10X: code := 21H
		| 11X: code := 22H
		| 12X: code := 21H
		| 13X: code := 22H
		| 14X: code := 24H
		| 15X: code := 23H
		| 16X: code := 24H
		| 17X: code := 23H
		| 1CX: code := 25H
		| 1DX: code := 27H
		| 1EX: code := 26H
		| 1FX: code := 28H
		| DEL: code := 2EH
		ELSE code := 0
		END;
		IF code # 0 THEN
			res := WinApi.SendMessageW(wnd, WinApi.WM_KEYDOWN, code, 0);
			res := WinApi.SendMessageW(wnd, WinApi.WM_KEYUP, code, 0)
		ELSIF IsEdit(wnd) THEN
			IF ch = apps THEN SendRightClick(wnd)
			ELSIF ch = ctrlZ THEN res := WinApi.SendMessageW(wnd, WinApi.EM_UNDO, 0, 0)
			ELSIF ch = ctrlA THEN res := WinApi.SendMessageW(wnd, WinApi.EM_SETSEL, 0, -1)
			ELSE res := WinApi.SendMessageW(wnd, WinApi.WM_CHAR, ORD(ch), 0)
			END
		ELSE
			res := WinApi.SendMessageW(wnd, WinApi.WM_CHAR, ORD(ch), 0)
		END
	END SendKey;

	PROCEDURE (t: TrapCleaner) Cleanup;
	BEGIN
		inHandleMouse := FALSE
	END Cleanup;

	PROCEDURE HandleMouse (wnd: WinApi.HANDLE; x, y: INTEGER; buttons: SET);
		VAR res, b, hc, m: INTEGER; pt: WinApi.POINT; w: WinApi.HANDLE; msg: WinApi.MSG;
			tc: TrapCleaner;
	BEGIN
		NEW(tc); Kernel.PushTrapCleaner(tc); inHandleMouse := TRUE;
		res := WinApi.ReleaseCapture(); b := 0;
		IF HostPorts.left IN buttons THEN INC(b) END;
		IF HostPorts.right IN buttons THEN INC(b, 2) END;
		IF Controllers.extend IN buttons THEN INC(b, 4) END;
		IF Controllers.modify IN buttons THEN INC(b, 8) END;
		pt.x := x; pt.y := y;
		REPEAT
			w := wnd; wnd := WinApi.ChildWindowFromPoint(wnd, pt);
			res := WinApi.ClientToScreen(w, pt);
			hc := WinApi.SendMessageW(wnd, WinApi.WM_NCHITTEST, 0, pt.x + pt.y * 65536);
			res := WinApi.ScreenToClient(wnd, pt)
		UNTIL (hc # 1) OR (w = wnd);
		IF hc > 1 THEN
			res := WinApi.ClientToScreen(wnd, pt);
			IF ODD(b) THEN m := WinApi.WM_NCLBUTTONDOWN ELSE m := WinApi.WM_NCRBUTTONDOWN END;
			res := WinApi.SendMessageW(wnd, m, hc, pt.x + pt.y * 65536)
		ELSE
			res := WinApi.GetClassLongW(wnd, -26);	(* classStyle *)
			IF (Controllers.doubleClick IN buttons) & ODD(res DIV 8) THEN	(* DblClks IN classStyle *)
				IF ODD(b) THEN m := WinApi.WM_LBUTTONDBLCLK ELSE m := WinApi.WM_RBUTTONDBLCLK END
			ELSE
				IF ODD(b) THEN m := WinApi.WM_LBUTTONDOWN ELSE m := WinApi.WM_RBUTTONDOWN END
			END;
			res := WinApi.SendMessageW(wnd, m, b, pt.x + pt.y * 65536)
		END;
		REPEAT
			res := WinApi.GetMessageW(msg, 0, 0, 0);
			IF (msg.message >= WinApi.WM_MOUSEMOVE) & (msg.message <= WinApi.WM_MBUTTONDBLCLK) THEN
				b := msg.wParam
			END;
			IF (msg.message = WinApi.WM_RBUTTONUP) & (b MOD 4 = 0) & (31 IN buttons) THEN
				pt.x := msg.lParam MOD 65536; pt.y := msg.lParam DIV 65536;
				res := WinApi.ClientToScreen(msg.hwnd, pt);
				res := WinApi.ScreenToClient(wnd, pt);
				msg.lParam := pt.x + pt.y * 65536;
				res := WinApi.SendMessageW(wnd,  WinApi.WM_RBUTTONDOWN, msg.wParam, msg.lParam);
				res := WinApi.SendMessageW(wnd,  WinApi.WM_RBUTTONUP, msg.wParam, msg.lParam);
			ELSE
				res := WinApi.TranslateMessage(msg);
				res := WinApi.DispatchMessageW(msg)
			END;
		UNTIL b MOD 4 = 0;
		inHandleMouse := FALSE; Kernel.PopTrapCleaner(tc)
	END HandleMouse;

	PROCEDURE HandleWheel (wnd: WinApi.HANDLE; x, y: INTEGER; op, nofLines: INTEGER;
		VAR done: BOOLEAN
	);
		VAR res: INTEGER;
	BEGIN
		IF op = Controllers.incPage THEN
			res := WinApi.SendMessageW(wnd, WinApi.WM_VSCROLL, WinApi.SB_PAGEDOWN, WinApi.NULL)
		ELSIF op = Controllers.decPage THEN
			res := WinApi.SendMessageW(wnd, WinApi.WM_VSCROLL, WinApi.SB_PAGEUP, WinApi.NULL)
		ELSE
			IF op = Controllers.incLine THEN
				op := WinApi.SB_LINEDOWN
			ELSE
				op := WinApi.SB_LINEUP
			END;
			WHILE nofLines > 0 DO
				res := WinApi.SendMessageW(wnd, WinApi.WM_VSCROLL, op, WinApi.NULL);
				DEC(nofLines)
			END
		END;
		done := TRUE
	END HandleWheel;

	PROCEDURE Mark (on, focus: BOOLEAN; VAR i: Info);
		VAR res: INTEGER;
	BEGIN
		IF focus THEN
			IF on THEN
				IF ~i.hasFocus THEN
					res := WinApi.SendMessageW(i.ctrl, WinApi.WM_SETFOCUS, 0, 0);
					i.hasFocus := TRUE
				END
			ELSE
				IF i.hasFocus THEN
					res := WinApi.SendMessageW(i.ctrl, WinApi.WM_KILLFOCUS, 0, 0);
					i.hasFocus := FALSE
				END
			END
		END
	END Mark;

	PROCEDURE SetLabel (IN in: ARRAY OF CHAR; OUT out: ARRAY OF CHAR);
	BEGIN
		Dialog.MapString(in, out);
	END SetLabel;

	PROCEDURE CheckLabel (IN label: ARRAY OF CHAR; ctrl: INTEGER);
		VAR res: INTEGER; lbl, s: Dialog.String;
	BEGIN
		Dialog.MapString(label, lbl);
		res := WinApi.GetWindowTextW(ctrl, s, LEN(s));
		IF s # lbl THEN res := WinApi.SetWindowTextW(ctrl, lbl) END
	END CheckLabel;

	(* PushButton *)

	PROCEDURE (f: PushButton) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: PushButton) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f: PushButton) Update;
		VAR res: INTEGER; style: SET;
	BEGIN
		IF ~f.disabled & ~f.readOnly THEN
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN
				IF f.default THEN
					style := BITS(WinApi.GetWindowLongW(f.i.ctrl, -16));	(* window style *)
					res := WinApi.SetWindowLongW(f.i.ctrl, -16, ORD(style + {0}))
				END;
				res := WinApi.EnableWindow(f.i.ctrl, 1)
			END
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN
				IF f.default THEN
					style := BITS(WinApi.GetWindowLongW(f.i.ctrl, -16));	(* window style *)
					res := WinApi.SetWindowLongW(f.i.ctrl, -16, ORD(style - {0}))
				END;
				res := WinApi.EnableWindow(f.i.ctrl, 0)
			END
		END;
		CheckLabel(f.label, f.i.ctrl);
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: PushButton) Restore (l, t, r, b: INTEGER);
		VAR style: SET; lbl: Dialog.String;
	BEGIN
		SetLabel(f.label, lbl);
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			style := {16, 30};	(* pushbutton, tabstop, child *)
			IF f.default & ~f.disabled THEN INCL(style, 0) END;	(* default *)
			Open(f, "BUTTON", lbl, style, {}, f.i)
		END;
		f.Update;
		IF f.i.wnd # 0 THEN Paint(f, f.i) ELSE Print(f, f.dot, -1, -1, lbl) END
	END Restore;

	PROCEDURE (f: PushButton) MouseDown (x, y: INTEGER; buttons: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons)
		END
	END MouseDown;

	PROCEDURE Execute (f: PushButton);
		VAR old: WinApi.HANDLE;
	BEGIN
		IF f.Do # NIL THEN
			old := WinApi.SetCursor(HostPorts.cursors[HostPorts.busyCursor]);
			Dialog.ShowStatus("");
			f.Do(f);
			old := WinApi.SetCursor(old)
		END
	END Execute;

	PROCEDURE (f: PushButton) KeyDown (ch: CHAR; modifiers: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		Execute(f)
	END KeyDown;

	PROCEDURE (f: PushButton) Mark (on, focus: BOOLEAN);
	BEGIN
		Mark(on, f.front, f.i)
	END Mark;



	(* CheckBox *)

	PROCEDURE (f: CheckBox) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: CheckBox) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f: CheckBox) Update;
		VAR res: INTEGER; value: BOOLEAN;
	BEGIN
		IF ~f.disabled THEN
			f.Get(f, value);
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END;
			IF f.undef THEN res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETCHECK, 2, 0)
			ELSIF value THEN res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETCHECK, 1, 0)
			ELSE res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETCHECK, 0, 0)
			END;
			IF f.readOnly THEN res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETSTATE, WinApi.TRUE, 0)
			ELSE res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETSTATE, WinApi.FALSE, 0)
			END
		ELSE
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETCHECK, 0, 0);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETSTATE, WinApi.FALSE, 0);
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN
				res := WinApi.EnableWindow(f.i.ctrl, 0)
			END
		END;
		CheckLabel(f.label, f.i.ctrl);
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: CheckBox) Restore (l, t, r, b: INTEGER);
		VAR style: SET; lbl: Dialog.String;
	BEGIN
		SetLabel(f.label, lbl);
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
(*
			f.Get(f, value);	(* update f.undef *)
			IF f.undef THEN style := {1, 2, 16, 30}	(* auto 3state, tabstop, child *)
			ELSE style := {0, 1, 16, 30}	(* auto checkbox, tabstop, child *)
			END;
*)
			style := {0, 2, 16, 30};	(* 3state, tabstop, child *)
			Open(f, "BUTTON", lbl, style, {}, f.i)
		END;
		f.Update;
		IF f.i.wnd # 0 THEN Paint(f, f.i) ELSE Print(f, -1, 12 * Ports.point, -1, lbl) END
	END Restore;

	PROCEDURE (f: CheckBox) MouseDown (x, y: INTEGER; buttons: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons)
		END
	END MouseDown;

	PROCEDURE (f: CheckBox) KeyDown (ch: CHAR; modifiers: SET);
		VAR res: INTEGER;
	BEGIN
		ASSERT(~f.disabled, 100);
		IF ch >= " " THEN
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYDOWN, ORD(" "), 0);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYUP, ORD(" "), 0)
		END
	END KeyDown;

	PROCEDURE (f: CheckBox) Mark (on, focus: BOOLEAN);
	BEGIN
		Mark(on, f.front, f.i)
	END Mark;



	(* RadioButton *)

	PROCEDURE (f: RadioButton) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: RadioButton) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f: RadioButton) Update;
		VAR res: INTEGER; value: BOOLEAN;
	BEGIN
		IF ~f.disabled THEN
			f.Get(f, value);
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END;
			IF f.undef THEN res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETCHECK, 0, 0)
			ELSIF value THEN res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETCHECK, 1, 0)
			ELSE res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETCHECK, 0, 0)
			END;
			IF f.readOnly THEN res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETSTATE, WinApi.TRUE, 0)
			ELSE res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETSTATE, WinApi.FALSE, 0)
			END
		ELSE
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETCHECK, 0, 0);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.BM_SETSTATE, WinApi.FALSE, 0);
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 0) END
		END;
		CheckLabel(f.label, f.i.ctrl);
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: RadioButton) Restore (l, t, r, b: INTEGER);
		VAR lbl: Dialog.String;
	BEGIN
		SetLabel(f.label, lbl);
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			Open(f, "BUTTON", lbl, {0, 3, 16, 30}, {}, f.i)	(* auto radiobutton, tabstop, child *)
		END;
		f.Update;
		IF f.i.wnd # 0 THEN Paint(f, f.i) ELSE Print(f, -2, 12 * Ports.point, -1, lbl) END
	END Restore;

	PROCEDURE (f: RadioButton) MouseDown (x, y: INTEGER; buttons: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons)
		END
	END MouseDown;

	PROCEDURE (f: RadioButton) KeyDown (ch: CHAR; modifiers: SET);
		VAR res: INTEGER;
	BEGIN
		ASSERT(~f.disabled, 100);
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYDOWN, ORD(" "), 0);
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYUP, ORD(" "), 0)
	END KeyDown;

	PROCEDURE (f: RadioButton) Mark (on, focus: BOOLEAN);
	BEGIN
		Mark(on, f.front, f.i)
	END Mark;



	(* ScrollBar *)

	PROCEDURE (f: ScrollBar) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: ScrollBar) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f: ScrollBar) Update;
		VAR res, size, sect, pos, p, q, m: INTEGER; i: WinApi.SCROLLINFO;
	BEGIN
		IF ~f.disabled THEN
			f.Get(f, size, sect, pos);
			IF size > sect THEN
				IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END;

				p := WinApi.MulDiv(pos, scrollRange, size - sect);
				i.cbSize := SIZE(WinApi.SCROLLINFO); i.fMask := {0, 1, 2};	(* range, page, pos *)
				res := WinApi.GetScrollInfo(f.i.ctrl, WinApi.SB_CTL, i);
				IF (res # 0) THEN
					IF sect > 0 THEN q := WinApi.MulDiv(sect, scrollRange, size - sect); m := scrollRange + q
					ELSE q := -1; m := scrollRange
					END;
					IF (i.nPos # p) OR (i.nPage # q + 1) THEN
						i.nPos := p; i.nPage := q + 1; i.nMax := m;
						res := WinApi.SetScrollInfo(f.i.ctrl, WinApi.SB_CTL, i, 1)
					END
				ELSIF p # WinApi.GetScrollPos(f.i.ctrl, WinApi.SB_CTL) THEN
					res := WinApi.SetScrollPos(f.i.ctrl, WinApi.SB_CTL, p, 1)
				END
	(*
				IF WinApi.GetScrollPos(f.i.ctrl, WinApi.SBCtl) # pos THEN
					res := WinApi.SetScrollRange(f.i.ctrl, WinApi.SBCtl, 0, size, 1);
					res := WinApi.SetScrollPos(f.i.ctrl, WinApi.SBCtl, pos, 1)
				END
	*)
			ELSE
				IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 0) END
			END
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 0) END
		END;
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: ScrollBar) Restore (l, t, r, b: INTEGER);
		VAR res, w, h: INTEGER; style: SET;
	BEGIN
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			style := {(*1,*) 16, 30};	(* topalign, tabstop, child *)
			f.view.context.GetSize(w, h);
			IF h > w THEN INCL(style, 0) END;	(* vertical *)
			Open(f, "SCROLLBAR", "", style, {}, f.i);
			res := WinApi.SetScrollRange(f.i.ctrl, WinApi.SB_CTL, 0, scrollRange, 1)
		END;
		f.Update;
		IF f.i.wnd # 0 THEN Paint(f, f.i) ELSE Print(f, f.dot, -1, -1, "") END
	END Restore;

	PROCEDURE (f: ScrollBar) MouseDown (x, y: INTEGER; buttons: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons)
		END
	END MouseDown;

	PROCEDURE (f: ScrollBar) KeyDown (ch: CHAR; modifiers: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		SendKey(ch, f.i.ctrl)
	END KeyDown;

	PROCEDURE (f: ScrollBar) Mark (on, focus: BOOLEAN);
	BEGIN
		Mark(on, f.front, f.i)
	END Mark;



	(* Field *)

	PROCEDURE (f: Field) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: Field) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE InsLF (VAR x: ARRAY OF CHAR);
		VAR i, j: INTEGER;
	BEGIN
		i := 0; j := 0;
		WHILE x[i] # 0X DO
			IF x[i] = 0DX THEN INC(j) END;
			INC(i); INC(j)
		END;
		x[j] := 0X;
		WHILE i # j DO
			DEC(i); DEC(j);
			IF x[i] = 0DX THEN x[j] := 0AX; DEC(j) END;
			x[j] := x[i]
		END
	END InsLF;

	PROCEDURE DelLF (VAR x: ARRAY OF CHAR);
		VAR i, j: INTEGER;
	BEGIN
		i := 0; j := 0;
		WHILE x[i] # 0X DO
			IF x[i] = 0AX THEN INC(i) END;
			x[j] := x[i]; INC(i); INC(j)
		END;
		x[j] := 0X
	END DelLF;

	PROCEDURE Equal (f: Field; VAR x, y: ARRAY OF CHAR): BOOLEAN;
	BEGIN
		DelLF(y);
		RETURN f.Equal(f, x, y)
	END Equal;

	PROCEDURE (f: Field) Update;
		VAR res: INTEGER; s, s1: ARRAY 512 OF CHAR; ps, ps1: POINTER TO ARRAY OF CHAR; style: SET;
	BEGIN
		IF f.maxLen > 255 THEN
			NEW(ps, 2 * f.maxLen + 1); NEW(ps1, 2 * f.maxLen + 1);
			IF f.undef OR f.disabled THEN ps[0] := 0X ELSE f.Get(f, ps^) END;
			res := WinApi.GetWindowTextW(f.i.ctrl, ps1^, LEN(ps1^));
			IF (WinApi.GetWindowTextLengthW(f.i.ctrl) >= LEN(ps^)) OR ~Equal(f, ps^, ps1^) THEN
				f.isUpdate := TRUE;
				IF f.multiLine THEN InsLF(ps^) END;
				res := WinApi.SetWindowTextW(f.i.ctrl, ps^);
				f.isUpdate := FALSE
			END
		ELSE
			IF f.undef OR f.disabled THEN s := "" ELSE f.Get(f, s) END;
			res := WinApi.GetWindowTextW(f.i.ctrl, s1, LEN(s1));
			IF (WinApi.GetWindowTextLengthW(f.i.ctrl) >= LEN(s)) OR ~Equal(f, s, s1) THEN
				f.isUpdate := TRUE;
				IF f.multiLine THEN InsLF(s) END;
				res := WinApi.SetWindowTextW(f.i.ctrl, s);
				f.isUpdate := FALSE
			END
		END;
		style := BITS(WinApi.GetWindowLongW(f.i.ctrl, -16));	(* window style *)
		IF (f.readOnly # f.i.readOnly) OR (f.undef # f.i.undef) THEN
(*
			res := WinApi.SetWindowLongW(f.i.ctrl, -16, ORD(style / {11}));
*)
			res := WinApi.InvalidateRect(f.i.ctrl, NIL, 1);
			f.i.readOnly := f.readOnly; f.i.undef := f.undef
		END;
		IF f.disabled THEN
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 0) END
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END
		END;
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: Field) Restore (l, t, r, b: INTEGER);
		VAR res, w, h: INTEGER; style: SET; s: Dialog.String;
	BEGIN
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			f.view.context.GetSize(w, h);
			IF h > dropDownHeight THEN
				style := {2, 6, 16, 23, 30};	(* multiline, autovscroll, tabstop, border, child *)
				IF f.multiLine THEN INCL(style, 21) END	(* ver scroll *)
			ELSE
				style := {7, 16, 23, 30}	(* autohscroll, tabstop, border, child *)
			END;
(*
			IF f.readOnly THEN INCL(style, 11) END;	(* readonly *)
*)
			f.i.readOnly := f.readOnly; f.i.undef := f.undef;
			IF f.right & ~f.left THEN style := style + {1, 2}	(* right align, multiline *)
			ELSIF ~f.left THEN style := style + {0, 2}	(* center, multiline *)
			END;
			IF f.password THEN INCL(style, 5) END;	(* password *)
			Open(f, "EDIT", "", style, WinApi.WS_EX_CLIENTEDGE, f.i);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_LIMITTEXT, f.maxLen, 0);
			f.Update;
			IF f.front & ~f.disabled THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_SETFOCUS, 0, 0);
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYDOWN, 23H, 0)
			END
		ELSE f.Update
		END;
		IF f.i.wnd # 0 THEN
			Paint(f, f.i)
		ELSE
			f.Get(f, s);
			Print(f, f.dot, 2 * Ports.point, 0, s)
		END
	END Restore;

	PROCEDURE (f: Field) MouseDown (x, y: INTEGER; buttons: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			IF ~f.readOnly THEN INCL(buttons, 31) END;
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons)
		END
	END MouseDown;

	PROCEDURE (f: Field) KeyDown (ch: CHAR; modifiers: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.multiLine OR (ch # 0DX) THEN
			f.del := FALSE;
			IF (ch = DEL) OR (ch = BS) THEN f.del := TRUE END;
			SendKey(ch, f.i.ctrl)
		END
	END KeyDown;

	PROCEDURE (f: Field) WheelMove (x, y: INTEGER; op, nofLines: INTEGER; VAR done: BOOLEAN);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			HandleWheel(f.i.ctrl, x, y, op, nofLines, done)
		END
	END WheelMove;

	PROCEDURE (f: Field) Edit (op: INTEGER; VAR v: Views.View; VAR w, h: INTEGER;
										VAR singleton, clipboard: BOOLEAN);
		VAR res: INTEGER;
	BEGIN
		IF clipboard THEN
			IF op = Controllers.cut THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_CUT, 0, 0)
			ELSIF op = Controllers.copy THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_COPY, 0, 0)
			ELSIF op = Controllers.paste THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_PASTE, 0, 0)
			END
		ELSIF op = Controllers.cut THEN
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_CLEAR, 0, 0)
		END
	END Edit;

	PROCEDURE (f: Field) Idle, EMPTY;

	PROCEDURE (f: Field) Select (from, to: INTEGER);
		VAR res: INTEGER;
	BEGIN
		IF to = MAX(INTEGER) THEN to := -1 END;
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_SETSEL, from, to)
	END Select;

	PROCEDURE (f: Field) GetSelection (OUT from, to: INTEGER);
		VAR res: INTEGER;
	BEGIN
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_GETSEL, SYSTEM.ADR(from), SYSTEM.ADR(to));
		IF from = -1 THEN to := -1 ELSIF to = -1 THEN to := MAX(INTEGER) END
	END GetSelection;

	PROCEDURE (f: Field) Mark (on, focus: BOOLEAN);
	BEGIN
		Mark(on, f.front, f.i)
	END Mark;

	PROCEDURE (f: Field) Length (): INTEGER;
		VAR res: INTEGER;
	BEGIN
		res := WinApi.GetWindowTextLengthW(f.i.ctrl);
		RETURN res
	END Length;

	PROCEDURE (f: Field) GetCursor (x, y: INTEGER; modifiers: SET; VAR cursor: INTEGER);
		VAR res, hc: INTEGER; pt: WinApi.POINT;
	BEGIN
		pt.x := (x - 1) DIV f.unit + 1; pt.y := (y - 1) DIV f.unit + 1;
		res := WinApi.ClientToScreen(f.i.wnd, pt);
		hc := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_NCHITTEST, 0, pt.x + pt.y * 65536);
		IF hc = 1 THEN cursor := Ports.textCursor END
	END GetCursor;



	(* UpDownField *)

	PROCEDURE (f: UpDownField) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: UpDownField) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0; f.i.ud := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f: UpDownField) Update;
		VAR res, val: INTEGER; s, s1: ARRAY 16 OF CHAR; style: SET; upd: BOOLEAN;
	BEGIN
		IF ~f.disabled THEN
			IF f.undef THEN upd := TRUE; s := ""
			ELSE f.Get(f, val);
				res := WinApi.GetWindowTextW(f.i.ctrl, s1, LEN(s1));
				Strings.IntToString(val, s);
				upd := (val # f.val) OR (s1 # s)
			END;
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN
				res := WinApi.EnableWindow(f.i.ctrl, 1); upd := TRUE
			END;
			IF f.readOnly THEN
				IF WinApi.IsWindowEnabled(f.i.ud) # 0 THEN res := WinApi.EnableWindow(f.i.ud, 0) END
			ELSE
				IF WinApi.IsWindowEnabled(f.i.ud) = 0 THEN res := WinApi.EnableWindow(f.i.ud, 1) END
			END;
			IF upd THEN
				f.isUpdate := TRUE;
				res := WinApi.SetWindowTextW(f.i.ctrl, s);
				f.val := val; f.isUpdate := FALSE
			END;
			style := BITS(WinApi.GetWindowLongW(f.i.ctrl, -16));	(* window style *)
		IF (f.readOnly # f.i.readOnly) OR (f.undef # f.i.undef) THEN
(*
			res := WinApi.SetWindowLongW(f.i.ctrl, -16, ORD(style / {11}));
*)
			res := WinApi.InvalidateRect(f.i.ctrl, NIL, 1);
			f.i.readOnly := f.readOnly; f.i.undef := f.undef
		END
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN
				f.isUpdate := TRUE;
				res := WinApi.SetWindowTextW(f.i.ctrl, "");
				f.isUpdate := FALSE;
				res := WinApi.EnableWindow(f.i.ctrl, 0)
			END;
			IF WinApi.IsWindowEnabled(f.i.ud) # 0 THEN res := WinApi.EnableWindow(f.i.ud, 0) END
		END;
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: UpDownField) Restore (l, t, r, b: INTEGER);
		VAR res, w, h: INTEGER; style: SET; s: ARRAY 16 OF CHAR;
	BEGIN
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			f.view.context.GetSize(w, h);
			style := {7, 16, 23, 30};	(* autohscroll, tabstop, border, child *)
(*
			IF f.readOnly THEN INCL(style, 11) END;	(* readonly *)
*)
			f.i.readOnly := f.readOnly; f.i.undef := f.undef;
			f.val := 0;
			Open(f, "EDIT", "0", style, WinApi.WS_EX_CLIENTEDGE, f.i);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_LIMITTEXT, 16, 0);
			f.Update;
			IF f.front & ~f.disabled THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_SETFOCUS, 0, 0);
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYDOWN, 23H, 0)
			END
		ELSE f.Update
		END;
		IF f.i.wnd # 0 THEN
			Paint(f, f.i)
		ELSE
			f.Get(f, w);
			Strings.IntToString(w, s);
			Print(f, f.dot, 2 * Ports.point, 0, s)
		END
	END Restore;

	PROCEDURE (f: UpDownField) MouseDown (x, y: INTEGER; buttons: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			IF (HostPorts.right IN buttons) & ~f.readOnly THEN 
				INCL(buttons, 31)
			END;
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons)
		END
	END MouseDown;

	PROCEDURE (f: UpDownField) KeyDown (ch: CHAR; modifiers: SET);
		VAR val: INTEGER;
	BEGIN
		ASSERT(~f.disabled, 100);
		IF (ch = AU) OR (ch = AD) THEN
			val := f.val;
			IF WinApi.GetKeyState(WinApi.VK_CONTROL) < 0 THEN (* Ctrl key pressed *)
				IF ch = AU THEN
					IF ((f.min + 9) DIV 10 <= val) & (val <= f.max DIV 10) THEN val := val * 10 END
				ELSE
					IF val < 0 THEN INC(val, 9) END;
					val := val DIV 10
				END
			ELSE
				IF ch = AU THEN
					IF val <= f.max - f.inc THEN val := val + f.inc ELSE val := f.max END
				ELSE
					IF val >= f.min + f.inc THEN val := val - f.inc ELSE val := f.min END
				END
			END;
			IF val # f.val THEN f.Set(f, val); f.Update END
		ELSIF (ch # 0DX) THEN
			SendKey(ch, f.i.ctrl)
		END
	END KeyDown;

	PROCEDURE (f: UpDownField) WheelMove (x, y: INTEGER; op, nofLines: INTEGER; VAR done: BOOLEAN);
	BEGIN
		WheelToArrow(f, op, done)
	END WheelMove;

	PROCEDURE (f: UpDownField) Edit (op: INTEGER; VAR v: Views.View; VAR w, h: INTEGER;
										VAR singleton, clipboard: BOOLEAN);
		VAR res: INTEGER;
	BEGIN
		IF clipboard THEN
			IF op = Controllers.cut THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_CUT, 0, 0)
			ELSIF op = Controllers.copy THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_COPY, 0, 0)
			ELSIF op = Controllers.paste THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_PASTE, 0, 0)
			END
		ELSIF op = Controllers.cut THEN
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_CLEAR, 0, 0)
		END
	END Edit;

	PROCEDURE (f: UpDownField) Idle, EMPTY;

	PROCEDURE (f: UpDownField) Select (from, to: INTEGER);
		VAR res: INTEGER;
	BEGIN
		IF to = MAX(INTEGER) THEN to := -1 END;
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_SETSEL, from, to)
	END Select;

	PROCEDURE (f: UpDownField) GetSelection (OUT from, to: INTEGER);
		VAR res: INTEGER;
	BEGIN
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_GETSEL, SYSTEM.ADR(from), SYSTEM.ADR(to));
		IF from = -1 THEN to := -1 ELSIF to = -1 THEN to := MAX(INTEGER) END
	END GetSelection;

	PROCEDURE (f: UpDownField) Mark (on, focus: BOOLEAN);
	BEGIN
		Mark(on, f.front, f.i)
	END Mark;

	PROCEDURE (f: UpDownField) GetCursor (x, y: INTEGER; modifiers: SET; VAR cursor: INTEGER);
		VAR res, hc: INTEGER; pt: WinApi.POINT;
	BEGIN
		pt.x := (x - 1) DIV f.unit + 1; pt.y := (y - 1) DIV f.unit + 1;
		res := WinApi.ClientToScreen(f.i.wnd, pt);
		hc := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_NCHITTEST, 0, pt.x + pt.y * 65536);
		IF hc = 1 THEN cursor := Ports.textCursor END
	END GetCursor;



	(* TimeField *)

	PROCEDURE GetTimePart (VAR time: Dates.Time; part: INTEGER; VAR val, min, max: INTEGER);
	BEGIN
		IF part = -1 THEN part := lastPart END;
		IF part = 4 THEN val := time.hour DIV 12; min := 0; max := 1
		ELSIF part = 3 THEN val := time.second; min := 0; max := 59
		ELSIF part = 2 THEN val := time.minute; min := 0; max := 59
		ELSIF lastPart = 3 THEN val := time.hour; min := 0; max := 23
		ELSE val := (time.hour - 1) MOD 12 + 1; min := 1; max := 12
		END
	END GetTimePart;

	PROCEDURE SetTimePart (VAR time: Dates.Time; part: INTEGER; val: INTEGER);
	BEGIN
		IF part = -1 THEN part := lastPart END;
		IF part = 4 THEN time.hour := val * 12 + time.hour MOD 12
		ELSIF part = 3 THEN time.second := val
		ELSIF part = 2 THEN time.minute := val
		ELSIF lastPart = 3 THEN time.hour := val
		ELSE time.hour := time.hour DIV 12 * 12 + val MOD 12
		END
	END SetTimePart;

	PROCEDURE TimeToString (VAR time: Dates.Time; VAR str: ARRAY OF CHAR);
		VAR val, min, max, i, j, k: INTEGER;
	BEGIN
		GetTimePart(time, 1, val, min, max);
		str[0] := CHR(val DIV 10 + ORD("0"));
		str[1] := CHR(val MOD 10 + ORD("0"));
		str[2] := timeSep;
		GetTimePart(time, 2, val, min, max);
		str[3] := CHR(val DIV 10 + ORD("0"));
		str[4] := CHR(val MOD 10 + ORD("0"));
		str[5] := timeSep;
		GetTimePart(time, 3, val, min, max);
		str[6] := CHR(val DIV 10 + ORD("0"));
		str[7] := CHR(val MOD 10 + ORD("0"));
		IF lastPart = 3 THEN
			str[8] := 0X
		ELSE
			str[8] := " ";
			i := 9; j := 0; k := time.hour DIV 12;
			WHILE desig[k, j] # 0X DO str[i] := desig[k, j]; INC(i); INC(j) END;
			str[i] := 0X
		END
	END TimeToString;

	PROCEDURE (f: TimeField) Select, NEW;
		VAR res: INTEGER; sel: INTEGER;
	BEGIN
		f.GetSel(f, sel);
		IF sel = -1 THEN sel := lastPart END;
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_SETSEL, 3 * sel - 3, 3 * sel - 1)
	END Select;

	PROCEDURE (f: TimeField) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: TimeField) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0; f.i.ud := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f: TimeField) Update;
		VAR res: INTEGER; s, s1: ARRAY 20 OF CHAR; time: Dates.Time; style: SET;
	BEGIN
		IF ~f.disabled THEN
			IF f.undef THEN s := "" ELSE f.Get(f, time); TimeToString(time, s) END;
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END;
			IF f.readOnly THEN
				IF WinApi.IsWindowEnabled(f.i.ud) # 0 THEN res := WinApi.EnableWindow(f.i.ud, 0) END
			ELSE
				IF WinApi.IsWindowEnabled(f.i.ud) = 0 THEN res := WinApi.EnableWindow(f.i.ud, 1) END
			END;
			res := WinApi.GetWindowTextW(f.i.ctrl, s1, LEN(s1));
			IF s # s1 THEN
				f.isUpdate := TRUE;
				res := WinApi.SetWindowTextW(f.i.ctrl, s);
				f.isUpdate := FALSE
			END;
			style := BITS(WinApi.GetWindowLongW(f.i.ctrl, -16));	(* window style *)
		IF (f.readOnly # f.i.readOnly) OR (f.undef # f.i.undef) THEN
(*
			res := WinApi.SetWindowLongW(f.i.ctrl, -16, ORD(style / {11}));
*)
			res := WinApi.InvalidateRect(f.i.ctrl, NIL, 1);
			f.i.readOnly := f.readOnly; f.i.undef := f.undef
		END;
			f.Select
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN
				f.isUpdate := TRUE;
				res := WinApi.SetWindowTextW(f.i.ctrl, "");
				f.isUpdate := FALSE;
				res := WinApi.EnableWindow(f.i.ctrl, 0)
			END;
			IF WinApi.IsWindowEnabled(f.i.ud) # 0 THEN res := WinApi.EnableWindow(f.i.ud, 0) END
		END;
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: TimeField) Restore (l, t, r, b: INTEGER);
		VAR res, w, h: INTEGER; style: SET; s: ARRAY 20 OF CHAR; time: Dates.Time;
	BEGIN
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			f.view.context.GetSize(w, h);
			style := {7, 16, 23, 30};	(* autohscroll, tabstop, border, child *)
(*
			IF f.readOnly THEN INCL(style, 11) END;	(* readonly *)
*)
			f.i.readOnly := f.readOnly; f.i.undef := f.undef;
			Open(f, "EDIT", "", style, WinApi.WS_EX_CLIENTEDGE, f.i);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_LIMITTEXT, 16, 0);
			f.Update;
			IF f.front & ~f.disabled THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_SETFOCUS, 0, 0);
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYDOWN, 23H, 0)
			END
		ELSE f.Update
		END;
		IF f.i.wnd # 0 THEN
			Paint(f, f.i)
		ELSE
			f.Get(f, time);
			TimeToString(time, s);
			Print(f, f.dot, 2 * Ports.point, 0, s)
		END
	END Restore;

	PROCEDURE (f: TimeField) MouseDown (x, y: INTEGER; buttons: SET);
		VAR res: INTEGER; sel: INTEGER;
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			f.GetSel(f, sel);
			IF sel = 0 THEN f.SetSel(f, 1); f.Select END;
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_GETSEL, 0, 0);
			sel := res MOD 65536 DIV 3 + 1;
			IF sel >= lastPart THEN sel := -1 END;
			f.cur := 0; f.SetSel(f, sel); f.Select
		END
	END MouseDown;

	PROCEDURE (f: TimeField) KeyDown (ch: CHAR; modifiers: SET);
		VAR sel, s, val, v, min, max: INTEGER; time: Dates.Time;
	BEGIN
		ASSERT(~f.disabled, 100);
		f.GetSel(f, sel); s := sel;
		IF s = -1 THEN s := lastPart END;
		f.Get(f, time);
		GetTimePart(time, s, val, min, max); v := val;
		IF (ch = TAB) OR (ch = AR) THEN
			s := s MOD lastPart + 1; f.cur := 0
		ELSIF (ch = LTAB) OR (ch = AL) THEN
			DEC(s); f.cur := 0;
			IF s <= 0 THEN s := lastPart END
		ELSIF (ch = PL) OR (ch = DL) THEN
			s := 1; f.cur := 0
		ELSIF (ch = PR) OR (ch = DR) THEN
			s := lastPart; f.cur := 0
		ELSIF (ch = DEL) OR (ch = BS) THEN
			v := min
		ELSIF ch = AU THEN
			IF v < max THEN INC(v) ELSE v := min END
		ELSIF ch = AD THEN
			IF v > min THEN DEC(v) ELSE v := max END
		ELSIF (ch >= "0") & (ch <= "9") & (s < 4) THEN
			v := v * 10 MOD 100 + ORD(ch) - ORD("0");
			IF v > max THEN v := v MOD 10 ELSIF v < min THEN v := min END
		ELSIF s = 4 THEN
			IF (ch = " ") OR (CAP(ch) = CAP(desig[1 - v, 0])) THEN v := 1 - v END
		END;
		IF s = lastPart THEN s := -1 END;
		IF v # val THEN
			SetTimePart(time, s, v); f.Set(f, time); f.Update
		ELSIF s # sel THEN
			f.SetSel(f, s); f.Select
		END
	END KeyDown;

	PROCEDURE (f: TimeField) WheelMove (x, y: INTEGER; op, nofLines: INTEGER; VAR done: BOOLEAN);
	BEGIN
		WheelToArrow(f, op, done)
	END WheelMove;

	PROCEDURE (f: TimeField) Edit (op: INTEGER; VAR v: Views.View; VAR w, h: INTEGER;
										VAR singleton, clipboard: BOOLEAN);
		VAR res: INTEGER;
	BEGIN
		IF clipboard THEN
			IF op = Controllers.copy THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_COPY, 0, 0)
			END
		END
	END Edit;

	PROCEDURE (f: TimeField) Mark (on, focus: BOOLEAN);
		VAR res: INTEGER;
	BEGIN
		Mark(on, f.front, f.i);
		IF ~on & focus THEN
			f.SetSel(f, 0); f.cur := 0;
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_SETSEL, 0, 0)
		END
	END Mark;

	PROCEDURE (f: TimeField) GetCursor (x, y: INTEGER; modifiers: SET; VAR cursor: INTEGER);
		VAR res, hc: INTEGER; pt: WinApi.POINT;
	BEGIN
		pt.x := (x - 1) DIV f.unit + 1; pt.y := (y - 1) DIV f.unit + 1;
		res := WinApi.ClientToScreen(f.i.wnd, pt);
		hc := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_NCHITTEST, 0, pt.x + pt.y * 65536);
		IF hc = 1 THEN cursor := Ports.textCursor END
	END GetCursor;



	(* DateField *)

	PROCEDURE GetDatePart (VAR date: Dates.Date; part: INTEGER; OUT val, min, max: INTEGER);
	(* GetDatePart picks the day, month or year part out of a given date and asigns it to the out
		parameter val, together with the min and max possible values for this part
	*)
	BEGIN
		IF part = -1 THEN part := 3 END;
		IF part = yearPart THEN val := date.year; min := 1; max := 9999
		ELSIF part = monthPart THEN val := date.month; min := 1; max := 12
		ELSE
			val := date.day; min := 1;
			IF date.month = 0 THEN
				max := 31
			ELSIF date.month = 2 THEN
				IF (date.year MOD 4 = 0)
					& ((date.year < 1583) OR (date.year MOD 100 # 0) OR (date.year MOD 400 = 0))
				THEN
					max := 29
				ELSE max := 28
				END
			ELSIF date.month IN {1, 3, 5, 7, 8, 10, 12} THEN max := 31
			ELSE max := 30
			END
		END
	END GetDatePart;

	PROCEDURE SetDatePart (VAR date: Dates.Date; part: INTEGER; val: INTEGER);
	(* SetDatePart sets the day, month or year part in a given date to the value specivied
		by the parameter val.
		If the month is set, the day is adjusted to the possible range of days in this month.
		If the day is set, then the month may be changed in order to obtain a valid date
	*)
		VAR v, min, max: INTEGER;
	BEGIN
		IF part = -1 THEN part := 3 END;
		IF part = yearPart THEN date.year := val
		ELSIF part = monthPart THEN date.month := val
		ELSE date.day := val
		END;
		GetDatePart(date, dayPart, v, min, max);
		IF (part = monthPart) THEN (* adjust day if month value is set and day > max *)
			IF v > max THEN date.day := max END
		ELSIF part = yearPart THEN (* adjust month is day value is set and day > max *)
			IF v > max THEN INC(date.month) END
		END
	END SetDatePart;

	PROCEDURE DateToString (VAR date: Dates.Date; VAR str: ARRAY OF CHAR);
		VAR val, min, max, p, i: INTEGER;
	BEGIN
		p := 1; i := 0;
		WHILE p <= 3 DO
			IF p > 1 THEN str[i] := dateSep; INC(i) END;
			GetDatePart(date, p, val, min, max);
			IF max = 9999 THEN
				str[i] := CHR(val DIV 1000 MOD 10 + ORD("0")); INC(i);
				str[i] := CHR(val DIV 100 MOD 10 + ORD("0")); INC(i)
			END;
			str[i] := CHR(val DIV 10 MOD 10 + ORD("0")); INC(i);
			str[i] := CHR(val MOD 10 + ORD("0")); INC(i);
			INC(p)
		END;
		str[i] := 0X
	END DateToString;

	PROCEDURE (f: DateField) Select, NEW;
		VAR res: INTEGER; sel, a, b: INTEGER;
	BEGIN
		f.GetSel(f, sel);
		IF sel = 1 THEN a := 0; b := del1
		ELSIF sel = 2 THEN a := del1 + 1; b := del2
		ELSE a := del2 + 1; b := del2 + 5
		END;
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_SETSEL, a, b)
	END Select;

	PROCEDURE (f: DateField) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: DateField) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0; f.i.ud := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f: DateField) Update;
		VAR res: INTEGER; s, s1: ARRAY 20 OF CHAR; date: Dates.Date; style: SET; sel: INTEGER;
	BEGIN
		IF ~f.disabled THEN
			IF f.undef THEN s := ""
			ELSE f.Get(f, date);
				IF f.cnt > 0 THEN f.GetSel(f, sel); SetDatePart(date, sel, f.val) END;
				DateToString(date, s)
			END;
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END;
			IF f.readOnly THEN
				IF WinApi.IsWindowEnabled(f.i.ud) # 0 THEN res := WinApi.EnableWindow(f.i.ud, 0) END
			ELSE
				IF WinApi.IsWindowEnabled(f.i.ud) = 0 THEN res := WinApi.EnableWindow(f.i.ud, 1) END
			END;
			res := WinApi.GetWindowTextW(f.i.ctrl, s1, LEN(s1));
			IF s # s1 THEN
				f.isUpdate := TRUE;
				res := WinApi.SetWindowTextW(f.i.ctrl, s);
				f.isUpdate := FALSE
			END;
			style := BITS(WinApi.GetWindowLongW(f.i.ctrl, -16));	(* window style *)
			IF (f.readOnly # f.i.readOnly) OR (f.undef # f.i.undef) THEN
	(*
				res := WinApi.SetWindowLongW(f.i.ctrl, -16, ORD(style / {11}));
	*)
				res := WinApi.InvalidateRect(f.i.ctrl, NIL, 1);
				f.i.readOnly := f.readOnly; f.i.undef := f.undef
			END;
			f.Select
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN
				f.isUpdate := TRUE;
				res := WinApi.SetWindowTextW(f.i.ctrl, "");
				f.isUpdate := FALSE;
				res := WinApi.EnableWindow(f.i.ctrl, 0)
			END;
			IF WinApi.IsWindowEnabled(f.i.ud) # 0 THEN res := WinApi.EnableWindow(f.i.ud, 0) END
		END;
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: DateField) Restore (l, t, r, b: INTEGER);
		VAR res, w, h: INTEGER; style: SET; s: ARRAY 20 OF CHAR; date: Dates.Date;
	BEGIN
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			f.view.context.GetSize(w, h);
			style := {7, 16, 23, 30};	(* autohscroll, tabstop, border, child *)
(*
			IF f.readOnly THEN INCL(style, 11) END;	(* readonly *)
*)
			f.i.readOnly := f.readOnly; f.i.undef := f.undef;
			Open(f, "EDIT", "", style, WinApi.WS_EX_CLIENTEDGE, f.i);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_LIMITTEXT, 16, 0);
			f.Update;
			IF f.front & ~f.disabled THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_SETFOCUS, 0, 0);
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYDOWN, 23H, 0)
			END
		ELSE f.Update
		END;
		IF f.i.wnd # 0 THEN
			Paint(f, f.i)
		ELSE
			f.Get(f, date);
			DateToString(date, s);
			Print(f, f.dot, 2 * Ports.point, 0, s)
		END
	END Restore;

	PROCEDURE ActualizeDate(f: DateField);
		VAR sel: INTEGER; date: Dates.Date; val, min, max: INTEGER;
	BEGIN
		IF f.cnt > 0 THEN
			f.GetSel(f, sel); IF sel = -1 THEN sel := 3 END;
			f.Get(f, date);
			IF (sel = yearPart) & (f.cnt <= 2) THEN
				IF f.val < 50 THEN f.val := f.val + 2000
				ELSIF f.val < 100 THEN f.val := f.val + 1900
				END
			END;
			GetDatePart(date, sel, val, min, max);
			IF (min <= f.val) & (f.val <= max) THEN
				SetDatePart(date, sel, f.val); f.Set(f, date)
			END;
			f.cnt := 0;
			f.Update
		END
	END ActualizeDate;

	PROCEDURE (f: DateField) MouseDown (x, y: INTEGER; buttons: SET);
		VAR res: INTEGER; sel: INTEGER;
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			ActualizeDate(f);
			f.GetSel(f, sel);
			IF sel = 0 THEN f.SetSel(f, 1); f.Select END;
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_GETSEL, 0, 0);
			res := res MOD 65536;
			IF res <= del1 THEN sel := 1
			ELSIF res <= del2 THEN sel := 2
			ELSE sel := -1
			END;
			f.SetSel(f, sel); f.Select
		END
	END MouseDown;

	PROCEDURE (f: DateField) KeyDown (ch: CHAR; modifiers: SET);
		VAR sel, s, val, min, max: INTEGER; date: Dates.Date;
	BEGIN
		ASSERT(~f.disabled, 100);
		f.GetSel(f, sel); s := sel;
		IF s = -1 THEN s := 3 END;
		f.Get(f, date);
		GetDatePart(date, s, val, min, max);

		IF (ch = TAB) OR (ch = AR) THEN
			s := s MOD 3 + 1;
			ActualizeDate(f)
		ELSIF ((ch = ".") OR (ch = ",")) & (f.cnt > 0) THEN
			s := s MOD 3 + 1;
			ActualizeDate(f)
		ELSIF (ch = LTAB) OR (ch = AL) THEN
			IF s = 0 THEN s := 1 END;
			s := (s - 2) MOD 3 + 1;
			ActualizeDate(f)
		ELSIF (ch = PL) OR (ch = DL) THEN
			s := 1;
			ActualizeDate(f)
		ELSIF (ch = PR) OR (ch = DR) THEN
			s := 3;
			ActualizeDate(f)
		ELSIF (ch = DEL) OR (ch = BS) THEN
			f.cnt := 4; f.val := min;
			ActualizeDate(f)
		ELSIF ch = AU THEN
			IF f.cnt = 0 THEN f.cnt := 4; f.val := val END;
			IF f.val < max THEN INC(f.val) ELSE f.val := min END;
			ActualizeDate(f)
		ELSIF ch = AD THEN
			IF f.cnt = 0 THEN f.cnt := 4; f.val := val END;
			IF f.val > min THEN DEC(f.val) ELSE f.val := max END;
			ActualizeDate(f)
		ELSIF (ch >= "0") & (ch <= "9") THEN
			IF s = yearPart THEN
				IF f.cnt = 0 THEN
					f.val := ORD(ch) - ORD("0"); INC(f.cnt);
					f.Update
				ELSE
					f.val := f.val * 10 + ORD(ch) - ORD("0");
					INC(f.cnt);
					IF f.cnt = 4 THEN ActualizeDate(f) ELSE f.Update END
				END
			ELSE
				IF f.cnt = 0 THEN
					f.val := ORD(ch) - ORD("0"); f.cnt := 1;
					f.Update
				ELSE
					f.val := f.val * 10 MOD 100 + ORD(ch) - ORD("0");
					IF (s = dayPart) & (max < f.val) & (f.val <= 31) THEN
						INC(date.month); f.Set(f, date); max := 31
					END;
					IF f.val > max THEN f.val := f.val MOD 10 END;
					ActualizeDate(f);
					INC(s)
				END
			END
		END;

		IF s = 3 THEN s := -1 END;
		IF s # sel THEN
			f.SetSel(f, s); f.Select
		END
	END KeyDown;

	PROCEDURE (f: DateField) WheelMove (x, y: INTEGER; op, nofLines: INTEGER; VAR done: BOOLEAN);
	BEGIN
		WheelToArrow(f, op, done)
	END WheelMove;

	PROCEDURE (f: DateField) Edit (op: INTEGER; VAR v: Views.View; VAR w, h: INTEGER;
										VAR singleton, clipboard: BOOLEAN);
		VAR res: INTEGER;
	BEGIN
		IF clipboard THEN
			IF op = Controllers.copy THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_COPY, 0, 0)
			END
		END
	END Edit;

	PROCEDURE (f: DateField) Mark (on, focus: BOOLEAN);
		VAR res: INTEGER;
	BEGIN
		IF ~on THEN ActualizeDate(f) END;

		Mark(on, f.front, f.i);
		IF ~on & focus THEN
			f.SetSel(f, 0);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.EM_SETSEL, 0, 0)
		END
	END Mark;

	PROCEDURE (f: DateField) GetCursor (x, y: INTEGER; modifiers: SET; VAR cursor: INTEGER);
		VAR res, hc: INTEGER; pt: WinApi.POINT;
	BEGIN
		pt.x := (x - 1) DIV f.unit + 1; pt.y := (y - 1) DIV f.unit + 1;
		res := WinApi.ClientToScreen(f.i.wnd, pt);
		hc := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_NCHITTEST, 0, pt.x + pt.y * 65536);
		IF hc = 1 THEN cursor := Ports.textCursor END
	END GetCursor;



	(* ColorField *)

	PROCEDURE (f: ColorField) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: ColorField) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f: ColorField) Update;
		VAR res, i, j: INTEGER; c: Ports.Color;
	BEGIN
		IF ~f.disabled THEN
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END;
			IF f.undef THEN j := -1
			ELSE
				f.Get(f, c); j := 0;
				WHILE (j < numColors) & (c # colors[j]) DO INC(j) END
			END;
			i := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_GETCURSEL, 0, 0);
			IF (i # j) OR (j = numColors) & (c # f.color) THEN
				f.color := c;
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SETCURSEL, j, 0)
			END
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SETCURSEL, -1, 0);
				res := WinApi.EnableWindow(f.i.ctrl, 0)
			END
		END;
		IF f.readOnly # f.i.readOnly THEN
			res := WinApi.InvalidateRect(f.i.ctrl, NIL, 1);
			f.i.readOnly := f.readOnly
		END;
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: ColorField) Restore (l, t, r, b: INTEGER);
		VAR res, w, h, i: INTEGER; style: SET;
	BEGIN
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			f.view.context.GetSize(w, h);
			f.i.dropDown := TRUE; f.i.readOnly := f.readOnly;
			(* drop down list, auto scroll, ownerdrawn, tabstop, ver scroll, border, child *)
			style := {0, 1, 4, 6, 16, 21, 23, 30};
			Open(f, "COMBOBOX", "", style, WinApi.WS_EX_CLIENTEDGE, f.i); i := 0;
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SETEXTENDEDUI, 1, 0);
			WHILE i < numColors DO
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_ADDSTRING, 0, colors[i]);
				INC(i)
			END;
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_ADDSTRING, 0, -1);
			Adapt(f, f.i)
		END;
		f.Update;
		IF f.i.wnd # 0 THEN Paint(f, f.i) ELSE Print(f, f.dot, -1, -1, "") END
	END Restore;

	PROCEDURE (f: ColorField) MouseDown (x, y: INTEGER; buttons: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons)
		END
	END MouseDown;

	PROCEDURE (f: ColorField) KeyDown (ch: CHAR; modifiers: SET);
		VAR res: INTEGER;
	BEGIN
		ASSERT(~f.disabled, 100);
		IF ch = " " THEN
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYDOWN, ORD(ch), 0);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYUP, ORD(ch), 0)
		ELSE SendKey(ch, f.i.ctrl)
		END
	END KeyDown;

	PROCEDURE (f: ColorField) Mark (on, focus: BOOLEAN);
	BEGIN
		Mark(on, f.front, f.i)
	END Mark;



	(* ListBox *)

	PROCEDURE (f: ListBox) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: ListBox) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f: ListBox) Update;
		VAR res, i, j: INTEGER;
	BEGIN
		IF ~f.disabled THEN
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END;
			IF f.undef THEN i := -1 ELSE f.Get(f, i) END; j := i;
			IF f.i.dropDown THEN
				IF (i < 0) OR (i >= WinApi.SendMessageW(f.i.ctrl, WinApi.CB_GETCOUNT, 0, 0)) THEN j := -1
				ELSIF f.sorted THEN
					j := 0;
					WHILE i # WinApi.SendMessageW(f.i.ctrl, WinApi.CB_GETITEMDATA, j, 0) DO INC(j) END
				END;
				i := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_GETCURSEL, j, 0);
				IF i # j THEN res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SETCURSEL, j, 0) END
			ELSE
				IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END;
				IF (i < 0) OR (i >= WinApi.SendMessageW(f.i.ctrl, WinApi.LB_GETCOUNT, 0, 0)) THEN j := -1
				ELSIF f.sorted THEN
					j := 0;
					WHILE i # WinApi.SendMessageW(f.i.ctrl, WinApi.LB_GETITEMDATA, j, 0) DO INC(j) END
				END;
				i := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_GETCURSEL, j, 0);
				IF i # j THEN res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_SETCURSEL, j, 0) END
			END
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN
				IF f.i.dropDown THEN
					res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SETCURSEL, -1, 0)
				ELSE
					res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_SETCURSEL, -1, 0)
				END;
				res := WinApi.EnableWindow(f.i.ctrl, 0)
			END
		END;
		IF f.readOnly # f.i.readOnly THEN
			res := WinApi.InvalidateRect(f.i.ctrl, NIL, 1);
			f.i.readOnly := f.readOnly
		END;
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: ListBox) UpdateList;
		VAR res, i, w, maxW: INTEGER; s: Dialog.String;
	BEGIN
		IF f.i.dropDown THEN
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_RESETCONTENT, 0, 0);
			i := 0; f.GetName(f, i, s);
			Dialog.MapString(s, s);
			WHILE s # "" DO
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_ADDSTRING, 0, SYSTEM.ADR(s));
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SETITEMDATA, res, i);
				INC(i); f.GetName(f, i, s); Dialog.MapString(s, s);
			END;
			Adapt(f, f.i)
		ELSE
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_RESETCONTENT, 0, 0);
			i := 0; f.GetName(f, i, s);
			Dialog.MapString(s, s);
			maxW := f.font.StringWidth(s);
			WHILE s # "" DO
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_ADDSTRING, 0, SYSTEM.ADR(s));
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_SETITEMDATA, res, i);
				INC(i); f.GetName(f, i, s); Dialog.MapString(s, s);
				w := f.font.StringWidth(s);
				IF w > maxW THEN maxW := w END
			END
		END;
		maxW := (maxW + Ports.point * 3) DIV f.unit;
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_SETHORIZONTALEXTENT, maxW, 0);
		f.Update
	END UpdateList;

	PROCEDURE (f: ListBox) Restore (l, t, r, b: INTEGER);
		VAR i, res, w, h: INTEGER; style: SET; s: ARRAY 512 OF CHAR;
	BEGIN
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			f.view.context.GetSize(w, h);
			IF h > dropDownHeight THEN
				f.i.dropDown := FALSE; f.i.readOnly := f.readOnly;
				style := {0, 16, 20, 21, 23, 30};	(* notify, tabstop, hor scroll, ver scroll, border, child *)
				IF f.sorted THEN INCL(style, 1) END;	(* sort *)
				Open(f, "LISTBOX", "", style, WinApi.WS_EX_CLIENTEDGE, f.i)
			ELSE
				f.i.dropDown := TRUE;
				style := {0, 1, 6, 16, 21, 23, 30};	(* drop down list, auto scroll, tabstop, ver scroll, border, child *)
				IF f.sorted THEN INCL(style, 8) END;	(* sort *)
				Open(f, "COMBOBOX", "", style, WinApi.WS_EX_CLIENTEDGE, f.i);
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SETEXTENDEDUI, WinApi.TRUE, 0)
			END;
			f.UpdateList
		ELSE
			f.Update
		END;
		IF f.i.wnd # 0 THEN Paint(f, f.i) 
		ELSE
			f.Get(f, i); f.GetName(f, i, s);
			Print(f, f.dot, 2 * Ports.point, 0, s)
		END
	END Restore;


	PROCEDURE (f: ListBox) DblClickOk (x, y: INTEGER): BOOLEAN;
		VAR res, i, j, msg: INTEGER; rect: WinApi.RECT;
	BEGIN
		f.Get(f, i); j := i;
		IF f.sorted THEN
			IF f.i.dropDown THEN msg := WinApi.CB_GETITEMDATA ELSE msg := WinApi.LB_GETITEMDATA END;
			j := 0;
			WHILE i # WinApi.SendMessageW(f.i.ctrl, msg, j, 0) DO INC(j) END
		END;		
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_GETITEMRECT, j, SYSTEM.ADR(rect));
		IF res = WinApi.LB_ERR THEN RETURN FALSE END;
		x := x DIV f.unit; y := y DIV f.unit;
		RETURN (x >= rect.left) & (x <= rect.right) & (y >= rect.top) & (y <= rect.bottom)
	END DblClickOk;

	PROCEDURE (f: ListBox) MouseDown (x, y: INTEGER; buttons: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons)
		END
	END MouseDown;

	PROCEDURE (f: ListBox) WheelMove (x, y: INTEGER; op, nofLines: INTEGER; VAR done: BOOLEAN);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			HandleWheel(f.i.ctrl, x DIV f.unit, y DIV f.unit, op, nofLines, done)
		END
	END WheelMove;

	PROCEDURE (f: ListBox) KeyDown (ch: CHAR; modifiers: SET);
		VAR res: INTEGER;
	BEGIN
		ASSERT(~f.disabled, 100);
		IF ch = " " THEN
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYDOWN, ORD(ch), 0);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYUP, ORD(ch), 0)
		ELSE
			SendKey(ch, f.i.ctrl)
		END
	END KeyDown;

	PROCEDURE (f: ListBox) Mark (on, focus: BOOLEAN);
	BEGIN
		Mark(on, f.front, f.i)
	END Mark;



	(* SelectionBox *)

	PROCEDURE (f: SelectionBox) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: SelectionBox) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f: SelectionBox) DblClickOk (x, y: INTEGER): BOOLEAN;
		VAR res, i: INTEGER; s: Dialog.String;
		sel: BOOLEAN; rect: WinApi.RECT;
	BEGIN
		i := 0; f.GetName(f, i, s);
		x := x DIV f.unit; y := y DIV f.unit;
		WHILE s # "" DO
			f.Get(f, i, sel);
			IF sel THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_GETITEMRECT, i, SYSTEM.ADR(rect));
				IF res = WinApi.LB_ERR THEN RETURN FALSE END;
				IF (x >= rect.left) & (x <= rect.right) & (y >= rect.top) & (y <= rect.bottom) THEN RETURN TRUE END
			END;
			INC(i); f.GetName(f, i, s); Dialog.MapString(s, s);
		END;
		RETURN FALSE
	END DblClickOk;

	PROCEDURE (f: SelectionBox) Update;
		VAR res, i, j, a, b: INTEGER; sel: BOOLEAN;
	BEGIN
		IF f.delayUpdate THEN f.needsUpdate := TRUE; RETURN END;
		IF ~f.disabled THEN
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END;
			i := 0;
			WHILE i < f.num DO j := i;
				IF f.sorted THEN
					j := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_GETITEMDATA, i, 0)
				END;
				f.Get(f, j, sel);
				IF sel & ~f.undef THEN a := 1 ELSE a := 0 END;
				b := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_GETSEL, i, 0);
				IF a # b THEN
					res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_SETSEL, a, i) END;
				INC(i)
			END
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_SETSEL, 0, -1);
				res := WinApi.EnableWindow(f.i.ctrl, 0)
			END
		END;
		IF f.readOnly # f.i.readOnly THEN
			res := WinApi.InvalidateRect(f.i.ctrl, NIL, 1);
			f.i.readOnly := f.readOnly
		END;
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: SelectionBox) UpdateRange (op, from, to: INTEGER);
		VAR res, i, a, b: INTEGER; sel: BOOLEAN;
	BEGIN
		ASSERT((from >= 0) & (from <= to) & (to < f.num), 100);
		IF (op = Dialog.set) OR (from # to) THEN f.Update; RETURN END;
		IF ~f.disabled THEN
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END;
			i := from;
			IF f.sorted THEN
				i := 0;
				WHILE WinApi.SendMessageW(f.i.ctrl, WinApi.LB_GETITEMDATA, i, 0) # from DO INC(i) END
			END;
			f.Get(f, from, sel);
			IF sel THEN a := 1 ELSE a := 0 END;
			b := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_GETSEL, i, 0);
			IF a # b THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_SETSEL, a, i) END
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 0) END
		END
	END UpdateRange;

	PROCEDURE (f: SelectionBox) UpdateList;
		VAR res, i, w, maxW: INTEGER; s: Dialog.String;
	BEGIN
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_RESETCONTENT, 0, 0);
		i := 0;
		f.GetName(f, i, s); Dialog.MapString(s, s);
		maxW := f.font.StringWidth(s);
		WHILE s # "" DO
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_ADDSTRING, 0, SYSTEM.ADR(s));
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_SETITEMDATA, res, i);
			INC(i); f.GetName(f, i, s);
			Dialog.MapString(s, s);
			w := f.font.StringWidth(s);
			IF w > maxW THEN maxW := w END
		END;
		maxW := (maxW + Ports.point * 3) DIV f.unit;
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.LB_SETHORIZONTALEXTENT, maxW, 0);
		f.num := i;
		f.Update
	END UpdateList;

	PROCEDURE (f: SelectionBox) Restore (l, t, r, b: INTEGER);
		VAR style: SET;
	BEGIN
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			f.i.readOnly := f.readOnly;
			style := {0, 3, 16, 20, 21, 23, 30} + WinApi.LBS_EXTENDEDSEL;
				(* notify, multiple sel, tabstop, hor scroll, ver scroll, border, child *)
			IF f.sorted THEN INCL(style, 1) END;	(* sort *)
			Open(f, "LISTBOX", "", style, WinApi.WS_EX_CLIENTEDGE, f.i);
			f.UpdateList
		ELSE
			f.Update
		END;
		IF f.i.wnd # 0 THEN Paint(f, f.i) ELSE Print(f, f.dot, -1, -1, "") END
	END Restore;

	PROCEDURE (f: SelectionBox) MouseDown (x, y: INTEGER; buttons: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons)
		END
	END MouseDown;

	PROCEDURE (f: SelectionBox) WheelMove (x, y: INTEGER; op, nofLines: INTEGER; VAR done: BOOLEAN);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			HandleWheel(f.i.ctrl, x, y, op, nofLines, done)
		END
	END WheelMove;

	PROCEDURE (f: SelectionBox) KeyDown (ch: CHAR; modifiers: SET);
		VAR res: INTEGER;
	BEGIN
		ASSERT(~f.disabled, 100);
		IF ch = " " THEN
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYDOWN, ORD(ch), 0);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYUP, ORD(ch), 0)
		ELSE
			SendKey(ch, f.i.ctrl)
		END
	END KeyDown;

	PROCEDURE (f: SelectionBox) Select (from, to: INTEGER), EMPTY;

	PROCEDURE (f: SelectionBox) GetSelection (OUT from, to: INTEGER);
	BEGIN
		from := 0; to := MAX(INTEGER)
	END GetSelection;

	PROCEDURE (f: SelectionBox) Mark (on, focus: BOOLEAN);
	BEGIN
		Mark(on, f.front, f.i)
	END Mark;



	(* ComboBox *)

	PROCEDURE (f: ComboBox) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: ComboBox) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f: ComboBox) Update;
		VAR res: INTEGER; s, s1: Dialog.String;
	BEGIN
		IF ~f.disabled THEN
			IF f.undef THEN s := "" ELSE f.Get(f, s) END;
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END;
			res := WinApi.GetWindowTextW(f.i.ctrl, s1, LEN(s1));
			IF (WinApi.GetWindowTextLengthW(f.i.ctrl) >= LEN(s)) OR (s # s1) THEN
				IF ~f.i.dropDown THEN
					res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_FINDSTRINGEXACT, -1, SYSTEM.ADR(s));
					res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SETCURSEL, res, 0)
				END;
				res := WinApi.SetWindowTextW(f.i.ctrl, s)
			END
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SETCURSEL, -1, 0);
				res := WinApi.SetWindowTextW(f.i.ctrl, "");
				res := WinApi.EnableWindow(f.i.ctrl, 0)
			END
		END;
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: ComboBox) UpdateList;
		VAR res, i: INTEGER; s: Dialog.String;
	BEGIN
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_RESETCONTENT, 0, 0);
		i := 0; f.GetName(f, i, s); Dialog.MapString(s, s);
		WHILE s # "" DO
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_ADDSTRING, 0, SYSTEM.ADR(s));
			INC(i); f.GetName(f, i, s); Dialog.MapString(s, s)
		END;
		Adapt(f, f.i);
		f.Update
	END UpdateList;

	PROCEDURE (f: ComboBox) Restore (l, t, r, b: INTEGER);
		VAR res, w, h: INTEGER; s: Dialog.String;
			style: SET; pt: WinApi.POINT;
	BEGIN
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			f.Get(f, s); f.view.context.GetSize(w, h);
			style := {6, 16, 21, 23, 30};	(* auto scroll, tabstop, ver scroll, border, child *)
			IF f.sorted THEN INCL(style, 8) END;	(* sort *)
			f.i.dropDown := h <= dropDownHeight;
			IF f.i.dropDown THEN INCL(style, 1) ELSE INCL(style, 0) END;	(* dropdown / simple *)
			Open(f, "COMBOBOX", s, style, WinApi.WS_EX_CLIENTEDGE, f.i);
			pt.x := 4; pt.y := 4;
			f.edit := WinApi.ChildWindowFromPoint(f.i.ctrl, pt);	(* hack found in win api manual ! *)
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SETEXTENDEDUI, 1, 0);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_LIMITTEXT, 255, 0);
			f.UpdateList
		ELSE
			f.Update
		END;
		IF f.i.wnd # 0 THEN Paint(f, f.i) ELSE Print(f, f.dot, -1, -1, "") END
	END Restore;

	PROCEDURE (f: ComboBox) MouseDown (x, y: INTEGER; buttons: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			IF ~f.readOnly THEN INCL(buttons, 31) END;
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons)
		END
	END MouseDown;

	PROCEDURE (f: ComboBox) KeyDown (ch: CHAR; modifiers: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		SendKey(ch, f.edit)
	END KeyDown;

	PROCEDURE (f: ComboBox) WheelMove (x, y: INTEGER; op, nofLines: INTEGER; VAR done: BOOLEAN);
	VAR list: WinApi.HANDLE; res: INTEGER;
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			list := WinApi.GetWindow(f.edit, WinApi.GW_HWNDPREV);
			IF list = 0 THEN
				IF op IN {Controllers.incLine, Controllers.incPage} THEN
					res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SHOWDROPDOWN, WinApi.TRUE, 0);
					res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_SETCURSOR, f.i.ctrl, 
						WinApi.HTBORDER + WinApi.WM_MOUSEMOVE * 65536)
				ELSE
					res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SHOWDROPDOWN, WinApi.FALSE, 0)
				END;
				done := TRUE
			ELSE
				HandleWheel(list, x DIV f.unit, y DIV f.unit, op, nofLines, done)
			END;
		END
	END WheelMove;

	PROCEDURE (f: ComboBox) Edit (op: INTEGER;
		VAR v: Views.View; VAR w, h: INTEGER; VAR singleton, clipboard: BOOLEAN
	);
		VAR res: INTEGER;
	BEGIN
		IF clipboard THEN
			IF op = Controllers.cut THEN
				res := WinApi.SendMessageW(f.edit, WinApi.WM_CUT, 0, 0)
			ELSIF op = Controllers.copy THEN
				res := WinApi.SendMessageW(f.edit, WinApi.WM_COPY, 0, 0)
			ELSIF op = Controllers.paste THEN
				res := WinApi.SendMessageW(f.edit, WinApi.WM_PASTE, 0, 0)
			END
		ELSIF op = Controllers.cut THEN
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_CLEAR, 0, 0)
		END
	END Edit;

	PROCEDURE (f: ComboBox) Idle, EMPTY;

	PROCEDURE (f: ComboBox) Select (from, to: INTEGER);
		VAR res: INTEGER;
	BEGIN
		IF to > 32767 THEN to := -1 END;
		IF from < 0 THEN res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SETEDITSEL, 0, 65535)
		ELSE res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_SETEDITSEL, 0, from MOD 65536 + to * 65536)
		END
	END Select;

	PROCEDURE (f: ComboBox) GetSelection (OUT from, to: INTEGER);
		VAR res: INTEGER;
	BEGIN
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.CB_GETEDITSEL, SYSTEM.ADR(from), SYSTEM.ADR(to));
		IF from = -1 THEN to := -1 ELSIF to = -1 THEN to := MAX(INTEGER) END
	END GetSelection;

	PROCEDURE (f: ComboBox) Mark (on, focus: BOOLEAN);
		VAR res: INTEGER;
	BEGIN
		IF f.front THEN
			IF on THEN
				IF ~f.i.hasFocus THEN
					res := WinApi.SendMessageW(f.edit, WinApi.WM_SETFOCUS, 0, 0);
					f.i.hasFocus := TRUE
				END
			ELSE
				IF f.i.hasFocus THEN
					res := WinApi.SendMessageW(f.edit, WinApi.WM_KILLFOCUS, 0, 0);
					f.i.hasFocus := FALSE
				END
			END
		END
	END Mark;

	PROCEDURE (f: ComboBox) Length (): INTEGER;
		VAR res: INTEGER;
	BEGIN
		res := WinApi.GetWindowTextLengthW(f.i.ctrl);
		RETURN res
	END Length;

	PROCEDURE (f: ComboBox) GetCursor (x, y: INTEGER; modifiers: SET; VAR cursor: INTEGER);
		VAR res, hc: INTEGER; pt: WinApi.POINT;
	BEGIN
		pt.x := (x - 1) DIV f.unit + 1; pt.y := (y - 1) DIV f.unit + 1;
		res := WinApi.ClientToScreen(f.i.wnd, pt);
		hc := WinApi.SendMessageW(f.edit, WinApi.WM_NCHITTEST, 0, pt.x + pt.y * 65536);
		IF hc = 1 THEN cursor := Ports.textCursor END
(*
		u := f.unit; pt.x := x DIV u - 1; pt.y := y DIV u - 1;
		wnd := WinApi.ChildWindowFromPoint(f.i.ctrl, pt);
		IF wnd = f.edit THEN cursor := Ports.textCursor
		ELSE cursor := Ports.arrowCursor
		END

		u := f.unit; pt.x := x DIV u; pt.y := y DIV u;
		res := WinApi.ClientToScreen(f.i.wnd, pt);
		ChildWindowAt(f.i.wnd, pt, wnd, hc);
		res := WinApi.ScreenToClient(wnd, pt);
		res := WinApi.SendMessageW(wnd, WinApi.WMSetCursor, wnd, hc + WinApi.WMMouseMove * 65536);
		cursor := -1
*)
	END GetCursor;



	(* Caption *)

	PROCEDURE (f: Caption) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: Caption) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f:Caption ) Update;
		VAR res: INTEGER;
	BEGIN
		IF ~f.disabled THEN
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 0) END
		END;
		CheckLabel(f.label, f.i.ctrl);
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: Caption) Restore (l, t, r, b: INTEGER);
		VAR lbl: Dialog.String; style: SET;
	BEGIN
		SetLabel(f.label, lbl);
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			style := {30};
			IF f.left THEN style := style + WinApi.SS_LEFT
			ELSIF f.right THEN style := style + WinApi.SS_RIGHT
			ELSE style := style + WinApi.SS_CENTER
			END;
			Open(f, "STATIC", lbl, style, {}, f.i)	(* left, child *)
		END;
		f.Update;
		IF f.i.wnd # 0 THEN Paint(f, f.i) ELSE Print(f, 0, 2 * Ports.point, 0, lbl) END
	END Restore;



	(* Group *)

	PROCEDURE (f: Group) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: Group) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0
		END
		(*f.Close^*)
	END Close;

	PROCEDURE (f: Group) Update;
		VAR res: INTEGER;
	BEGIN
		IF ~f.disabled THEN
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 1) END
		ELSE
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN res := WinApi.EnableWindow(f.i.ctrl, 0) END
		END;
		CheckLabel(f.label, f.i.ctrl);
		res := WinApi.UpdateWindow(f.i.ctrl)
	END Update;

	PROCEDURE (f: Group) Restore (l, t, r, b: INTEGER);
		VAR lbl: Dialog.String;
	BEGIN
		SetLabel(f.label, lbl);
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			Open(f, "BUTTON", lbl, {0, 1, 2, 30}, {}, f.i)	(* group box, child *)
		END;
		f.Update;
		IF f.i.wnd # 0 THEN Paint(f, f.i) ELSE Print(f, -3, 2 * Ports.point, 0, lbl) END
	END Restore;


	(* TreeFrame *)

	PROCEDURE (f: TreeFrame) SetOffset (x, y: INTEGER);
	BEGIN
		f.SetOffset^(x, y);
		Adapt(f, f.i)
	END SetOffset;

	PROCEDURE (f: TreeFrame) Close;
		VAR res: INTEGER;
	BEGIN
		IF f.i.wnd # 0 THEN	(* deallocate *)
			ASSERT(f.rider # NIL, 100); ASSERT(f.rider.Base() # NIL, 101);
			res := WinApi.DestroyWindow(f.i.wnd);
			f.i.wnd := 0; f.i.ctrl := 0
		END;
		IF f.himl # NIL THEN res := WinCtl.ImageList_Destroy(f.himl) END
	END Close;

	PROCEDURE (f: TreeFrame) Recreate (), NEW;
		VAR res: INTEGER; style: SET;
			icex: WinCtl.INITCOMMONCONTROLSEX; ok: WinApi.BOOL; hbmp: WinApi.HANDLE;
	BEGIN
		f.Close(); f.i.readOnly := f.readOnly;
		icex.dwSize := SIZE(WinCtl.INITCOMMONCONTROLSEX);
		icex.dwICC := WinCtl.ICC_TREEVIEW_CLASSES;
		ok := WinCtl.InitCommonControlsEx(icex);
		style := WinApi.WS_VISIBLE + WinApi.WS_CHILD + WinApi.WS_BORDER + WinCtl.TVS_SHOWSELALWAYS
				+ WinCtl.TVS_DISABLEDRAGDROP;
		IF f.haslines THEN style := style + WinCtl.TVS_HASLINES END;
		IF f.hasbuttons THEN style := style + WinCtl.TVS_HASBUTTONS END;
		IF f.atroot THEN style := style + WinCtl.TVS_LINESATROOT END;
		Open(f, WinCtl.WC_TREEVIEWW, "", style, {}, f.i);	(* 3 *)
		res := WinApi.SendMessageW(f.i.ctrl, WinCtl.TV_FIRST + 033, 100, 0); (* TVM_SETSCROLLTIME*)
		IF f.foldericons THEN
			(* Create an imagelist and associate it with the control *)
			IF f.himl # NIL THEN ok := WinCtl.ImageList_Destroy(f.himl) END;
			f.himl := WinCtl.ImageList_Create(16, 16, {}, 3, 0);
			IF f.himl # NIL THEN
				hbmp := WinApi.LoadImageW(
					instance, "#6", WinApi.IMAGE_ICON, 16, 16, ORD(WinApi.LR_LOADTRANSPARENT));
				f.folderimg := WinCtl.ImageList_ReplaceIcon(f.himl, -1, hbmp);
				ok := WinApi.DestroyIcon(hbmp);
				hbmp := WinApi.LoadImageW(
					instance, "#7", WinApi.IMAGE_ICON, 16, 16, ORD(WinApi.LR_LOADTRANSPARENT));
				f.openimg := WinCtl.ImageList_ReplaceIcon(f.himl, -1, hbmp);
				ok := WinApi.DestroyIcon(hbmp);
				hbmp := WinApi.LoadImageW(
					instance, "#8", WinApi.IMAGE_ICON, 16, 16, ORD(WinApi.LR_LOADTRANSPARENT));
				f.leafimg := WinCtl.ImageList_ReplaceIcon(f.himl, -1, hbmp);
				ok := WinApi.DestroyIcon(hbmp);
				res := WinApi.SendMessageW(
					f.i.ctrl, WinCtl.TVM_SETIMAGELIST, WinCtl.TVSIL_NORMAL, SYSTEM.VAL(INTEGER, f.himl))
			END
		END
	END Recreate;

	PROCEDURE (f: TreeFrame) Update;
		VAR res: INTEGER;
	BEGIN
		IF ~f.disabled & ~f.readOnly THEN
			res := WinApi.SendMessageW(f.i.ctrl, WinCtl.TV_FIRST + 029, 0, -1); (* TVM_SETBKCOLOR *)
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN
				res := WinApi.EnableWindow(f.i.ctrl, 1)
			END
		ELSIF ~f.disabled & f.readOnly THEN
			res := WinApi.SendMessageW(
				f.i.ctrl, WinCtl.TV_FIRST + 029, 0, Ports.dialogBackground); (* TVM_SETBKCOLOR *)
			IF WinApi.IsWindowEnabled(f.i.ctrl) = 0 THEN
				res := WinApi.EnableWindow(f.i.ctrl, 1)
			END
		ELSE
			res := WinApi.SendMessageW(
				f.i.ctrl, WinCtl.TV_FIRST + 029, 0, Ports.dialogBackground); (* TVM_SETBKCOLOR *)
			IF WinApi.IsWindowEnabled(f.i.ctrl) # 0 THEN
				res := WinApi.EnableWindow(f.i.ctrl, 0)
			END
		END;
		res := WinApi.UpdateWindow(f.i.ctrl);
		IF ~f.disabled & (f.readOnly # f.i.readOnly) THEN
			IF f.readOnly THEN
				(* Selection has to be focused since it won't be visible on a gray backround otherwise *)
				res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_SETFOCUS, 0, 0)
			ELSE
				IF ~f.i.hasFocus THEN
					res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KILLFOCUS, 0, 0)
				END
			END;
			f.i.readOnly := f.readOnly
		END;
		res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_SETFOCUS, 0, 0)
	END Update;

	PROCEDURE (f: TreeFrame) WinInsertItem (
		VAR tn: Dialog.TreeNode; pWndAdr: INTEGER; OUT wndAdr: INTEGER
	), NEW;
		VAR res: INTEGER; p: Dialog.TreeNode; s: Dialog.String;
			tvi: WinCtl.TVITEMW; tvins: WinCtl.TVINSERTSTRUCTW;
	BEGIN
		ASSERT(tn # NIL, 20);
		f.treeArray[f.curindex].tn := tn;
		p := f.Parent(f, tn);
		tvi.mask := WinCtl.TVIF_TEXT + WinCtl.TVIF_PARAM + WinCtl.TVIF_IMAGE + WinCtl.TVIF_SELECTEDIMAGE;
		tn.GetName(s); Dialog.MapString(s, s);
		tvi.pszText := s; tvi.cchTextMax := LEN(s); tvi.lParam := f.curindex;
		IF tn.IsFolder() THEN
			IF tn.IsExpanded() THEN tvi.iImage := f.openimg
			ELSE tvi.iImage := f.folderimg
			END
		ELSE
			tvi.iImage := f.leafimg
		END;
		tvi.iSelectedImage := tvi.iImage; tvins.item := tvi;
		tvins.hParent := SYSTEM.VAL(WinCtl.Ptr_TREEITEM, pWndAdr);
		IF f.sorted THEN tvins.hInsertAfter := SYSTEM.VAL(WinCtl.Ptr_TREEITEM, WinCtl.TVI_SORT)
		ELSE tvins.hInsertAfter := SYSTEM.VAL(WinCtl.Ptr_TREEITEM, WinCtl.TVI_LAST)
		END;
		wndAdr := WinApi.SendMessageW(f.i.ctrl, WinCtl.TVM_INSERTITEMW, 0, SYSTEM.ADR(tvins));
		f.treeArray[f.curindex].wndAdr := wndAdr;
		IF p # NIL THEN
			IF p.IsExpanded() THEN
				res := WinApi.SendMessageW(f.i.ctrl, WinCtl.TVM_EXPAND, WinCtl.TVE_EXPAND, pWndAdr)
			ELSE
				res := WinApi.SendMessageW(f.i.ctrl, WinCtl.TVM_EXPAND, WinCtl.TVE_COLLAPSE, pWndAdr)
			END
		END;
		INC(f.curindex)
	END WinInsertItem;

	PROCEDURE (f: TreeFrame) InsertNode(tn: Dialog.TreeNode; pWndAdr: INTEGER), NEW;
		VAR wndAdr: INTEGER;
	BEGIN
		IF tn # NIL THEN
			f.WinInsertItem(tn, pWndAdr, wndAdr);
			f.InsertNode(f.Child(f, tn), wndAdr);
			f.InsertNode(f.Next(f, tn), pWndAdr)
		END
	END InsertNode;

	PROCEDURE (f: TreeFrame) UpdateList;
		VAR res, len, i: INTEGER; done: BOOLEAN; sel: Dialog.TreeNode;
	BEGIN
		f.inUpdateList := TRUE;
		len := 0; f.curindex := 0; sel := f.Selected(f);
		len := f.NofNodes(f); IF len > 0 THEN NEW(f.treeArray, len) END;
		(* Throw away the old windows control and create a new one *)
		f.Recreate();
		(* Go through the tree and insert new nodes into the treeview *)
		f.InsertNode(f.Child(f, NIL), WinCtl.TVI_ROOT);
		(* Select the node that is selected *)
		IF sel = NIL THEN
			res := WinApi.SendMessageW(f.i.ctrl, WinCtl.TVM_SELECTITEM, WinCtl.TVGN_CARET, 0)
		ELSE
			i := 0; done := FALSE;
			WHILE (i < len) & (~done) DO
				IF sel = f.treeArray[i].tn THEN
					res := WinApi.SendMessageW(
						f.i.ctrl, WinCtl.TVM_SELECTITEM, WinCtl.TVGN_CARET, f.treeArray[i].wndAdr);
					done := TRUE
				END;
				INC(i)
			END
		END;
		f.Select(f, sel);
		f.Update;
		IF f.i.hasFocus THEN res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_SETFOCUS, 0, 0) END;
		f.inUpdateList := FALSE
	END UpdateList;

	PROCEDURE (f: TreeFrame) ExpandCollapse(index, action: INTEGER), NEW;
		VAR res: INTEGER; tn: Dialog.TreeNode; tvi: WinCtl.TVITEMW;
	BEGIN
		tn := f.treeArray[index].tn;
		IF action = WinCtl.TVE_COLLAPSE THEN
			f.SetExpansion(f, tn, FALSE)
		ELSIF action = WinCtl.TVE_EXPAND THEN
			f.SetExpansion(f, tn, TRUE)
		ELSIF action = WinCtl.TVE_TOGGLE THEN
			f.SetExpansion(f, tn, ~tn.IsExpanded())
		END;
		tvi.mask := WinCtl.TVIF_IMAGE + WinCtl.TVIF_SELECTEDIMAGE;
		tvi.hItem := SYSTEM.VAL(WinCtl.Ptr_TREEITEM, f.treeArray[index].wndAdr);
		IF tn.IsExpanded() THEN
			tvi.iImage := f.openimg; tvi.iSelectedImage := f.openimg
		ELSE
			tvi.iImage := f.folderimg; tvi.iSelectedImage := f.folderimg
		END;
		res := WinApi.SendMessageW(f.i.ctrl, WinCtl.TVM_SETITEMW, 0, SYSTEM.ADR(tvi))
	END ExpandCollapse;

	PROCEDURE (f: TreeFrame) DblClickOk (x, y: INTEGER): BOOLEAN;
		VAR res: INTEGER; hinf: WinCtl.TVHITTESTINFO;
	BEGIN
		hinf.pt.x := x DIV f.unit; hinf.pt.y := y DIV f.unit;
		res := WinApi.SendMessageW(f.i.ctrl, WinCtl.TVM_HITTEST, 0, SYSTEM.ADR(hinf));
		IF hinf.hItem = NIL THEN
			RETURN FALSE
		ELSE
			RETURN SYSTEM.VAL(INTEGER, hinf.hItem) = f.treeArray[f.selIndex].wndAdr
		END
	END DblClickOk;

	PROCEDURE (f: TreeFrame) MouseDown (x, y: INTEGER; buttons: SET);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			HandleMouse(f.i.wnd, x DIV f.unit, y DIV f.unit, buttons)
		END
	END MouseDown;

	PROCEDURE (f: TreeFrame) WheelMove (x, y: INTEGER; op, nofLines: INTEGER; VAR done: BOOLEAN);
	BEGIN
		ASSERT(~f.disabled, 100);
		IF f.rider # NIL THEN
			HandleWheel(f.i.ctrl, x, y, op, nofLines, done)
		END
	END WheelMove;

	PROCEDURE (f: TreeFrame) Restore (l, t, r, b: INTEGER);
	BEGIN
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			f.UpdateList
		ELSE
			f.Update
		END;
		IF f.i.wnd # 0 THEN PaintRect(f, f.i, l, t, r, b) ELSE Print(f, f.dot, -1, -1, "") END
	END Restore;

	PROCEDURE (f: TreeFrame) KeyDown (ch: CHAR; modifiers: SET);
		VAR res: INTEGER;
	BEGIN
		ASSERT(~f.disabled, 100);
		IF ch = " " THEN
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYDOWN, ORD(ch), 0);
			res := WinApi.SendMessageW(f.i.ctrl, WinApi.WM_KEYUP, ORD(ch), 0)
		ELSE
			SendKey(ch, f.i.ctrl)
		END
	END KeyDown;

	PROCEDURE (f: TreeFrame) Mark (on, focus: BOOLEAN);
	BEGIN
		Mark(on, f.front, f.i)
	END Mark;

	PROCEDURE (f: TreeFrame) GetSize (OUT w, h: INTEGER);
		VAR rect: WinApi.RECT; res: INTEGER;
	BEGIN
		res := WinApi.GetWindowRect(f.i.wnd, rect);
		w := (rect.right - rect.left) * f.unit; h := (rect.bottom - rect.top) * f.unit
	END GetSize;


	(* Directory *)

	PROCEDURE (d: Directory) GetPushButtonSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 56 * Ports.point END;
		IF h = Views.undefined THEN h := 18 * Ports.point END
	END GetPushButtonSize;

	PROCEDURE (d: Directory) GetCheckBoxSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 60 * Ports.point END;
		IF h = Views.undefined THEN h := 12 * Ports.point END
	END GetCheckBoxSize;

	PROCEDURE (d: Directory) GetRadioButtonSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 60 * Ports.point END;
		IF h = Views.undefined THEN h := 12 * Ports.point END
	END GetRadioButtonSize;

	PROCEDURE (d: Directory) GetScrollBarSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 120 * Ports.point END;
		IF h = Views.undefined THEN h := 12 * Ports.point END
	END GetScrollBarSize;

	PROCEDURE (d: Directory) GetFieldSize (max: INTEGER; VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN
			IF max = 0 THEN w := 80 * Ports.point
			ELSIF max < 10 THEN w := 32 * Ports.point
			ELSIF max < 15 THEN w := 56 * Ports.point
			ELSIF max < 30 THEN w := 80 * Ports.point
			ELSIF max < 100 THEN w := 120 * Ports.point
			ELSE w := 150 * Ports.point
			END
		END;
		IF h = Views.undefined THEN h := 17 * Ports.point END
	END GetFieldSize;

	PROCEDURE (d: Directory) GetUpDownFieldSize (max: INTEGER; VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 56 * Ports.point END;
		IF h = Views.undefined THEN h := 17 * Ports.point END
	END GetUpDownFieldSize;

	PROCEDURE (d: Directory) GetDateFieldSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 72 * Ports.point END;
		IF h = Views.undefined THEN h := 17 * Ports.point END
	END GetDateFieldSize;

	PROCEDURE (d: Directory) GetTimeFieldSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 72 * Ports.point END;
		IF h = Views.undefined THEN h := 17 * Ports.point END
	END GetTimeFieldSize;

	PROCEDURE (d: Directory) GetColorFieldSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 36 * Ports.point END;
		IF h = Views.undefined THEN h := 18 * Ports.point END
	END GetColorFieldSize;

	PROCEDURE (d: Directory) GetListBoxSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 100 * Ports.point END;
		IF h = Views.undefined THEN h := 18 * Ports.point END
	END GetListBoxSize;

	PROCEDURE (d: Directory) GetSelectionBoxSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 100 * Ports.point END;
		IF h = Views.undefined THEN h := 54 * Ports.point END
	END GetSelectionBoxSize;

	PROCEDURE (d: Directory) GetComboBoxSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 100 * Ports.point END;
		IF h = Views.undefined THEN h := 18 * Ports.point END
	END GetComboBoxSize;

	PROCEDURE (d: Directory) GetCaptionSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 50 * Ports.point END;
		IF h = Views.undefined THEN h := 12 * Ports.point END
	END GetCaptionSize;

	PROCEDURE (d: Directory) GetGroupSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 100 * Ports.point END;
		IF h = Views.undefined THEN h := 100 * Ports.point END
	END GetGroupSize;

	PROCEDURE (d: Directory) GetTreeFrameSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 100 * Ports.point END;
		IF h = Views.undefined THEN h := 100 * Ports.point END
	END GetTreeFrameSize;

	PROCEDURE (d: Directory) NewPushButton (): StdCFrames.PushButton;
		VAR f: PushButton;
	BEGIN
		NEW(f); RETURN f
	END NewPushButton;

	PROCEDURE (d: Directory) NewCheckBox (): StdCFrames.CheckBox;
		VAR f: CheckBox;
	BEGIN
		NEW(f); RETURN f
	END NewCheckBox;

	PROCEDURE (d: Directory) NewRadioButton (): StdCFrames.RadioButton;
		VAR f: RadioButton;
	BEGIN
		NEW(f); RETURN f
	END NewRadioButton;

	PROCEDURE (d: Directory) NewScrollBar (): StdCFrames.ScrollBar;
		VAR f: ScrollBar;
	BEGIN
		NEW(f); RETURN f
	END NewScrollBar;

	PROCEDURE (d: Directory) NewField (): StdCFrames.Field;
		VAR f: Field;
	BEGIN
		NEW(f); RETURN f
	END NewField;

	PROCEDURE (d: Directory) NewUpDownField (): StdCFrames.UpDownField;
		VAR f: UpDownField;
	BEGIN
		NEW(f); RETURN f
	END NewUpDownField;

	PROCEDURE (d: Directory) NewDateField (): StdCFrames.DateField;
		VAR f: DateField;
	BEGIN
		NEW(f); RETURN f
	END NewDateField;

	PROCEDURE (d: Directory) NewTimeField (): StdCFrames.TimeField;
		VAR f: TimeField;
	BEGIN
		NEW(f); RETURN f
	END NewTimeField;

	PROCEDURE (d: Directory) NewColorField (): StdCFrames.ColorField;
		VAR f: ColorField;
	BEGIN
		NEW(f); RETURN f
	END NewColorField;

	PROCEDURE (d: Directory) NewListBox (): StdCFrames.ListBox;
		VAR f: ListBox;
	BEGIN
		NEW(f); RETURN f
	END NewListBox;

	PROCEDURE (d: Directory) NewSelectionBox (): StdCFrames.SelectionBox;
		VAR f: SelectionBox;
	BEGIN
		NEW(f); RETURN f
	END NewSelectionBox;

	PROCEDURE (d: Directory) NewComboBox (): StdCFrames.ComboBox;
		VAR f: ComboBox;
	BEGIN
		NEW(f); RETURN f
	END NewComboBox;

	PROCEDURE (d: Directory) NewCaption (): StdCFrames.Caption;
		VAR f: Caption;
	BEGIN
		NEW(f); RETURN f
	END NewCaption;

	PROCEDURE (d: Directory) NewGroup (): StdCFrames.Group;
		VAR f: Group;
	BEGIN
		NEW(f); RETURN f
	END NewGroup;

	PROCEDURE (d: Directory) NewTreeFrame (): StdCFrames.TreeFrame;
		VAR f: TreeFrame;
	BEGIN
		NEW(f); f.inUpdateList := FALSE; RETURN f
	END NewTreeFrame;

	(* control window class *)

	(* Used for common controls, not standard controls *)
	PROCEDURE HandleNotifyMsg (wnd: WinApi.HANDLE; lParam, out: INTEGER);
		VAR
			c: StdCFrames.Frame; ret: INTEGER;
			pnmhdr: WinApi.PtrNMHDR; pnmtv: WinCtl.PtrNMTREEVIEWW;
	BEGIN
		ret := WinApi.FALSE;
		c := SYSTEM.VAL(StdCFrames.Frame, WinApi.GetWindowLongW(wnd, dlgWindowExtra));
		pnmhdr := SYSTEM.VAL(WinApi.PtrNMHDR, lParam);
		WITH c: TreeFrame DO
			IF c.i.ctrl # 0 THEN
				IF ~c.inUpdateList & c.readOnly & (pnmhdr.code = WinCtl.TVN_SELCHANGINGW) THEN
					ret := WinApi.TRUE
				ELSIF pnmhdr.code = WinCtl.TVN_SELCHANGEDW THEN
					pnmtv := SYSTEM.VAL(WinCtl.PtrNMTREEVIEWW, lParam);
					c.selIndex := pnmtv.itemNew.lParam;
					c.Select(c, c.treeArray[c.selIndex].tn)
				ELSIF pnmhdr.code = WinCtl.TVN_ITEMEXPANDEDW THEN
					pnmtv := SYSTEM.VAL(WinCtl.PtrNMTREEVIEWW, lParam);
					c.ExpandCollapse(pnmtv.itemNew.lParam, pnmtv.action)
				END
			END
		ELSE
		END;
		SYSTEM.PUT(out, ret)
	END HandleNotifyMsg;

	PROCEDURE HandleCommand (wnd: WinApi.HANDLE; wParam, lParam: INTEGER);
		VAR res, nc, i, j: INTEGER; c: StdCFrames.Frame; s: ARRAY 512 OF CHAR;
			ps: POINTER TO ARRAY OF CHAR; b: BOOLEAN;
	BEGIN
		c := SYSTEM.VAL(StdCFrames.Frame, WinApi.GetWindowLongW(wnd, dlgWindowExtra));
		nc := wParam DIV 65536;
		WITH c: PushButton DO
			IF c.i.ctrl # 0 THEN
				IF nc = WinApi.BN_CLICKED THEN Execute(c)
				END
			END
		| c: CheckBox DO
			IF c.i.ctrl # 0 THEN
				IF nc = WinApi.BN_CLICKED THEN
					i := WinApi.SendMessageW(c.i.ctrl, WinApi.BM_GETCHECK, 0, 0);
					IF i = 0 THEN i := 1 ELSE i := 0 END;
					res := WinApi.SendMessageW(c.i.ctrl, WinApi.BM_SETCHECK, i, 0);
					c.undef := FALSE; c.Set(c, i = 1)
(*
					c.undef := res = 2;
					c.Set(c, res = 1)
*)
				END
			END
		| c: RadioButton DO
			IF c.i.ctrl # 0 THEN
				IF nc = WinApi.BN_CLICKED THEN
					res := WinApi.SendMessageW(c.i.ctrl, WinApi.BM_GETCHECK, 0, 0);
					c.Set(c, res # 0)
				END
			END
		| c: Field DO
			IF c.i.ctrl # 0 THEN
				IF (nc = WinApi.EN_CHANGE) & ~c.isUpdate THEN
					IF ~c.left THEN (* right center alignment implies multiline which sends two update messages *)
						IF c.maxLen > 255 THEN
							NEW(ps, 2 * c.maxLen + 1);
							res := WinApi.GetWindowTextW(c.i.ctrl, ps^, LEN(ps^));
							IF c.multiLine THEN DelLF(ps^) END;
							IF c.del OR (ps$ # "") THEN c.Set(c, ps^) END
						ELSE
							res := WinApi.GetWindowTextW(c.i.ctrl, s, LEN(s));
							IF c.multiLine THEN DelLF(s) END;
							IF c.del OR (s$ # "") THEN c.Set(c, s) END
						END
					ELSE
						IF c.maxLen > 255 THEN
							NEW(ps, 2 * c.maxLen + 1);
							res := WinApi.GetWindowTextW(c.i.ctrl, ps^, LEN(ps^));
							IF c.multiLine THEN DelLF(ps^) END;
							c.Set(c, ps^)
						ELSE
							res := WinApi.GetWindowTextW(c.i.ctrl, s, LEN(s));
							IF c.multiLine THEN DelLF(s) END;
							c.Set(c, s)
						END
					END
				END
			END
		| c: UpDownField DO
			IF c.i.ctrl # 0 THEN
				IF (nc = WinApi.EN_CHANGE) & ~c.isUpdate THEN
					res := WinApi.GetWindowTextW(c.i.ctrl, s, LEN(s));
					IF (s = "") OR (s = "-") THEN c.val := 0; res := 0
					ELSE Strings.StringToInt(s, c.val, res)
					END;
					IF res = 0 THEN c.Set(c, c.val)
					ELSE Dialog.Beep; c.Update
					END
				END
			END
		| c: ColorField DO
			IF c.i.ctrl # 0 THEN
				IF nc = WinApi.CBN_SELCHANGE THEN
					i := WinApi.SendMessageW(c.i.ctrl, WinApi.CB_GETCURSEL, 0, 0);
					IF i = numColors THEN
						Dialog.GetColor(c.color, i, b);
						IF b THEN c.Set(c, i); c.Update END
					ELSIF i >= 0 THEN
						c.color := colors[i]; c.Set(c, c.color)
					END
				END
			END
		| c: ListBox DO
			IF c.i.ctrl # 0 THEN
				IF c.i.dropDown THEN
					IF nc = WinApi.CBN_SELCHANGE THEN
						i := WinApi.SendMessageW(c.i.ctrl, WinApi.CB_GETCURSEL, 0, 0);
						IF c.sorted THEN i := WinApi.SendMessageW(c.i.ctrl, WinApi.CB_GETITEMDATA, i, 0) END;
						IF i >= 0 THEN c.Set(c, i) END
					END
				ELSE
					IF nc = WinApi.LBN_SELCHANGE THEN
						i := WinApi.SendMessageW(c.i.ctrl, WinApi.LB_GETCURSEL, 0, 0);
						IF c.sorted THEN i := WinApi.SendMessageW(c.i.ctrl, WinApi.LB_GETITEMDATA, i, 0) END;
						IF i >= 0 THEN c.Set(c, i) END
					END
				END
			END
		| c: SelectionBox DO
			IF c.i.ctrl # 0 THEN
				IF nc = WinApi.LBN_SELCHANGE THEN
(*
					i := WinApi.SendMessageW(c.i.ctrl, WinApi.LBGetAnchorIndex, 0, 0);
					j := WinApi.SendMessageW(c.i.ctrl, WinApi.LBGetCaretIndex, 0, 0);
					res := WinApi.SendMessageW(c.i.ctrl, WinApi.LBGetSel, i, 0);
					IF c.sorted THEN
						i := WinApi.SendMessageW(c.i.ctrl, WinApi.LBGetItemData, i, 0);
						j := WinApi.SendMessageW(c.i.ctrl, WinApi.LBGetItemData, j, 0)
					END;
					IF (i >= 0) & (i < c.num) THEN
						Log.Int(i); Log.Int(j); Log.Ln; Log.Int(wParam); Log.Int(lParam); Log.Int(res); Log.Ln;
						IF (res # 0) THEN c.Incl(c, i, i) ELSE c.Excl(c, i, i) END
					END
*)
					c.delayUpdate := TRUE; c.needsUpdate := FALSE;
					i := 0;
					WHILE i < c.num DO
						j := i;
						res := WinApi.SendMessageW(c.i.ctrl, WinApi.LB_GETSEL, j, 0);
						IF c.sorted THEN j := WinApi.SendMessageW(c.i.ctrl, WinApi.LB_GETITEMDATA, j, 0) END;
						c.Get(c, j, b);
						IF (res # 0) & ~b THEN c.Incl(c, j, j)
						ELSIF (res = 0) & b THEN c.Excl(c, j, j)
						END;
						INC(i)
					END;
					c.delayUpdate := FALSE;
					IF c.needsUpdate THEN c.Update END
				END
			END
		| c: ComboBox DO
			IF c.i.ctrl # 0 THEN
				IF nc = WinApi.CBN_EDITCHANGE THEN
					res := WinApi.GetWindowTextW(c.i.ctrl, s, LEN(s));
					c.Set(c, s)
				ELSIF nc = WinApi.CBN_SELCHANGE THEN
					i := WinApi.SendMessageW(c.i.ctrl, WinApi.CB_GETCURSEL, 0, 0);
					IF i >= 0 THEN
						res := WinApi.SendMessageW(c.i.ctrl, WinApi.CB_GETLBTEXT, i, SYSTEM.ADR(s));
						c.Set(c, s)
					END
				END
			END
		ELSE
		END
	END HandleCommand;

	PROCEDURE HandleScroll (wnd: WinApi.HANDLE; wParam, lParam: INTEGER);
		VAR nc, size, sect, pos: INTEGER; c: StdCFrames.Frame; arrows: BOOLEAN;
	BEGIN
		arrows := FALSE;
		c := SYSTEM.VAL(StdCFrames.Frame, WinApi.GetWindowLongW(wnd, dlgWindowExtra));
		WITH c: ScrollBar DO
			IF c.i.ctrl # 0 THEN
				Views.ValidateRoot(Views.RootOf(c));
				nc := wParam MOD 65536; c.Get(c, size, sect, pos);
				IF (nc = WinApi.SB_THUMBPOSITION) OR
					WinDialog.visualScroll & (nc = WinApi.SB_THUMBTRACK) THEN
						c.Set(c, WinApi.MulDiv(wParam DIV 65536, size - sect, scrollRange))
				ELSIF nc = WinApi.SB_LINEUP THEN c.Track(c, StdCFrames.lineUp, pos)
				ELSIF nc = WinApi.SB_LINEDOWN THEN c.Track(c, StdCFrames.lineDown, pos)
				ELSIF nc = WinApi.SB_PAGEUP THEN c.Track(c, StdCFrames.pageUp, pos)
				ELSIF nc = WinApi.SB_PAGEDOWN THEN c.Track(c, StdCFrames.pageDown, pos)
				END;
				c.Update
			END
		| c: UpDownField DO
			IF c.i.ctrl # 0 THEN arrows := TRUE END
		| c: TimeField DO
			IF c.i.ctrl # 0 THEN arrows := TRUE END
		| c: DateField DO
			IF c.i.ctrl # 0 THEN arrows := TRUE END
		ELSE
		END;
		IF arrows THEN
			nc := wParam MOD 65536;
			IF nc = WinApi.SB_THUMBPOSITION THEN
				IF wParam DIV 65536 # 0 THEN c.KeyDown(AU, {})
				ELSE c.KeyDown(AD, {})
				END
			END
		END
	END HandleScroll;

	PROCEDURE HandleDraw (wnd: WinApi.HANDLE; wParam, lParam: INTEGER);
		VAR res: INTEGER; c: StdCFrames.Frame; p: WinApi.PtrDRAWITEMSTRUCT;
			brush: WinApi.HANDLE; rect: WinApi.RECT; col: Ports.Color;
	BEGIN
		c := SYSTEM.VAL(StdCFrames.Frame, WinApi.GetWindowLongW(wnd, dlgWindowExtra));
		p := SYSTEM.VAL(WinApi.PtrDRAWITEMSTRUCT, lParam);
		WITH c: ColorField DO
			IF 0 IN p.itemState THEN
				rect := p.rcItem;
				brush := WinApi.CreateSolidBrush(HostPorts.selBackground);
				res := WinApi.FillRect(p.hDC, rect, brush);
				res := WinApi.DeleteObject(brush);
				INC(rect.left); INC(rect.top); DEC(rect.right); DEC(rect.bottom);
				brush := WinApi.CreateSolidBrush(Ports.background);
				res := WinApi.FillRect(p.hDC, rect, brush);
				res := WinApi.DeleteObject(brush)
			ELSE
				IF c.disabled OR c.readOnly THEN
					brush := WinApi.CreateSolidBrush(Ports.dialogBackground)
				ELSE
					brush := WinApi.CreateSolidBrush(Ports.background)
				END;
				res := WinApi.FillRect(p.hDC, p.rcItem, brush);
				res := WinApi.DeleteObject(brush)
			END;
			IF (p.itemID = numColors) & (p.rcItem.top > 20) THEN
				brush := WinApi.CreateSolidBrush(HostPorts.textCol);
				rect.top := p.rcItem.bottom - 6; rect.bottom := rect.top + 2;
				rect.left := p.rcItem.left + 4; rect.right := rect.left + 2;
				res := WinApi.FillRect(p.hDC, rect, brush);
				rect.left := p.rcItem.left + 8; rect.right := rect.left + 2;
				res := WinApi.FillRect(p.hDC, rect, brush);
				rect.left := p.rcItem.left + 12; rect.right := rect.left + 2;
				res := WinApi.FillRect(p.hDC, rect, brush)
			ELSIF p.itemID >= 0 THEN
				rect := p.rcItem; INC(rect.left, 2); INC(rect.top, 2); DEC(rect.right, 2); DEC(rect.bottom, 2);
				IF p.itemID = numColors THEN col := c.color
				ELSIF (p.itemID >= 0) & (p.itemID < LEN(colors)) THEN col := colors[p.itemID]
				ELSE col := HostPorts.textCol
				END;
				brush := WinApi.CreateSolidBrush(col);
				res := WinApi.FillRect(p.hDC, rect, brush);
				res := WinApi.DeleteObject(brush)
			END
		END
	END HandleDraw;

	PROCEDURE ColorBrush (col: Ports.Color): WinApi.HANDLE;
		VAR p: BrushCache;
	BEGIN
		IF col = Ports.white THEN RETURN WinApi.GetStockObject(WinApi.WHITE_BRUSH)
		ELSIF col = Ports.dialogBackground THEN RETURN HostPorts.dialogBrush
		ELSE
			p := brushes;
			WHILE (p # NIL) & (p.col # col) DO p := p.next END;
			IF p # NIL THEN RETURN p.brush
			ELSE
				NEW(p); p.col := col; p.brush := WinApi.CreateSolidBrush(col); p.next := brushes; brushes := p;
				RETURN p.brush
			END
		END
	END ColorBrush;

	PROCEDURE [2] CtrlHandler (wnd: WinApi.HANDLE; message, wParam, lParam: INTEGER): INTEGER;
		VAR res: INTEGER; c: StdCFrames.Frame; p: WinApi.PtrMEASUREITEMSTRUCT;
	BEGIN
		IF message = WinApi.WM_COMMAND THEN
			Kernel.Try(HandleCommand, wnd, wParam, lParam);
			RETURN 0
		ELSIF message = WinApi.WM_NOTIFY THEN
			res := -1;
			Kernel.Try(HandleNotifyMsg, wnd, lParam, SYSTEM.ADR(res));
			IF res # -1 THEN RETURN res ELSE RETURN 0 END
		ELSIF (message = WinApi.WM_HSCROLL) OR (message = WinApi.WM_VSCROLL) THEN
			Kernel.Try(HandleScroll, wnd, wParam, lParam);
			RETURN 0
		ELSIF message = WinApi.WM_DRAWITEM THEN
			Kernel.Try(HandleDraw, wnd, wParam, lParam);
			RETURN 1
		ELSIF message = WinApi.WM_MEASUREITEM THEN
			c := SYSTEM.VAL(StdCFrames.Frame, WinApi.GetWindowLongW(wnd, dlgWindowExtra));
			WITH c: ColorField DO
				p := SYSTEM.VAL(WinApi.PtrMEASUREITEMSTRUCT, lParam);
				IF p.itemID = -1 THEN p.itemHeight := 11 * Ports.point DIV c.unit + 1
				ELSE p.itemHeight := 11 * Ports.point DIV c.unit - 1
				END;
				RETURN 1
			ELSE
			END
		ELSIF (message >= WinApi.WM_CTLCOLORMSGBOX) & (message <= WinApi.WM_CTLCOLORSTATIC) THEN
			c := SYSTEM.VAL(StdCFrames.Frame, WinApi.GetWindowLongW(wnd, dlgWindowExtra));
			IF ((c IS Field) OR (c IS UpDownField) OR (c IS TimeField) OR (c IS DateField)
					OR (c IS ListBox) OR (c IS SelectionBox) OR (c IS ColorField))
					& (c.disabled OR c.readOnly) THEN
				res := WinApi.SetBkColor(wParam, Ports.dialogBackground);
				res := WinApi.SetBkMode(wParam, 2);	(* opaque *)
				RETURN HostPorts.dialogBrush
			ELSIF (c IS TreeFrame) & (c.disabled OR c.readOnly) THEN
				res := WinApi.SetBkColor(wParam, Ports.dialogBackground);
				res := WinApi.SetBkMode(wParam, 2);	(* opaque *)
				RETURN HostPorts.dialogBrush
			ELSIF ((c IS Field) OR (c IS UpDownField) OR (c IS TimeField) OR (c IS DateField))
					& ~c.disabled & c.undef THEN
				res := WinApi.SetBkMode(wParam, 1); (* transparent *)
				res := WinApi.SetTextColor(wParam, 0C0C0C0H);
				RETURN HostPorts.dim50Brush
			ELSIF c IS Caption THEN
				res := WinApi.SetTextColor(wParam, HostPorts.dialogTextCol);
				res := WinApi.SetBkColor(wParam, c(Caption).i.bkgnd);
				res := WinApi.SetBkMode(wParam, 2);	(* opaque *)
				RETURN ColorBrush(c(Caption).i.bkgnd)
			ELSIF CtlColor # NIL THEN
				res := CtlColor(message, wParam, lParam);
				IF res # 0 THEN RETURN res END
			ELSIF c IS CheckBox THEN
				res := WinApi.SetTextColor(wParam, HostPorts.dialogTextCol);
				res := WinApi.SetBkColor(wParam, c(CheckBox).i.bkgnd);
				res := WinApi.SetBkMode(wParam, 2);	(* opaque *)
				RETURN ColorBrush(c(CheckBox).i.bkgnd)
			ELSIF c IS RadioButton THEN
				res := WinApi.SetTextColor(wParam, HostPorts.dialogTextCol);
				res := WinApi.SetBkColor(wParam, c(RadioButton).i.bkgnd);
				res := WinApi.SetBkMode(wParam, 2);	(* opaque *)
				RETURN ColorBrush(c(RadioButton).i.bkgnd)
			ELSIF c IS Group THEN
				res := WinApi.SetTextColor(wParam, HostPorts.dialogTextCol);
				res := WinApi.SetBkColor(wParam, c(Group).i.bkgnd);
				res := WinApi.SetBkMode(wParam, 2);	(* opaque *)
				RETURN ColorBrush(c(Group).i.bkgnd)
			END
		ELSIF message = WinApi.WM_ERASEBKGND THEN
			RETURN 1
		END;
		RETURN WinApi.DefWindowProcW(wnd, message, wParam, lParam)
	END CtrlHandler;


	PROCEDURE SetDefFonts*;
	BEGIN
		StdCFrames.defaultFont := HostFonts.dlgFont;
		StdCFrames.defaultLightFont := Fonts.dir.This(
			HostFonts.dlgFont.typeface, HostFonts.dlgFont.size, HostFonts.dlgFont.style, Fonts.normal)
	END SetDefFonts;

	PROCEDURE Install;
		VAR dir: Directory;
	BEGIN
		NEW(dir); StdCFrames.SetDir(dir)
	END Install;

	PROCEDURE InitClass;
		VAR res: INTEGER; class: WinApi.WNDCLASSW;
	BEGIN
		class.hCursor := WinApi.LoadCursorW(0, SYSTEM.VAL(WinApi.PtrWSTR, WinApi.IDC_ARROW));
		class.hIcon := WinApi.LoadIconW(instance, SYSTEM.VAL(WinApi.PtrWSTR, 1));
		class.lpszMenuName := NIL;
		class.lpszClassName := "Oberon Ctrl";
		class.hbrBackground := 0;	(* no background *)
		class.style := {};
		class.hInstance := instance;
		class.lpfnWndProc := CtrlHandler;
		class.cbClsExtra := 0;
		class.cbWndExtra := dlgWindowExtra + 4;
		res := WinApi.RegisterClassW(class)
	END InitClass;

	PROCEDURE InitNationalInfo;
		VAR res: INTEGER; str: ARRAY 8 OF CHAR;
	BEGIN
		res := WinApi.GetLocaleInfoW(HostRegistry.localeId, WinApi.LOCALE_SDATE, str, LEN(str));
		dateSep := str[0];
		res := WinApi.GetLocaleInfoW(HostRegistry.localeId, WinApi.LOCALE_STIME, str, LEN(str));
		timeSep := str[0];
		res := WinApi.GetLocaleInfoW(HostRegistry.localeId, WinApi.LOCALE_IDATE, str, LEN(str));
		IF str = "1" THEN dayPart := 1; monthPart := 2; yearPart := 3; del1 := 2; del2 := 5
		ELSIF str = "2" THEN yearPart := 1; monthPart := 2; dayPart := 3; del1 := 4; del2 := 7
		ELSE monthPart := 1; dayPart := 2; yearPart := 3; del1 := 2; del2 := 5
		END;
		res := WinApi.GetLocaleInfoW(HostRegistry.localeId, WinApi.LOCALE_ITIME, str, LEN(str));
		IF str = "1" THEN
			lastPart := 3
		ELSE
			lastPart := 4;
			res := WinApi.GetLocaleInfoW(HostRegistry.localeId, WinApi.LOCALE_S1159, str, LEN(str));
			desig[0] := str$;
			res := WinApi.GetLocaleInfoW(HostRegistry.localeId, WinApi.LOCALE_S2359, str, LEN(str));
			desig[1] := str$
		END
	END InitNationalInfo;

	PROCEDURE InitColors;
	BEGIN
		colors[0] := 000000FFH;
		colors[1] := 0000FF00H;
		colors[2] := 00FF0000H;
		colors[3] := 0000FFFFH;
		colors[4] := 00FFFF00H;
		colors[5] := 00FF00FFH;
		colors[6] := 00000000H
	END InitColors;

	PROCEDURE Init;
	BEGIN
		StdCFrames.setFocus := TRUE;
		SetDefFonts;
		scW := WinApi.GetSystemMetrics(0);	(* screen width *)
		scH := WinApi.GetSystemMetrics(1);	(* screen height *)
		instance := WinApi.GetModuleHandleW(NIL);
		InitClass;
		InitNationalInfo;
		InitColors;
		Install
	END Init;

BEGIN
	Init
END WinCFrames.
