MODULE WinWindows;
(**
	project	= "BlackBox"
	organization	= "www.oberon.ch"
	contributors	= "Oberon microsystems, Alexander Iljin, Josef Templ"
	version	= "System/Rsrc/About"
	copyright	= "System/Rsrc/About"
	license	= "Docu/BB-License"
	changes	= "
	- 20060531, ai, Updated GetThisWindow to allow parameter p = NIL
	- 20060608, ai, Updated WMPaint processing to RETURN 0
	- 20070131, bh, Unicode support
	- 20070205, bh, Win32s handling removed
	- 20070209, bh, Windows character mapping removed
	- 20070210, jt, hookDocWinHandler added
	- 20070308, bh, handling of minimized windows corrected
	- 20080119, bh, handling of minimized windows corrected
	- 20141008, center #15, Dialog.appVersion used instead of Dialog.version 
	- 20141213, center #21, Fixing Controllers.TickMsg.tick overflow bug.
	- 20150511, center #45, Redraw window when is resizing
	- 20151105, center #83, Different window title for Mod, Rsrc, and Docu files
	- 20160324, center #111, code cleanups
	- 20160604, center #107, update windows from Idle procedure
	- 20170905, center #173, adding new modifiers to Controllers
	- 20171019, center #106, adding a work-around for View restored twice on Open
	- 20190922, center #202, drag & pick across BlackBox instances traps
	"
	issues	= "
	- ...
	"

**)

	IMPORT Log,
		SYSTEM, WinApi, Services, WinGui, WinKernel,
		Kernel, Meta, Files,
		HostFiles := WinFiles,
		Ports,
		StdRegistry,
		HostPorts := WinPorts,
		Properties, WinDialog, WinClipboard, StdDialog, 
		Views, Controllers, Containers, Dialog, Converters, Documents, Windows,
		HostMechanisms := WinMechanisms (* don't remove *);

	CONST
		inPlace* = 31;	(* flag for in place windows *)
		untitledKey = "#System:untitled";
		allocKey = "#System:AllocatedMemory";
		totalKey = "#System:Total";
		byteKey = "#System:Bytes";
		iClose = 100;	(* known in HostMenus *)
		scrollRange = 16384;
		stdBorderW = 5 * Ports.point;
		guardCheck = 4;
		useSeparators = TRUE; noSeparators = FALSE; 	(* parameters to be used with AppendInt *)

		ENTER = 0DX; ESC = 1BX;
		TAB = 09X; LTAB = 0AX; RDEL = 07X; LDEL = 08X;
		PL = 10X; PR = 11X; PU = 12X; PD = 13X;
		DL = 14X; DR = 15X; DU = 16X; DD = 17X;
		AL = 1CX; AR = 1DX; AU = 1EX; AD = 1FX;

		debug = FALSE;

	TYPE
		Hook* = POINTER TO ABSTRACT RECORD END;

		Window* = POINTER TO EXTENSIBLE RECORD (Windows.Window)
			hook*: Hook;
			wnd-: WinApi.HANDLE;
			child-: BOOLEAN;	(* if window is a child of the application window *)
			dlg: BOOLEAN;	(* if window has a 3d dialog border *)
			fix: BOOLEAN;	(* window is opened with fix coordinates *)
			next: Window;	(* window ring, to prevent garbage collection of windows *)
			trapped: BOOLEAN;	(* if window caused trap, it won't get idle messages anymore *)
			setup: BOOLEAN;
			dirty: BOOLEAN;	(* dirty mark shown *)
			used: BOOLEAN;	(* window received at least on message *)
			dw, dh: INTEGER;	(* window size correction for tools *)
			mw, mh: INTEGER;	(* max window size *)
			destroyed: BOOLEAN;
			minimized: BOOLEAN;
			wheelPos: INTEGER;
			title: Views.Title
		END;

		Directory* = POINTER TO EXTENSIBLE RECORD (Windows.Directory)
			unit*: INTEGER;
			invisible*: BOOLEAN;
			unmoveable*: BOOLEAN;
			background*: BOOLEAN
		END;

		ScrollInfo* = RECORD [1]
			size*: INTEGER;
			mask*: SET;
			min*, max*, page*, pos*: INTEGER;
			trackPos*: INTEGER
		END;


	VAR
		noAppWin*: BOOLEAN; (* If true only tool windows are shown *)
		noClientScroll*: BOOLEAN; (* If true the client window of the application window doesn't display scroll bars*)
		fullSize*: BOOLEAN; (* If true the client window of the application window doesn't display scroll bars*)
		dir-: Directory;
		main-, client-, status-: WinApi.HANDLE;	(* main windows *)
		unit-: INTEGER;	(* resolution of main window *)
		ctl3d-: WinApi.HANDLE;
		scW-, scH-: INTEGER;	(* screen width and height *)
		mainW-, mainH-: INTEGER;	(* main window client area size *)
		mainHook*: Hook;
		
		tWindow, fWindow: Window;	(* target and front focus windows *)
		aWindow: Window;	(* activated child window *)
		newNumber: INTEGER;	(* number for next untitled document *)
		winAnchor: Window;	(* list of all windows, from top to bottom, first is dumy header *)
		bgWindow: Window; 	(* current background window, if any *)
		whiteBrush, nullPen: WinApi.HANDLE;
		docIcon, dirtyIcon: WinApi.HANDLE;
		instance: WinApi.HANDLE;	(* application instance *)
		mpid: INTEGER;	(* main window process id *)
		cbViewer: Window;	(* pseudo window for clipboard drawing *)
		cbValid: BOOLEAN;	(* clipboard contents is valid *)
		mainActive: BOOLEAN;	(* main is active window *)
		activating: BOOLEAN;	(* used for mouse window activation *)
		(* actMod: Kernel.Module; *)

		font, info: WinApi.HANDLE;
		statusH, alloc, total: INTEGER;
		allocStr, totalStr, byteStr: ARRAY 256 OF CHAR;
		idleTraped: BOOLEAN;
		showState: INTEGER;	(* show command for main window *)

		lBorder, tBorder, rBorder, bBorder: INTEGER;	(* space for tools in main window *)

		hookDocWinHandler*: PROCEDURE (
			wnd: WinApi.HANDLE; message, wParam, lParam: INTEGER; VAR res: INTEGER): BOOLEAN;
			(* hook for handling application specific messages; returns TRUE iff handled *)
		creatingDoc-: BOOLEAN; (* work-around for issue-#106 until a final solution is available;
			experiments showed that a simple view (i.e. without scrollbars) can skip Restore when this flag is TRUE *)
		
		borderW: INTEGER;
		
	PROCEDURE ScrollModPressed (): BOOLEAN;
	BEGIN
		RETURN WinApi.GetAsyncKeyState(11H) < 0
	END ScrollModPressed;

	(* auxiliary portable prcedures *)

	PROCEDURE GetSection (w: Window; focus, vertical: BOOLEAN;
								VAR size, sect, pos: INTEGER; VAR valid: BOOLEAN);
		VAR msg: Controllers.PollSectionMsg;
	BEGIN	(* portable *)
		msg.focus := focus; msg.vertical := vertical;
		msg.wholeSize := 1; msg.partSize := 0; msg.partPos := 0;
		msg.valid := FALSE; msg.done := FALSE;
		w.ForwardCtrlMsg(msg);
		IF msg.done THEN
			size := msg.wholeSize; sect := msg.partSize; pos := msg.partPos;
			IF size < 0 THEN size := 0 END;
			IF sect < 0 THEN sect := 0 ELSIF sect > size THEN sect := size END;
			IF pos > size - sect THEN pos := size - sect END;
			IF pos < 0 THEN pos := 0 END
		ELSE size := 1; sect := 0; pos := 0
		END;
		valid := msg.valid
	END GetSection;

	PROCEDURE SetOrigin (w: Window; focus, vertical: BOOLEAN; pos: INTEGER);
	(* set origin of window's view *)
		VAR msg: Controllers.ScrollMsg;
	BEGIN	(* portable *)
		msg.focus := focus; msg.vertical := vertical;
		msg.op := Controllers.gotoPos; msg.pos := pos;
		msg.done := FALSE;
		w.ForwardCtrlMsg(msg)
	END SetOrigin;

	PROCEDURE Scroll (w: Window; focus, vertical: BOOLEAN; dir: INTEGER);
	(* scroll relative, by line or page increment or decrement *)
		VAR msg: Controllers.ScrollMsg; c: Containers.Controller; v: Views.View;
	BEGIN	(* portable *)
		c := w.doc.ThisController(); v := c.ThisFocus();
		IF (v # NIL) & (v IS Containers.View) THEN
			Containers.FadeMarks(v(Containers.View).ThisController(), FALSE)
		END;
		msg.focus := focus; msg.vertical := vertical;
		msg.op := dir;
		msg.done := FALSE;
		w.ForwardCtrlMsg(msg)
	END Scroll;


	(** miscellaneous procedures **)

	PROCEDURE ActualWnd* (): WinApi.HANDLE;
	(* looks like it was only used in WinDialog; cf. comment for WinDialog.ActualWnd *)
	BEGIN
(*
		IF (fWindow # NIL) & ~fWindow.child & fWindow.used THEN RETURN fWindow.wnd
		ELSE RETURN main
		END
*)
		IF (fWindow # NIL) & ~fWindow.child & fWindow.used THEN RETURN fWindow.wnd
		ELSIF WinApi.IsWindowVisible(main) # 0 THEN RETURN main
		ELSE RETURN 0
		END
	END ActualWnd;

	PROCEDURE ThisWindow (wnd: WinApi.HANDLE): Window;
	(* determine window by its WindowPtr *)
	BEGIN
		IF wnd = cbViewer.wnd THEN RETURN cbViewer
		ELSE RETURN SYSTEM.VAL(Window, WinApi.GetWindowLongW(wnd, 0))
		END
	END ThisWindow;

	PROCEDURE AppendInt (VAR s: ARRAY OF CHAR; n: INTEGER; useSeparators: BOOLEAN);
		VAR len: INTEGER; i, j: INTEGER; d: ARRAY 32 OF CHAR;
	BEGIN
		ASSERT(n >= 0, 20);
		i := 0; REPEAT
			d[i] := CHR(30H + n MOD 10); INC(i); n := n DIV 10;
			IF useSeparators & (i MOD 4 = 3) & (n # 0) THEN d[i] := "'"; INC(i) END
		UNTIL n = 0;
		len := LEN(s) - 1;
		j := 0; WHILE s[j] # 0X DO INC(j) END;
		IF j + i < len THEN
			REPEAT DEC(i); s[j] := d[i]; INC(j) UNTIL i = 0;
			s[j] := 0X
		END
	END AppendInt;

	PROCEDURE Append (VAR s: ARRAY OF CHAR; t: ARRAY OF CHAR);
		VAR len: INTEGER; i, j: INTEGER; ch: CHAR;
	BEGIN
		len := LEN(s);
		i := 0; WHILE s[i] # 0X DO INC(i) END;
		j := 0; REPEAT ch := t[j]; s[i] := ch; INC(j); INC(i) UNTIL (ch = 0X) OR (i = len);
		s[len - 1] := 0X
	END Append;

	PROCEDURE StripTitle (VAR s: Views.Title);
		VAR i: INTEGER;
	BEGIN
		IF s[0] = "<" THEN
			i := 1; WHILE (s[i] # ">") & (s[i] # 0X) DO s[i - 1] := s[i]; INC(i) END;
			DEC(i); s[i] := 0X
		END
	END StripTitle;

	PROCEDURE GenTitle (w: Window; name: ARRAY OF CHAR; VAR title: ARRAY OF CHAR);
	(* generate window title for a document *)
		VAR newName: ARRAY 64 OF CHAR; i: INTEGER;
	BEGIN
		IF w.sub THEN title[0] := "<"; title[1] := 0X ELSE title[0] := 0X END;
		IF name # "" THEN
			i := 0;
			WHILE name[i] # 0X DO INC(i) END;
			IF (i > 4) & (name[i-4] = ".") & (CAP(name[i-3]) = "O") & (CAP(name[i-2]) = "D") & (CAP(name[i-1]) = "C")
			THEN
				name[i-4] := 0X
			END;
			Append(title, name)
		ELSE
			Dialog.MapString(untitledKey, newName);
			Append(title, newName); AppendInt(title, newNumber, noSeparators);
			INC(newNumber)
		END;
		IF w.sub THEN Append(title, ">") END
	END GenTitle;

	PROCEDURE GenPathTitle (w: Window; OUT title: ARRAY OF CHAR);
		VAR loc: Files.Locator; ch: CHAR; s1, s2: HostFiles.FullName; i, j: INTEGER;
	BEGIN
		loc := w.loc; title := "";
		WITH loc: HostFiles.Locator DO
			i := 0; ch := loc.path[0]; j := 0; s2 := "";
			WHILE ch # 0X DO
				IF (ch = "\") OR (ch = "/") THEN
					s1[j] := 0X; s2 := s1$; j := 0
				ELSE
					s1[j] := ch; INC(j)
				END;
				INC(i); ch := loc.path[i]
			END;
			s1[j] := 0X;
			IF s2 # "" THEN
				IF (CAP(s1[0]) = "M") & (CAP(s1[1]) = "O") & (CAP(s1[2]) = "D") & (s1[3] = 0X) THEN
					title := "(" + s2 + ")"
				ELSIF (CAP(s1[0])="D") & (CAP(s1[1])="O") & (CAP(s1[2])="C") & (CAP(s1[3])="U") & (s1[4] = 0X) THEN
					title := "{" + s2 + "}"
				ELSIF (CAP(s1[0])="R") & (CAP(s1[1])="S") & (CAP(s1[2])="R") & (CAP(s1[3])="C") & (s1[4] = 0X) THEN
					title := "[" + s2 + "]"
				END
			END
		ELSE
		END;
		Append(title, w.name)
	END GenPathTitle;

	PROCEDURE ThisStyle (base, flags: SET): SET;
	BEGIN
		IF ~(Windows.noHScroll IN flags) THEN INCL(base, 20) END;	(* hor scrollbar *)
		IF ~(Windows.noVScroll IN flags) THEN INCL(base, 21) END;	(* ver scrollbar *)
		IF ~(Windows.noResize IN flags) THEN INCL(base, 18) END;	(* sizing border *)
		RETURN base
	END ThisStyle;

	PROCEDURE^ (w: Window) UpdateScrollbars (focus: BOOLEAN), NEW;


	(* Hook *)

	PROCEDURE (hk: Hook) Activate* (on: BOOLEAN), NEW, ABSTRACT;
	PROCEDURE (hk: Hook) Focus* (on: BOOLEAN), NEW, ABSTRACT;
	PROCEDURE (hk: Hook) Resize* (w, h: INTEGER), NEW, ABSTRACT;


	(* Window creation *)

	PROCEDURE OpenDoc (w: Window; l, t, r, b: INTEGER; min, max: BOOLEAN);
		(* first part of Open, called from directory.open *)
		VAR dw, dh, res: INTEGER; wnd: WinApi.HANDLE; m: WinApi.MDICREATESTRUCTW; c: Containers.Controller;
	BEGIN
		ASSERT(~(Windows.isTool IN w.flags), 20);
		m.szTitle := "";
		IF Windows.isAux IN w.flags THEN m.szClass := "Oberon Aux"
		ELSE m.szClass := "Oberon Doc"
		END;
		m.hOwner := instance;
		m.x := WinApi.CW_USEDEFAULT;
		m.y := WinApi.CW_USEDEFAULT;
		m.cx := WinApi.CW_USEDEFAULT;
		m.cy := WinApi.CW_USEDEFAULT;
		IF (l >= 0) & (t >= 0) & ~((l = 0) & (t = 0) & (r = 0) & (b = 0)) THEN
			m.x := l; m.y := t;
			IF (r > l) & (b > t) THEN
				m.cx := r - l; m.cy := b - t; w.fix := TRUE
			END
		END;
(*
		IF (l < r) & (t < b) THEN
			m.x := l; m.w := r - l; m.y := t; m.h := b - t; w.fix := TRUE
		ELSE
			m.x := WinApi.CWUseDefault;
			m.y := WinApi.CWUseDefault;
			m.w := WinApi.CWUseDefault;
			m.h := WinApi.CWUseDefault
		END;
*)
		m.style := ThisStyle({}, w.flags);
		IF ~(Windows.noResize IN w.flags) THEN
			IF max THEN INCL(m.style, 24) END	(* maximized *)
(*
			IF min THEN INCL(m.style, 29) END	(* minimized *)
*)
		END;
		m.lParam := SYSTEM.VAL(INTEGER, w);
		w.child := TRUE;

		c := w.doc.ThisController();
		IF dir.background THEN
			w.doc.PollRect(l, t, r, b);
			dw := (r - l ) DIV w.frame.unit; dh := (b - t) DIV w.frame.unit; (* TODO: Correct this *)
			IF Documents.winWidth IN c.opts THEN m.cx := mainW ELSE m.cx := dw END;
			IF Documents.winHeight IN c.opts THEN m.cy := mainH ELSE m.cy := dh END;
			m.x := 0; m.y := 0;
			w.fix := TRUE;
			m.style := {}
		END;
		wnd := WinApi.SendMessageW(client, WinApi.WM_MDICREATE, 0, SYSTEM.ADR(m));
		IF dir.background THEN
			res := WinApi.SetWindowPos(
				wnd, WinApi.HWND_BOTTOM, 0, 0, 0, 0, WinApi.SWP_NOSIZE + WinApi.SWP_NOMOVE)
		END;
		IF ~(Windows.noResize IN w.flags) THEN
(*
			IF max THEN res := WinApi.ShowWindow(wnd, 3) END;	(* maximized *)
*)
			IF min THEN res := WinApi.ShowWindow(wnd, 6) END	(* minimized *)
		END;
		c.SetFocus(w.doc.ThisView());
		res := WinApi.SetFocus(wnd);
		res := WinApi.UpdateWindow(wnd)
	END OpenDoc;

	PROCEDURE CreateDoc (wnd: WinApi.HANDLE; wParam, lParam: INTEGER);
		(* second part of Open, called from window handler *)
		VAR res, cw, ch, u, dl, dt, dr, db: INTEGER; w: Window; style, f: SET; v: Views.View; col: Ports.Color;
			dc: WinApi.HANDLE; rect, crect: WinApi.RECT; c: Containers.Controller;
			cs: WinApi.PtrCREATESTRUCTW; mdics: WinApi.PtrMDICREATESTRUCTW;
	BEGIN
		cs := SYSTEM.VAL(WinApi.PtrCREATESTRUCTW, lParam);
		mdics := SYSTEM.VAL(WinApi.PtrMDICREATESTRUCTW, cs.lpCreateParams);
		w := SYSTEM.VAL(Window, mdics.lParam);
		res := WinApi.SetWindowLongW(wnd, 0, SYSTEM.VAL(INTEGER, w));
		w.wnd := wnd;
		dc := WinApi.GetDC(wnd);
		w.port(HostPorts.Port).SetDC(dc, wnd);
		IF ~(Windows.noHScroll IN w.flags) THEN
			res := WinApi.SetScrollRange(wnd, WinApi.SB_HORZ, 0, scrollRange, 1)
		END;
		IF ~(Windows.noVScroll IN w.flags) THEN
			res := WinApi.SetScrollRange(wnd, WinApi.SB_VERT, 0, scrollRange, 1)
		END;
		v := w.doc.ThisView(); f := {};
		WITH v: Containers.View DO
			c := v.ThisController();
			IF c # NIL THEN f := c.opts END
		ELSE
		END;
		col := Views.transparent; v.GetBackground(col);
		w.dlg := ({Containers.noCaret, Containers.noSelection} - f = {})	(* mask mode *)
				& (col = Ports.dialogBackground);	(* dialog background *)

		style := BITS(WinApi.GetWindowLongW(wnd, -16));
		style := ThisStyle(style - {20, 21, 18}, w.flags);
		IF Windows.noResize IN w.flags THEN
			style := style - (WinApi.WS_THICKFRAME + WinApi.WS_MAXIMIZEBOX) + WinApi.WS_BORDER
		ELSE
			style := style + WinApi.WS_THICKFRAME
		END;

		IF dir.background THEN
			style := style - (WinApi.WS_THICKFRAME + WinApi.WS_MAXIMIZEBOX +
				 WinApi.WS_MINIMIZEBOX + WinApi.WS_BORDER + WinApi.WS_DLGFRAME);
			res := WinApi.ShowScrollBar(wnd, WinApi.SB_BOTH, WinApi.FALSE)
		END;

		res := WinApi.SetWindowLongW(wnd, -16, ORD(style));
		style := BITS(WinApi.GetWindowLongW(wnd, -20));
		IF ~dir.background THEN INCL(style, 8) ELSE EXCL(style, 8) END;	(* window edge *)
		IF w.dlg THEN (* INCL(style, 0) *) ELSE INCL(style, 9) END;	(* dialog modal frame, client edge *)

		res := WinApi.SetWindowLongW(wnd, -20, ORD(style));
		res := WinApi.SetWindowPos(w.wnd, 0, 0, 0, 0, 0, {0, 1, 2, 3, 5});
			(* no size, no move, no z order, no redraw, frame changed *)
		res := WinApi.GetClientRect(wnd, rect);
		u := w.frame.unit; cw := rect.right; ch := rect.bottom;
		w.port.SetSize(0, 0);
		w.doc.PollRect(dl, dt, dr, db);
		IF w.fix THEN
			IF dir.background THEN
				w.doc.SetRect(0, 0, dr - dl , db - dt) (* TODO: Corrct this *)
			ELSE
				IF w.dlg THEN w.doc.SetRect(0, 0, cw * u, ch * u)
				ELSE w.doc.SetRect(borderW, borderW, cw * u - borderW, ch * u - borderW)
				END
			END
		ELSIF w.dlg THEN
			cw := (dr - dl) DIV u;
			ch := (db - dt) DIV u;
			w.doc.SetRect(0, 0, dr - dl, db - dt)
		ELSE
			res := WinApi.GetClientRect(client, crect);
			cw := (dr - dl + 2 * borderW) DIV u + 1;
			ch := (db - dt + 2 * borderW) DIV u + 1;
			IF ~(Windows.noHScroll IN w.flags) & (cw > crect.right - 40) THEN cw := crect.right - 80 END;
			IF ~(Windows.noVScroll IN w.flags) & (ch > crect.bottom - 40) THEN ch := crect.bottom - 80 END;
(*
			IF cw > rect.right THEN cw := rect.right END;
			IF ch > rect.bottom THEN ch := rect.bottom END;
*)
			w.doc.SetRect(borderW, borderW, borderW + dr - dl, borderW + db - dt)
		END;
		IF cw < 0 THEN cw := 0 END;
		IF ch < 0 THEN ch := 0 END;
		w.SetSize(cw, ch);
		w.Restore(0, 0, cw, ch);
		w.Update;
		w.UpdateScrollbars(FALSE);
		IF ~w.fix THEN w.SetSize(cw, ch) END;
		res := WinApi.GetWindowRect(wnd, rect);
		w.mw := rect.right - rect.left; w.mh := rect.bottom - rect.top;
		HostMechanisms.InstallDropTarget(wnd, w);
		w.setup := TRUE
	END CreateDoc;

	PROCEDURE OpenDlg (w: Window; l, t, r, b: INTEGER; min, max: BOOLEAN);
		(* first part of Open, called from directory.open *)
		VAR res, cx, cy, cw, ch: INTEGER; wnd: WinApi.HANDLE; style: SET;
			rect: WinApi.RECT; c: Containers.Controller;
	BEGIN
		ASSERT(Windows.isTool IN w.flags, 20);
		style := ThisStyle({7, 19, 22, 23, (*25,*) 31}, w.flags);	(* dialog, sysmenu, border, clipchildren, popup *)
		w.child := FALSE; w.dlg := TRUE;
		res := WinApi.GetWindowRect(main, rect);
		cw := (rect.right - rect.left) DIV 2;
		cx := rect.left + cw DIV 2;
		ch := (rect.bottom - rect.top) DIV 2;
		cy := rect.top + ch DIV 2;
		IF (l >= 0) & (t >= 0) & ~((l = 0) & (t = 0) & (r = 0) & (b = 0)) THEN
			cx := l; cy := t;
			IF (r > l) & (b > t) THEN
				cw := r - l; ch := b - t; w.fix := TRUE
			END
		END;
(*
		IF (l < r) & (t < b) THEN
			cx := l; cw := r - l; cy := t; ch := b - t; w.fix := TRUE
		ELSE
			res := WinApi.GetWindowRect(main, rect);
			cw := (rect.right - rect.left) DIV 2;
			cx := rect.left + cw DIV 2;
			ch := (rect.bottom - rect.top) DIV 2;
			cy := rect.top + ch DIV 2
		END;
*)
		IF noAppWin THEN
			style := style + WinApi.WS_MINIMIZEBOX;
			wnd := WinApi.CreateWindowExW(WinApi.WS_EX_APPWINDOW + WinApi.WS_EX_DLGMODALFRAME,
															"Oberon Dlg", "", style, cx, cy, cw, ch,
															ActualWnd(), 0, instance, SYSTEM.VAL(INTEGER, w))
		ELSE
			wnd := WinApi.CreateWindowExW({0}, "Oberon Dlg", "", style, cx, cy, cw, ch,
															ActualWnd(), 0, instance, SYSTEM.VAL(INTEGER, w))
		END;
		res := WinApi.ShowWindow(wnd, 1);
		c := w.doc.ThisController();
		c.SetFocus(w.doc.ThisView());
		res := WinApi.SetFocus(wnd);
		res := WinApi.UpdateWindow(wnd)
	END OpenDlg;

	PROCEDURE CreateDlg (wnd: WinApi.HANDLE; wParam, lParam: INTEGER);
		(* second part of Open, called from window handler *)
		VAR res, cw, ch, dl, dt, dr, db: INTEGER; w: Window;
			dc, menu: WinApi.HANDLE; rect: WinApi.RECT;
			cs: WinApi.PtrCREATESTRUCTW;
	BEGIN
		cs := SYSTEM.VAL(WinApi.PtrCREATESTRUCTW, lParam);
		w := SYSTEM.VAL(Window, cs.lpCreateParams);
		res := WinApi.SetWindowLongW(wnd, 0, SYSTEM.VAL(INTEGER, w));
		w.wnd := wnd; w.child := FALSE; w.dlg := TRUE;
		IF ~(inPlace IN w.flags) THEN

			menu := WinApi.GetSystemMenu(wnd, 0);
			res := WinApi.RemoveMenu(menu, 0F000H, {});	(* SC_SIZE *)
			IF ~noAppWin THEN res := WinApi.RemoveMenu(menu, 0F020H, {}) END;	(* SC_MINIMIZE *)
			res := WinApi.RemoveMenu(menu, 0F030H, {});	(* SC_MAXIMIZE *)
			res := WinApi.RemoveMenu(menu, 0F120H, {});	(* SC_RESTORE *)
			IF dir.unmoveable THEN
				res := WinApi.RemoveMenu(menu, 0F010H, {})	(* SC_MOVE *)
			END;

			dc := WinApi.GetDC(wnd);
			w.port(HostPorts.Port).SetDC(dc, wnd);
			IF ~(Windows.noHScroll IN w.flags) THEN
				res := WinApi.SetScrollRange(wnd, WinApi.SB_HORZ, 0, scrollRange, 1)
			END;
			IF ~(Windows.noVScroll IN w.flags) THEN
				res := WinApi.SetScrollRange(wnd, WinApi.SB_VERT, 0, scrollRange, 1)
			END;
			res := WinApi.GetClientRect(wnd, rect);
			IF w.fix THEN
				cw := rect.right; ch := rect.bottom;
				w.doc.SetRect(0, 0, cw * w.frame.unit, ch * w.frame.unit)
			ELSE
				w.doc.PollRect(dl, dt, dr, db);
				cw := (dr - dl) DIV w.frame.unit;
				ch := (db - dt) DIV w.frame.unit;
				w.doc.SetRect(0, 0, dr - dl, db - dt)
			END;
			IF cw < 0 THEN cw := 0 END;
			IF ch < 0 THEN ch := 0 END;
			w.SetSize(cw, ch);
			w.Restore(0, 0, cw, ch);
			w.Update;
			w.UpdateScrollbars(FALSE);
			w.SetSize(cw, ch)
		END;
		HostMechanisms.InstallDropTarget(wnd, w);
		w.setup := TRUE
	END CreateDlg;



	(** Window **)

	PROCEDURE (w: Window) ForwardCtrlMsg* (VAR msg: Controllers.Message), EXTENSIBLE;
		VAR d: BOOLEAN; res: INTEGER;
	BEGIN
		IF w.frame # NIL THEN
			Views.SetRoot(w.frame, w.frame.view, w = fWindow, w.flags);
			WITH msg: Controllers.TickMsg DO
				IF ~w.trapped THEN w.trapped := TRUE; w.ForwardCtrlMsg^(msg); w.trapped := FALSE END
			ELSE
				w.ForwardCtrlMsg^(msg);
				WITH msg: Controllers.ScrollMsg DO
					w.UpdateScrollbars(FALSE)
				ELSE
				END;
				IF (w.flags * {Windows.isAux, Windows.isTool} = {}) & (w.seq # NIL) THEN
					d := ~(Windows.neverDirty IN w.flags) & w.seq.Dirty();
					IF (d # w.dirty) & (w = aWindow) THEN
						res := WinApi.SendMessageW(w.wnd, WinApi.WM_NCACTIVATE, 1, 0);
						IF WinApi.IsZoomed(w.wnd) # 0 THEN res := WinApi.DrawMenuBar(main) END
					END
				END
			END
		END
	END ForwardCtrlMsg;

	PROCEDURE (w: Window) SetSize* (width, height: INTEGER);
		VAR res, x, y, dw, dh: INTEGER; cr, wr: WinApi.RECT; p: WinApi.POINT;
	BEGIN
		IF w.port # NIL THEN
			IF (width = 0) & (height = 0) THEN
				w.minimized := TRUE
				(* no SetSize^ call *)
			ELSE
				w.minimized := FALSE;
				w.SetSize^(width, height);
				res := WinApi.GetClientRect(w.wnd, cr);
				dw := width - cr.right; dh := height - cr.bottom;
				IF ~(inPlace IN w.flags) & (w # cbViewer) & ((dw # 0) OR (dh # 0)) THEN
					res := WinApi.GetWindowRect(w.wnd, wr);
					p.x := wr.left; p.y := wr.top;
					IF ~(Windows.isTool IN w.flags) THEN res := WinApi.ScreenToClient(client, p) END;
					x := p.x; y := p.y;
					IF Windows.isTool IN w.flags THEN DEC(x, dw DIV 2); DEC(y, dh DIV 2) END;
					width := wr.right - wr.left + dw; height := wr.bottom - wr.top + dh;
					IF w.child THEN
						res := WinApi.GetClientRect(client, cr);
						dw := cr.right; dh := cr.bottom
					ELSE
						dw := scW; dh := scH
					END;
					IF x + width > dw THEN x := dw - width END;
					IF y + height > dh THEN y := dh - height END;

					IF x < 0 THEN x := 0 END;
					IF y < 0 THEN y := 0 END;
	(*
					IF x + width > dw THEN width := dw - x END;
					IF y + height > dh THEN height := dh - y END;
	*)
					res := WinApi.SetWindowPos(w.wnd, 0, x, y, width, height,
						WinApi.SWP_FRAMECHANGED + WinApi.SWP_NOZORDER + WinApi.SWP_NOACTIVATE)
				END
			END
		END
	END SetSize;

	PROCEDURE (w: Window) SetTitle2* (title: Views.Title), NEW;
	(* assign name of window, generate title out of name, and update window title bar *)
		VAR res: INTEGER; h: Window; t: ARRAY 256 OF CHAR;
	BEGIN
		ASSERT(w.wnd # 0, 20);
		StripTitle(title);
		h := w;
		REPEAT
			GenTitle(h, title, t);
			res := WinApi.SetWindowTextW(h.wnd, t);
			h := h.link(Window)
		UNTIL h = w
	END SetTitle2;

	PROCEDURE (w: Window) SetTitle* (title: Views.Title);
	BEGIN
		ASSERT(w.wnd # 0, 20);
		w.title := title; Dialog.MapString(w.title, title);
		w.SetTitle2(title)
	END SetTitle;

	PROCEDURE (w: Window) RefreshTitle* ;
		VAR title: Views.Title;
	BEGIN
		ASSERT(w.wnd # 0, 20);
		Dialog.MapString(w.title, title);
		w.SetTitle2(title)
	END RefreshTitle;

	PROCEDURE (w: Window) GetTitle* (OUT title: Views.Title);
	(* get name of window *)
		VAR res: INTEGER;
	BEGIN
		ASSERT(w.wnd # 0, 20);
		title := w.title;
		IF title = "" THEN
			res := WinApi.GetWindowTextW(w.wnd, title, LEN(title));
			StripTitle(title)
		END
	END GetTitle;

	PROCEDURE (w: Window) SetSpec* (loc: Files.Locator; name: Files.Name; conv: Converters.Converter);
		VAR title: Views.Title;
	BEGIN
		IF name # "" THEN
			Files.dir.GetFileName(name, Files.docType, name)
		END;
		w.SetSpec^ (loc, name, conv);
		IF (loc # NIL) & (w.wnd # 0) THEN GenPathTitle(w, title); w.SetTitle(title) END
	END SetSpec;

	PROCEDURE (w: Window) Mark (do, wk: BOOLEAN), NEW;
		VAR mark: Controllers.MarkMsg;
	BEGIN
		mark.show := do;
		mark.focus := ~wk;
		w.ForwardCtrlMsg(mark);
		Properties.IncEra
	END Mark;

	PROCEDURE (w: Window) MouseDown* (x, y, time: INTEGER; modifiers: SET);
	(* handle a mouse down event in window *)
		VAR pw, ph: INTEGER; track: Controllers.TrackMsg;
	BEGIN
		track.modifiers := modifiers;
		w.port.GetSize(pw, ph); track.x := x * w.port.unit; track.y := y * w.port.unit;
		w.ForwardCtrlMsg(track);
		Properties.IncEra
	END MouseDown;

	PROCEDURE (w: Window) KeyDown* (ch: CHAR; buttons: SET);
	BEGIN
		w.KeyDown^(ch, buttons);
		Properties.IncEra
	END KeyDown;


	PROCEDURE UpdateScrollbar (w: Window; vertical, focus: BOOLEAN);
		VAR res, size, sect, pos, type, p, q, m: INTEGER; valid: BOOLEAN; i: WinApi.SCROLLINFO;
			msg: Controllers.PollSectionMsg; f: Views.Frame;
	BEGIN
		IF w.frame = NIL THEN RETURN END;
		IF vertical THEN type := WinApi.SB_VERT ELSE type := WinApi.SB_HORZ END;
		GetSection(w, focus, vertical, size, sect, pos, valid);
		IF valid THEN
			res := WinApi.ShowScrollBar(w.wnd, type, 1);
			res := WinApi.EnableScrollBar(w.wnd, type, WinApi.ESB_ENABLE_BOTH);
			p := WinApi.MulDiv(pos, scrollRange, size - sect);
			i.cbSize := SIZE(WinApi.SCROLLINFO); i.fMask := {0, 1, 2};	(* range, page, pos *)
			res := WinApi.GetScrollInfo(w.wnd, type, 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(w.wnd, type, i, 1)
				END
			ELSIF p # WinApi.GetScrollPos(w.wnd, type) THEN
				res := WinApi.SetScrollPos(w.wnd, type, p, 1)
			END
		ELSIF ~focus THEN
			msg.focus := FALSE; msg.vertical := vertical; msg.done := FALSE;
			f := Views.ThisFrame(w.frame, w.doc.ThisView());
			IF f # NIL THEN
				Views.ForwardCtrlMsg(f, msg);
				IF msg.done THEN
					res := WinApi.ShowScrollBar(w.wnd, type, 1);
					res := WinApi.EnableScrollBar(w.wnd, type, WinApi.ESB_DISABLE_BOTH)
				ELSE res := WinApi.ShowScrollBar(w.wnd, type, 0)
				END
			ELSE res := WinApi.ShowScrollBar(w.wnd, type, 0)
			END
		END
	END UpdateScrollbar;

	PROCEDURE (w: Window) Scroll (code, p: INTEGER; focus, vertical: BOOLEAN), NEW;
		VAR res, size, sect, pos, type: INTEGER; valid, noBuf: BOOLEAN;
	BEGIN
		GetSection(w, focus, vertical, size, sect, pos, valid);
		IF valid THEN
			noBuf := HostPorts.noBuffer; HostPorts.noBuffer := TRUE;
			IF code = WinApi.SB_THUMBPOSITION THEN
				SetOrigin(w, focus, vertical, WinApi.MulDiv(p, size - sect, scrollRange))
			ELSIF WinDialog.visualScroll & (code = WinApi.SB_THUMBTRACK) THEN
				SetOrigin(w, focus, vertical, WinApi.MulDiv(p, size - sect, scrollRange));
				res := WinApi.UpdateWindow(w.wnd);
				dir.Update(w)
			ELSIF code = WinApi.SB_LINEUP THEN Scroll(w, focus, vertical, Controllers.decLine)
			ELSIF code = WinApi.SB_LINEDOWN THEN Scroll(w, focus, vertical, Controllers.incLine)
			ELSIF code = WinApi.SB_PAGEUP THEN Scroll(w, focus, vertical, Controllers.decPage)
			ELSIF code = WinApi.SB_PAGEDOWN THEN Scroll(w, focus, vertical, Controllers.incPage)
			END;
(*
			UpdateScrollbar(w, vertical, focus);
*)
			GetSection(w, focus, vertical, size, sect, pos, valid);
			IF vertical THEN type := WinApi.SB_VERT ELSE type := WinApi.SB_HORZ END;
			res := WinApi.SetScrollPos(w.wnd, type, WinApi.MulDiv(pos, scrollRange, size - sect), 1);
			dir.Update(w);
			HostPorts.noBuffer := noBuf
		END
	END Scroll;

	PROCEDURE (w: Window) UpdateScrollbars (focus: BOOLEAN), NEW;
	BEGIN
		IF (WinApi.GetAsyncKeyState(1) >= 0)
				& (WinApi.GetAsyncKeyState(4) >= 0)
				& (WinApi.GetAsyncKeyState(2) >= 0) THEN
			IF ~(Windows.noHScroll IN w.flags) THEN UpdateScrollbar(w, FALSE, focus) END;
			IF ~(Windows.noVScroll IN w.flags) THEN UpdateScrollbar(w, TRUE, focus) END
		END
	END UpdateScrollbars;

	PROCEDURE (w: Window) UpdateCursor (x, y: INTEGER; modifiers: SET), NEW;
		VAR pw, ph: INTEGER; msg: Controllers.PollCursorMsg; cur: INTEGER;
	BEGIN
		w.port.GetSize(pw, ph);
		IF ((w = fWindow) OR (w = tWindow) OR ~w.child) & (x >= 0) & (x < pw) & (y >= 0) & (y < ph) THEN
			msg.x := x * w.frame.unit; msg.y := y * w.frame.unit; msg.cursor := Ports.arrowCursor;
			msg.modifiers := modifiers;
			w.ForwardCtrlMsg(msg); cur := msg.cursor
		ELSE cur := Ports.arrowCursor
		END;
		IF cur >= 0 THEN w.frame.SetCursor(cur) END
	END UpdateCursor;

	PROCEDURE (w: Window) PutOnTop, NEW;
		VAR v: Window;
	BEGIN
		IF w # bgWindow THEN
			v := winAnchor;
			WHILE (v # NIL) & (v.next # w) DO v := v.next END;
			IF v # NIL THEN
				v.next := w.next; w.next := winAnchor.next; winAnchor.next := w
			END
		END
	END PutOnTop;

	PROCEDURE (w: Window) Close*, EXTENSIBLE;
		VAR res: INTEGER; h: Window;
	BEGIN
		ASSERT(w.frame # NIL, 20);
		IF bgWindow = w THEN bgWindow := NIL END;
		IF fWindow = w THEN
			w.Mark(FALSE, FALSE); fWindow := NIL;
			IF tWindow = w THEN tWindow := NIL END
		ELSIF tWindow = w THEN
			w.Mark(FALSE, FALSE); tWindow := NIL
		END;
(*
		(* remove all shown marks in all windows *)
		IF fWindow # NIL THEN
			ASSERT(fWindow.frame # NIL, 125); ASSERT(fWindow.wnd # 0, 126);
			mark.show := FALSE; fWindow.ForwardCtrlMsg(mark)
		END;
		tWindow := NIL; fWindow := NIL;
*)
(*
		IF w = fWindow THEN fWindow := NIL END;
		IF w = tWindow THEN tWindow := NIL END;
*)
		h := winAnchor;
		WHILE (h.next # NIL) & (h.next # w) DO h := h.next END;
		ASSERT(h.next = w, 21);
		h.next := w.next; w.next := NIL;
		HostMechanisms.RemoveDropTarget(w.wnd);
		w.Close^;
		IF ~w.destroyed THEN
			w.destroyed := TRUE;
			IF w.child THEN
				IF WinApi.IsZoomed(w.wnd) # 0 THEN
					res := WinApi.SendMessageW(client, WinApi.WM_MDIRESTORE, w.wnd, 0)
				END;
				res := WinApi.SendMessageW(client, WinApi.WM_MDIDESTROY, w.wnd, 0)
			ELSE
				res := WinApi.DestroyWindow(w.wnd)
			END;
			w.trapped := TRUE; w.wnd := 0
		END;
		ASSERT(w.frame = NIL, 60)
	END Close;


	PROCEDURE ShowMain*;
		VAR res: INTEGER; menu: WinApi.HANDLE;
	BEGIN
		IF debug THEN Log.String("show main"); Log.Ln END;
		IF fullSize THEN
			menu := WinApi.GetSystemMenu(main, 0);
			res := WinApi.RemoveMenu(menu, WinApi.SC_MOVE, {})
		END;
		res := WinApi.ShowWindow(main, showState);
		res := WinApi.UpdateWindow(main);
		showState := WinApi.SW_SHOW
	END ShowMain;


	(* Directory *)

	PROCEDURE (d: Directory) Open* (
		w: Windows.Window; doc: Documents.Document; flags: SET; name: Views.Title;
		loc: Files.Locator; fname: Files.Name; conv: Converters.Converter
	), EXTENSIBLE;
		VAR res: INTEGER; v, bg: Window; p: HostPorts.Port;
	BEGIN
		WITH w: Window DO
			(* if background then it has to be and AuxDialog *)
			IF d.background
				& ((Windows.isTool IN flags) OR ~(Windows.noHScroll IN flags) OR ~(Windows.noVScroll IN flags))
			THEN
				d.background := FALSE
			END;
			(* port allocation *)
			NEW(p);
			IF d.unit # 0 THEN p.Init(d.unit, Ports.screen) ELSE p.Init(unit, Ports.screen) END;
			(* initialization *)
			w.trapped := FALSE;
			w.Init(p);
			d.Open^(w, doc, flags, name, loc, fname, conv);
			IF w # cbViewer THEN
				IF ~d.background THEN
					w.next := winAnchor.next; winAnchor.next := w	(* new top window *)
				ELSE
					v := winAnchor;
					WHILE v.next # NIL DO v := v.next END; v.next := w; w.next := NIL;
					bg := bgWindow; bgWindow := w (* new background window *)
				END;
				IF ~(inPlace IN w.flags) THEN
					(* window creation *)
					IF Windows.isTool IN flags THEN
						OpenDlg(w, d.l, d.t, d.r, d.b, d.minimized, d.maximized)
					ELSE
						IF ~d.invisible & (WinApi.IsWindowVisible(main) = 0) THEN ShowMain END;
						OpenDoc(w, d.l, d.t, d.r, d.b, d.minimized, d.maximized)
					END;
					IF (loc # NIL) & (name = fname) THEN GenPathTitle(w, name) END;
					w.SetTitle(name);
					IF Windows.isTool IN flags THEN
						res := WinApi.SendMessageW(w.wnd, WinApi.WM_NCACTIVATE, 0, 0);
						res := WinApi.SendMessageW(w.wnd, WinApi.WM_NCACTIVATE, 1, 0)
					END;
					ASSERT(w.frame # NIL, 60)
				END
			END;
			d.l := -1; d.t := -1; d.r := -1; d.b := -1;
			d.minimized := FALSE; d.maximized := FALSE;
			d.unit := 0;
			d.unmoveable := FALSE;
			IF d.background THEN
				IF bg # NIL THEN dir.Close(bg) END;
				bgWindow := w;
				d.background := FALSE
			END
		END
	END Open;

	PROCEDURE (d: Directory) First* (): Window;
	BEGIN
		RETURN winAnchor.next
	END First;

	PROCEDURE (d: Directory) Next* (w: Windows.Window): Window;
	BEGIN
		IF w # NIL THEN RETURN w(Window).next ELSE RETURN NIL END
	END Next;

	PROCEDURE (d: Directory) New* (): Window, EXTENSIBLE;
		VAR w: Window;
	BEGIN
		NEW(w); RETURN w
	END New;

	PROCEDURE (d: Directory) Focus* (target: BOOLEAN): Window;
	BEGIN
		IF target THEN RETURN tWindow ELSE RETURN fWindow END
	END Focus;

	PROCEDURE (d: Directory) Select* (w: Windows.Window; lazy: BOOLEAN);
		VAR res: INTEGER;
	BEGIN
		WITH w: Window DO
			IF ~(inPlace IN w.flags) THEN
				res := WinApi.SetForegroundWindow(main);
				IF w.child THEN res := WinApi.BringWindowToTop(main) END
			END;
			IF WinApi.IsIconic(w.wnd) # 0 THEN
				res := WinApi.ShowWindow(w.wnd, 9);	(* restore *)
				res := WinApi.InvalidateRect(w.wnd, NIL, 0)
			END;
			IF WinApi.GetTopWindow(main) # w.wnd THEN
				res := WinApi.BringWindowToTop(w.wnd)
			END;
(*
			res := WinApi.UpdateWindow(w.wnd);
*)
			res := WinApi.SetFocus(w.wnd)
		END
	END Select;

	PROCEDURE (d: Directory) GetThisWindow* (p: Ports.Port; px, py: INTEGER;
																		OUT x, y: INTEGER; OUT w: Windows.Window);
		VAR res, wpid: INTEGER; wnd: WinApi.HANDLE; pt: WinApi.POINT; s: ARRAY 32 OF CHAR;
	BEGIN
		pt.x := px; pt.y := py;
		IF p # NIL THEN
			wnd := p(HostPorts.Port).wnd;
			res := WinApi.ClientToScreen(wnd, pt)
		END;
		wnd := WinApi.WindowFromPoint(pt);
		res := WinApi.GetWindowThreadProcessId(wnd, wpid);
		IF (wnd # 0) & (wpid = mpid) THEN
			res := WinApi.GetClassNameW(wnd, s, LEN(s));
			IF (s = "Oberon Doc") OR (s = "Oberon Aux") OR (s = "Oberon Dlg") THEN
				res := WinApi.ScreenToClient(wnd, pt);
				x := pt.x; y := pt.y; w := ThisWindow(wnd)
			ELSE
				w := NIL
			END
		ELSE
			w := NIL
		END
	END GetThisWindow;

	PROCEDURE (d: Directory) Close* (w: Windows.Window);
		VAR v, u: Windows.Window; h: Window;
	BEGIN
		h := winAnchor; WHILE (h.next # NIL) & (h.next # w) DO h := h.next END;
		IF h.next = w THEN
			IF ~w.sub THEN
				v := w.link;
				WHILE v # w DO u := v.link; v.Close; v := u END
			END;
			w.Close
		END
	END Close;

	PROCEDURE (d: Directory) GetBounds* (OUT w, h: INTEGER);
		VAR rect: WinApi.RECT; res: INTEGER;
	BEGIN
		res := WinApi.GetClientRect(client, rect);
		w := rect.right; h := rect.bottom
	END GetBounds;


	(** window handlers **)

	PROCEDURE CallHeapShow* (a: INTEGER);
		TYPE P = PROCEDURE(a: INTEGER; t: ARRAY OF CHAR);
			V = RECORD (Meta.Value) p: P END;
		VAR i: Meta.Item; ok: BOOLEAN; v: V;
	BEGIN
		Meta.Lookup("DevDebug", i);
		IF i.obj = Meta.modObj THEN i.Lookup("ShowHeapObject", i);
			IF i.obj = Meta.procObj THEN i.GetVal(v, ok);
				IF ok THEN v.p(a, "") END
			END
		END
	END CallHeapShow;

	PROCEDURE KeyModifiers (data: INTEGER): SET;
		VAR b: SET;
	BEGIN
		b := {};
		IF WinApi.GetKeyState(10H) < 0 THEN INCL(b, Controllers.extend); INCL(b, HostPorts.shift) END;
		IF WinApi.GetKeyState(11H) < 0 THEN INCL(b, Controllers.modify); INCL(b, HostPorts.ctrl) END;
		IF ODD(data DIV 20000000H) THEN INCL(b, HostPorts.alt); INCL(b, Controllers.pick) END;
		RETURN b
	END KeyModifiers;

	PROCEDURE HandleKey (wnd: WinApi.HANDLE; code, ext: INTEGER);
		VAR b: SET; w: Window; pmsg: Controllers.PollFocusMsg; scroll: BOOLEAN; c: Containers.Controller;
	BEGIN
		w := ThisWindow(wnd); b := KeyModifiers(ext); w.used := TRUE;
		scroll := ODD(WinApi.GetKeyState(91H)) (* scroll lock *);
		pmsg.focus := NIL; w.ForwardCtrlMsg(pmsg);
		IF (pmsg.focus # NIL) & (pmsg.focus.view IS Containers.View) THEN
			c := pmsg.focus.view(Containers.View).ThisController();
			IF (c # NIL) & (Containers.noCaret IN c.opts) THEN scroll := TRUE END
		END;
		CASE code OF
		| 2EH: w.KeyDown(RDEL, b)	(* delete -> right delete *)
		| 08H: w.KeyDown(LDEL, b)	(* backspace -> left delete *)
		| 09H: (* tab *)
			IF Controllers.extend IN b THEN w.KeyDown(LTAB, b)	(* left tab *)
			ELSE w.KeyDown(TAB, b)	(* right tab *)
			END
		| 0DH: w.KeyDown(ENTER, b)	(* enter *)
		| 1BH: w.KeyDown(ESC, b)	(* escape *)
		| 21H:	(* page up *)
			IF scroll THEN
				w.Scroll(WinApi.SB_PAGEUP, 0, TRUE, ~(Controllers.modify IN b))
			ELSIF Controllers.modify IN b THEN (* move caret left one page *)
				w.KeyDown(PL, b - {Controllers.modify})
			ELSE (* move caret up one page *)
				w.KeyDown(PU, b)
			END
		| 22H: (* page down *)
			IF scroll THEN
				w.Scroll(WinApi.SB_PAGEDOWN, 0, TRUE, ~(Controllers.modify IN b))
			ELSIF Controllers.modify IN b THEN (* move caret right one page *)
				w.KeyDown(PR, b - {Controllers.modify})
			ELSE (* move caret down one page *)
				w.KeyDown(PD, b)
			END
		| 23H: (* end *)
			IF scroll THEN
				w.Scroll(WinApi.SB_THUMBPOSITION, scrollRange, TRUE, Controllers.modify IN b)
			ELSIF Controllers.modify IN b THEN (* move caret to doc end *)
				w.KeyDown(DD, b - {Controllers.modify})
			ELSE (* move caret to line end *)
				w.KeyDown(DR, b)
			END
		| 24H: (* home *)
			IF scroll THEN
				w.Scroll(WinApi.SB_THUMBPOSITION, 0, TRUE, Controllers.modify IN b)
			ELSIF Controllers.modify IN b THEN (* move caret to doc start *)
				w.KeyDown(DU, b - {Controllers.modify})
			ELSE (* move caret to line start *)
				w.KeyDown(DL, b)
			END
		| 25H: (* left *)
			IF scroll THEN
				w.Scroll(WinApi.SB_LINEUP, 0, TRUE, FALSE)
			ELSE
				w.KeyDown(AL, b)
			END
		| 26H: (* up *)
			IF scroll THEN
				w.Scroll(WinApi.SB_LINEUP, 0, TRUE, TRUE)
			ELSE
				w.KeyDown(AU, b)
			END
		| 27H: (* right *)
			IF scroll THEN
				w.Scroll(WinApi.SB_LINEDOWN, 0, TRUE, FALSE)
			ELSE
				w.KeyDown(AR, b)
			END
		| 28H: (* down *)
			IF scroll THEN
				w.Scroll(WinApi.SB_LINEDOWN, 0, TRUE, TRUE)
			ELSE
				w.KeyDown(AD, b)
			END
		ELSE
		END;
		Properties.IncEra
	END HandleKey;

	PROCEDURE HandleChar (wnd: WinApi.HANDLE; wParam, lParam: INTEGER);
		VAR w: Window; mod: SET;
	BEGIN
		IF (wParam >= 20H) & (wParam # 7FH) THEN
			w := ThisWindow(wnd); mod := KeyModifiers(lParam); w.used := TRUE;
			IF WinApi.VkKeyScanW(CHR(wParam)) >= 4 * 256 THEN mod := mod - {HostPorts.alt, Controllers.pick} END;
			w.KeyDown(CHR(wParam), mod)
		END
	END HandleChar;

	PROCEDURE HandleMouse (wnd: WinApi.HANDLE; wParam, lParam: INTEGER);
		VAR w: Window; isDown: BOOLEAN; x, y: INTEGER; b: SET; f, g: Views.Frame;
	BEGIN
		w := ThisWindow(wnd); b := {}; w.used := TRUE;
		IF ODD(wParam) THEN INCL(b, HostPorts.left) END;
		IF ODD(wParam DIV 16) THEN INCL(b, HostPorts.middle); INCL(b, Controllers.pick) END;
		IF ODD(wParam DIV 2) THEN INCL(b, HostPorts.right); INCL(b, Controllers.popup) END;
		isDown := b # {};
		IF ODD(wParam DIV 4) THEN INCL(b, HostPorts.shift); INCL(b, Controllers.extend) END;
		IF ODD(wParam DIV 8) THEN INCL(b, HostPorts.ctrl); INCL(b, Controllers.modify) END;
		IF WinApi.GetAsyncKeyState(12H) < 0 THEN INCL(b, HostPorts.alt); INCL(b, Controllers.pick) END;
		IF ODD(wParam DIV 128) THEN INCL(b, Controllers.doubleClick) END;
		x := lParam MOD 65536;
		y := lParam DIV 65536;
		HostPorts.SetMouseState(x, y, b, isDown);
		IF wParam DIV 256 = 1 THEN
			IF {HostPorts.middle, HostPorts.shift, HostPorts.ctrl} - b = {} THEN
				CallHeapShow(SYSTEM.VAL(INTEGER, w))
			ELSIF {HostPorts.middle, HostPorts.shift, HostPorts.alt} - b = {} THEN
				f := w.frame; x := x * f.unit; y := y * f.unit;
				REPEAT g := f; f := Views.FrameAt(g, x - g.gx, y - g.gy) UNTIL f = NIL;
				CallHeapShow(SYSTEM.VAL(INTEGER, g))
			ELSIF ~activating THEN
				w.MouseDown(x, y, 0, b)
			END
		ELSIF wParam DIV 256 = 2 THEN
			w.UpdateCursor(x, y, b)
		END;
		IF ~isDown THEN activating := FALSE END;
		Properties.IncEra
	END HandleMouse;

	PROCEDURE DeactivateWin (wnd: WinApi.HANDLE; wParam, lParam: INTEGER);
		VAR w: Window;
	BEGIN
		w := ThisWindow(wnd);
		IF fWindow = w THEN
			w.Mark(FALSE, TRUE); fWindow := NIL;
			IF (inPlace IN w.flags) OR ~(Windows.isTool IN w.flags) THEN
				w.Mark(TRUE, TRUE);
				IF (w # aWindow) & ~(inPlace IN w.flags) THEN tWindow := NIL END
			END
		END
	END DeactivateWin;

	PROCEDURE ActivateWin (wnd: WinApi.HANDLE; wParam, lParam: INTEGER);
		VAR res: INTEGER; w: Window;
	BEGIN
		w := ThisWindow(wnd);
		IF fWindow # w THEN
			IF fWindow # NIL THEN
				DeactivateWin(fWindow.wnd, 0, 0)
			END;
			w.PutOnTop;
			res := WinApi.UpdateWindow(wnd);
			IF (inPlace IN w.flags) OR ~(Windows.isTool IN w.flags) THEN
				w.Mark(FALSE, TRUE);
				tWindow := w
			END;
			fWindow := w; w.Mark(TRUE, TRUE);
			Properties.IncEra;
(*
			Dialog.CheckGuards
*)
			Dialog.Notify(0, 0, {guardCheck})
		END
	END ActivateWin;

	PROCEDURE ActivateWindow* (w: Windows.Window; do: BOOLEAN);
	BEGIN
		IF debug THEN
			IF do THEN Log.String("Activate ") ELSE Log.String("Deactivate ") END;
			Log.IntForm(SYSTEM.VAL(INTEGER, w), 16, 8, "0", FALSE);
			Log.Char(" ");
			Log.IntForm(SYSTEM.VAL(INTEGER, fWindow), 16, 8, "0", FALSE);
			IF inPlace IN w.flags THEN Log.String(" ip") END;
			IF Windows.isTool IN w.flags THEN Log.String(" tool") END;
			Log.Ln
		END;

		IF do THEN ActivateWin(w(Window).wnd, 0, 0)
		ELSE DeactivateWin(w(Window).wnd, 0, 0)
		END
	END ActivateWindow;

	PROCEDURE SizeWin (wnd: WinApi.HANDLE; w, h: INTEGER);
		VAR v: Window;
	BEGIN
		v := ThisWindow(wnd);
		IF ~(inPlace IN v.flags) THEN
			v.SetSize(w, h)
		END;
		IF v.hook # NIL THEN v.hook.Resize(w, h) END
	END SizeWin;

	PROCEDURE ScrollWin (wnd: WinApi.HANDLE; wParam, vertical: INTEGER);
		VAR w: Window;
	BEGIN
		w := ThisWindow(wnd);
		IF ~activating THEN w.Scroll(wParam MOD 65536, wParam DIV 65536, ScrollModPressed(), vertical # 0)
		END
	END ScrollWin;

	PROCEDURE WheelScroll (wnd: WinApi.HANDLE; wParam, lParam: INTEGER);
		VAR w: Window; res, lines, delta, keys: INTEGER; msg: Controllers.WheelMsg;
			p: WinApi.POINT; dx: INTEGER;
	BEGIN
		delta := wParam DIV 10000H; keys := wParam MOD 10000H;
		w := ThisWindow(wnd);
		IF ~({Windows.isTool, Windows.noResize} / w.flags = {}) & ODD(wParam DIV 8) THEN
			dx := unit DIV 10;
			IF wParam < 0 THEN ELSIF w.port.unit > dx THEN dx := -dx ELSE dx := 0 END;
			w.port.Init(w.port.unit + dx, Ports.IsPrinterPort(w.port));
			w.frame.ConnectTo(w.port);
			Views.AdaptRoot(w.frame);
			Views.Update(w.doc, Views.rebuildFrames);
			RETURN
		END;
		
		lines := 1;
		(*
		res := WinApi.SystemParametersInfoW(104 (*SPI_GETWHEELSCROLLLINES*), 0, SYSTEM.ADR(lines), 0);
		*)
		p.x := lParam MOD 65536; p.y := lParam DIV 65536;
		res := WinApi.ScreenToClient(wnd, p);
		msg.x := p.x * w.port.unit; msg.y := p.y * w.port.unit;
		msg.nofLines := 0; msg.op := -1;
		IF lines > 10 THEN	(* scroll pages *)
			INC(w.wheelPos, delta);
			IF w.wheelPos >= 120 THEN
				msg.op := Controllers.decPage;
				DEC(w.wheelPos, 120)
			ELSIF w.wheelPos <= -120 THEN
				msg.op := Controllers.incPage;
				INC(w.wheelPos, 120)
			END
		ELSIF lines > 0 THEN
			INC(w.wheelPos, delta * lines);
			WHILE w.wheelPos >= 120 DO
				msg.op := Controllers.decLine;
				INC(msg.nofLines);
				DEC(w.wheelPos, 120)
			END;
			WHILE w.wheelPos <= -120 DO
				msg.op := Controllers.incLine;
				INC(msg.nofLines);
				INC(w.wheelPos, 120)
			END
		END;
		msg.done := FALSE;
		IF msg.op >= 0 THEN w.ForwardCtrlMsg(msg) END;
		IF ~msg.done THEN (* scroll document *)
			CASE msg.op OF
			| Controllers.decPage:
				w.Scroll(WinApi.SB_PAGEUP, 0, ODD(keys DIV 8), ~ODD(keys DIV 4))
			| Controllers.incPage:
				w.Scroll(WinApi.SB_PAGEDOWN, 0, ODD(keys DIV 8), ~ODD(keys DIV 4))
			| Controllers.decLine:
				WHILE msg.nofLines > 0 DO
					w.Scroll(WinApi.SB_LINEUP, 0, ODD(keys DIV 8), ~ODD(keys DIV 4));
					DEC(msg.nofLines)
				END
			| Controllers.incLine:
				WHILE msg.nofLines > 0 DO
					w.Scroll(WinApi.SB_LINEDOWN, 0, ODD(keys DIV 8), ~ODD(keys DIV 4));
					DEC(msg.nofLines)
				END
			ELSE
			END
(*
			IF lines > 10 THEN	(* scroll pages *)
				INC(w.wheelPos, delta);
				IF w.wheelPos >= 120 THEN
					w.Scroll(WinApi.SBPageUp, 0, ODD(keys DIV 8), ~ODD(keys DIV 4));
					DEC(w.wheelPos, 120)
				ELSIF w.wheelPos <= -120 THEN
					w.Scroll(WinApi.SBPageDown, 0, ODD(keys DIV 8), ~ODD(keys DIV 4));
					INC(w.wheelPos, 120)
				END
			ELSIF lines > 0 THEN
				INC(w.wheelPos, delta * lines);
				WHILE w.wheelPos >= 120 DO
					w.Scroll(WinApi.SBLineUp, 0, ODD(keys DIV 8), ~ODD(keys DIV 4));
					DEC(w.wheelPos, 120)
				END;
				WHILE w.wheelPos <= -120 DO
					w.Scroll(WinApi.SBLineDown, 0, ODD(keys DIV 8), ~ODD(keys DIV 4));
					INC(w.wheelPos, 120)
				END
			END
*)
		END
	END WheelScroll;

	PROCEDURE InvalidateWin (wnd, dc: WinApi.HANDLE; paint: INTEGER);
		TYPE RectPtr = POINTER TO WinApi.RECT;
		VAR w: Window; rp: RectPtr;
	BEGIN
		w := ThisWindow(wnd);
		w.port(HostPorts.Port).SetDC(dc, wnd);
		rp := SYSTEM.VAL(RectPtr, paint);
		w.Restore(rp.left, rp.top, rp.right, rp.bottom)
	END InvalidateWin;

	PROCEDURE PaintWin (wnd: WinApi.HANDLE; a, b: INTEGER);
		VAR w: Window;
	BEGIN
		w := ThisWindow(wnd);
		w.Update
	END PaintWin;

	PROCEDURE [2] DocWinHandler (wnd: WinApi.HANDLE; message, wParam, lParam: INTEGER): INTEGER;
		VAR res, x, dx, dy: INTEGER; w: Window; ps: WinApi.PAINTSTRUCT;
			(* pm: WinApi.PMinMaxInfo; *)
			rec: WinApi.RECT; style, st: SET; wp: WinApi.WINDOWPOS;
	BEGIN
		Controllers.SetCurrentPath(Controllers.targetPath);
		IF (hookDocWinHandler # NIL) & hookDocWinHandler(wnd, message, wParam, lParam, res) THEN
			Controllers.ResetCurrentPath();
			RETURN res
		END;
		CASE message OF
		| WinApi.WM_CREATE:
			creatingDoc := TRUE;
			Kernel.Try(CreateDoc, wnd, wParam, lParam);
			creatingDoc := FALSE
		| WinApi.WM_CLOSE:
			res := WinApi.SendMessageW(main, WinApi.WM_COMMAND, iClose, 0);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_DESTROY:
			w := ThisWindow(wnd);
			IF ~w.destroyed THEN
				w.destroyed := TRUE;
				w.Close
			END;
			IF w = aWindow THEN aWindow := NIL END;
			IF w = tWindow THEN tWindow := NIL END
		| WinApi.WM_CHILDACTIVATE:
			w := ThisWindow(wnd);
			w.PutOnTop; aWindow := w;
			tWindow := w
		| WinApi.WM_SETFOCUS:
			w := ThisWindow(wnd);
			IF debug THEN
				Log.String("Doc: SetFocus ");
				Log.IntForm(SYSTEM.VAL(INTEGER, w), 16, 8, "0", FALSE);
				Log.Char(" ");
				Log.IntForm(SYSTEM.VAL(INTEGER, fWindow), 16, 8, "0", FALSE);
				IF inPlace IN w.flags THEN Log.String(" ip") END;
				IF Windows.isTool IN w.flags THEN Log.String(" tool") END;
				Log.Ln
			END;
			IF tWindow # w THEN activating := TRUE END;
			IF w = bgWindow THEN
				res := WinApi.SetWindowPos(wnd, WinApi.HWND_BOTTOM, 0, 0, 0, 0,
					WinApi.SWP_NOSIZE + WinApi.SWP_NOMOVE + WinApi.SWP_NOREDRAW)
			END;
			Kernel.Try(ActivateWin, wnd, wParam, lParam);
			IF w.hook # NIL THEN w.hook.Focus(TRUE) END
		| WinApi.WM_KILLFOCUS:
			w := ThisWindow(wnd);
			IF debug THEN
				Log.String("Doc: KillFocus ");
				Log.IntForm(SYSTEM.VAL(INTEGER, w), 16, 8, "0", FALSE);
				Log.Char(" ");
				Log.IntForm(SYSTEM.VAL(INTEGER, fWindow), 16, 8, "0", FALSE);
				IF inPlace IN w.flags THEN Log.String(" ip") END;
				IF Windows.isTool IN w.flags THEN Log.String(" tool") END;
				Log.Ln
			END;
			IF wParam # main THEN Kernel.Try(DeactivateWin, wnd, wParam, lParam) END;
			IF w.hook # NIL THEN w.hook.Focus(FALSE) END
		| WinApi.WM_SIZE:
			w := ThisWindow(wnd);
			IF (w = bgWindow) & ((wParam = WinApi.SIZE_MAXHIDE) OR (wParam = WinApi.SIZE_MAXSHOW)) THEN
				Controllers.ResetCurrentPath();
				RETURN 0
			ELSE
				Kernel.Try(SizeWin, wnd, lParam MOD 65536, lParam DIV 65536 MOD 65536)
			END
		| WinApi.WM_HSCROLL:
			Kernel.Try(ScrollWin, wnd, wParam, 0);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_VSCROLL:
			Kernel.Try(ScrollWin, wnd, wParam, 1);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_MOUSEWHEEL:
			Kernel.Try(WheelScroll, wnd, wParam, lParam);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_PAINT:
			w := ThisWindow(wnd);
			w.GetSize(dx, dy);
			IF ~w.minimized THEN
				(* clip region must be reset to ensure correct ps.paint ! *)
				res := WinApi.SelectClipRgn(w.port(HostPorts.Port).dc, 0);
				x := WinApi.BeginPaint(w.wnd, ps);
				Kernel.Try(InvalidateWin, wnd, x, SYSTEM.ADR(ps.rcPaint));
				res := WinApi.EndPaint(w.wnd, ps);
				Kernel.Try(PaintWin, wnd, 0, 0);
				Controllers.ResetCurrentPath();
				RETURN 0
			END
		| WinApi.WM_LBUTTONDOWN, WinApi.WM_RBUTTONDOWN, WinApi.WM_MBUTTONDOWN:
			IF wnd # WinApi.GetFocus() THEN
				res := WinApi.SendMessageW(client, WinApi.WM_MDIACTIVATE, wnd, 0);
				res := WinApi.SetFocus(wnd)
			END;
			res := WinApi.SetCapture(wnd);
			Kernel.Try(HandleMouse, wnd, wParam + 256, lParam);
			IF WinApi.GetCapture() = wnd THEN res := WinApi.ReleaseCapture() END;
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_LBUTTONDBLCLK, WinApi.WM_RBUTTONDBLCLK, WinApi.WM_MBUTTONDBLCLK:
			res := WinApi.SetCapture(wnd);
			Kernel.Try(HandleMouse, wnd, wParam + (128 + 256), lParam);
			IF WinApi.GetCapture() = wnd THEN res := WinApi.ReleaseCapture() END;
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_LBUTTONUP, WinApi.WM_RBUTTONUP, WinApi.WM_MBUTTONUP:
			Kernel.Try(HandleMouse, wnd, wParam, lParam);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_MOUSEMOVE:
			Kernel.Try(HandleMouse, wnd, wParam + (2 * 256), lParam);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_KEYDOWN:
			Kernel.Try(HandleKey, wnd, wParam, lParam);
			IF (wParam = WinApi.VK_TAB)
				OR (wParam = WinApi.VK_LEFT) OR (wParam = WinApi.VK_UP)
				OR (wParam = WinApi.VK_RIGHT) OR (wParam = WinApi.VK_DOWN)
			THEN
				res := WinApi.SendMessageW(wnd, 0127H (* WM_CHANGEUISTATE *),
							2 (* UIS_CLEAR *) + 10000H (* UISF_HIDEFOCUS *), 0)
			END
		| WinApi.WM_SYSKEYDOWN:
			IF wParam = WinApi.VK_MENU THEN
				res := WinApi.SendMessageW(wnd, 0127H (* WM_CHANGEUISTATE *),
							2 (* UIS_CLEAR *) + 10000H (* UISF_HIDEFOCUS *) + 20000H (* UISF_HIDEACCEL *), 0)
			END
		| WinApi.WM_CHAR:
			Kernel.Try(HandleChar, wnd, wParam, lParam);
			Controllers.ResetCurrentPath();
			RETURN 0
(*
		| WinApi.WM_UNICHAR:
			IF wParam = WinApi.UNICODE_NOCHAR THEN RETURN 1 END;
			Kernel.Try(HandleChar, wnd, wParam, lParam);
			Controllers.ResetCurrentPath();
			RETURN 0
*)
		| WinApi.WM_NCLBUTTONDOWN:
			IF activating & ((wParam = 6) OR (wParam = 7) OR (wParam = 20)) THEN	(* in scrollbar, close *)
				Controllers.ResetCurrentPath();
				RETURN 0
			END
		| WinApi.WM_NCLBUTTONUP, WinApi.WM_NCRBUTTONUP:
			activating := FALSE
		| WinApi.WM_MDIACTIVATE:
			w := ThisWindow(wnd);
			IF w.hook # NIL THEN w.hook.Activate(wnd = lParam) END
		| WinApi.WM_NCACTIVATE:
			w := ThisWindow(wnd);
			IF (w = bgWindow) OR ((bgWindow # NIL) & (wParam MOD 65536 = WinApi.FALSE)) THEN
				res := WinApi.SetWindowPos(bgWindow.wnd, WinApi.HWND_BOTTOM, 0, 0, 0, 0,
					WinApi.SWP_NOSIZE + WinApi.SWP_NOMOVE + WinApi.SWP_NOACTIVATE)
			END;
			style := BITS(WinApi.GetWindowLongW(wnd, -16));
			IF wParam MOD 65536 # 0 THEN
				IF ~mainActive & (w # tWindow) & (tWindow # NIL) THEN
					st := BITS(WinApi.GetWindowLongW(tWindow.wnd, -16));
					st := st - {16, 17, 19};	(* minimize box, maximize box, sysmenu *)
					res := WinApi.SetWindowLongW(tWindow.wnd, -16, ORD(st));
					res := WinApi.GetWindowRect(tWindow.wnd, rec);
					x := WinApi.CreateRectRgnIndirect(rec);
					res := WinApi.SendMessageW(tWindow.wnd, WinApi.WM_NCPAINT, x, 0)
				END;
				IF (w.flags * {Windows.isAux, Windows.isTool} = {}) THEN
					IF ~(Windows.neverDirty IN w.flags) & w.seq.Dirty() THEN
						res := WinApi.SetClassLongW(wnd, -14, dirtyIcon); w.dirty := TRUE
					ELSE
						res := WinApi.SetClassLongW(wnd, -14, docIcon); w.dirty := FALSE
					END
				END;
				style := style + WinApi.WS_MINIMIZEBOX + WinApi.WS_SYSMENU;
				IF ~(Windows.noResize IN w.flags) THEN style := style + WinApi.WS_MAXIMIZEBOX END
			ELSIF mainActive THEN
				style := style - {16, 17, 19}	(* minimize box, maximize box, sysmenu *)
			END;
			res := WinApi.SetWindowLongW(wnd, -16, ORD(style))
		| WinApi.WM_WINDOWPOSCHANGED:
			w := ThisWindow(wnd);
			wp := SYSTEM.VAL(WinApi.WINDOWPOS, lParam);
			IF (w = bgWindow) & (wp.hwndInsertAfter # WinApi.HWND_BOTTOM) THEN
				res := WinApi.SetWindowPos(wnd, 1 (* HWND_BOTTOM *), 0, 0, 0, 0,
					WinApi.SWP_NOSIZE + WinApi.SWP_NOMOVE + WinApi.SWP_NOACTIVATE)
			END
(*
		| WinApi.WM_GETMINMAXINFO:
			w := ThisWindow(wnd);
			IF (w # NIL) & (w.mw > 0) & (Windows.noResize IN w.flags) THEN
				pm := SYSTEM.VAL(WinApi.PMinMaxInfo, lParam);
				pm.maxTrackSize.x := w.mw;
				pm.maxTrackSize.y := w.mh;
				Controllers.ResetCurrentPath();
				RETURN 0
			END
*)
		ELSE
		END;
		Controllers.ResetCurrentPath();
		RETURN WinApi.DefMDIChildProcW(wnd, message, wParam, lParam)
	END DocWinHandler;

	PROCEDURE [2] DialogHandler (wnd: WinApi.HANDLE; message, wParam, lParam: INTEGER): INTEGER;
		VAR res, x, dx, dy: INTEGER; w: Window; ps: WinApi.PAINTSTRUCT;
	BEGIN
		Controllers.SetCurrentPath(Controllers.targetPath);
		CASE message OF
		| WinApi.WM_CREATE:
			Kernel.Try(CreateDlg, wnd, wParam, lParam)
		| WinApi.WM_CLOSE:
			res := WinApi.SendMessageW(main, WinApi.WM_COMMAND, iClose, 0);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_DESTROY:
			w := ThisWindow(wnd);
			IF ~w.destroyed THEN
				w.destroyed := TRUE;
				w.Close
			END
		| WinApi.WM_SETFOCUS:
			w := ThisWindow(wnd);
			IF debug THEN
				Log.String("Dlg: SetFocus ");
				Log.IntForm(SYSTEM.VAL(INTEGER, w), 16, 8, "0", FALSE);
				Log.Char(" ");
				Log.IntForm(SYSTEM.VAL(INTEGER, fWindow), 16, 8, "0", FALSE);
				IF inPlace IN w.flags THEN Log.String(" ip") END;
				IF Windows.isTool IN w.flags THEN Log.String(" tool") END;
				Log.Ln
			END;
			Kernel.Try(ActivateWin, wnd, wParam, lParam);
			IF w.hook # NIL THEN w.hook.Focus(TRUE) END
		| WinApi.WM_KILLFOCUS:
			w := ThisWindow(wnd);
			IF debug THEN
				Log.String("Dlg: KillFocus ");
				Log.IntForm(SYSTEM.VAL(INTEGER, w), 16, 8, "0", FALSE);
				Log.Char(" ");
				Log.IntForm(SYSTEM.VAL(INTEGER, fWindow), 16, 8, "0", FALSE);
				IF inPlace IN w.flags THEN Log.String(" ip") END;
				IF Windows.isTool IN w.flags THEN Log.String(" tool") END;
				Log.Ln
			END;
			IF ~(inPlace IN w.flags) THEN Kernel.Try(DeactivateWin, wnd, wParam, lParam) END;
			IF w.hook # NIL THEN w.hook.Focus(FALSE) END
		| WinApi.WM_SIZE:
			w := ThisWindow(wnd);
			Kernel.Try(SizeWin, wnd, lParam MOD 65536 + w.dw, lParam DIV 65536 MOD 65536 + w.dh)
		| WinApi.WM_HSCROLL:
			Kernel.Try(ScrollWin, wnd, wParam, 0);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_VSCROLL:
			Kernel.Try(ScrollWin, wnd, wParam, 1);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_MOUSEWHEEL:
			Kernel.Try(WheelScroll, wnd, wParam, lParam);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_PAINT:
			w := ThisWindow(wnd);
			w.GetSize(dx, dy);
			IF ~w.minimized THEN
				(* clip region must be reset to ensure correct ps.paint ! *)
				res := WinApi.SelectClipRgn(w.port(HostPorts.Port).dc, 0);
				x := WinApi.BeginPaint(w.wnd, ps);
				Kernel.Try(InvalidateWin, wnd, x, SYSTEM.ADR(ps.rcPaint));
				res := WinApi.EndPaint(w.wnd, ps);
				Kernel.Try(PaintWin, wnd, 0, 0);
				Controllers.ResetCurrentPath();
				RETURN 0
			END
		| WinApi.WM_LBUTTONDOWN, WinApi.WM_RBUTTONDOWN, WinApi.WM_MBUTTONDOWN:
			res := WinApi.SetCapture(wnd);
			Kernel.Try(HandleMouse, wnd, wParam + 256, lParam);
			IF WinApi.GetCapture() = wnd THEN res := WinApi.ReleaseCapture() END;
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_LBUTTONDBLCLK, WinApi.WM_RBUTTONDBLCLK, WinApi.WM_MBUTTONDBLCLK:
			res := WinApi.SetCapture(wnd);
			Kernel.Try(HandleMouse, wnd, wParam + (128 + 256), lParam);
			IF WinApi.GetCapture() = wnd THEN res := WinApi.ReleaseCapture() END;
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_LBUTTONUP, WinApi.WM_RBUTTONUP, WinApi.WM_MBUTTONUP:
			Kernel.Try(HandleMouse, wnd, wParam, lParam);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_MOUSEMOVE:
			Kernel.Try(HandleMouse, wnd, wParam + (2 * 256), lParam);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_KEYDOWN:
			Kernel.Try(HandleKey, wnd, wParam, lParam);
			IF (wParam = WinApi.VK_TAB)
				OR (wParam = WinApi.VK_LEFT) OR (wParam = WinApi.VK_UP)
				OR (wParam = WinApi.VK_RIGHT) OR (wParam = WinApi.VK_DOWN)
			THEN
				res := WinApi.SendMessageW(wnd, 0127H (* WM_CHANGEUISTATE *),
							2 (* UIS_CLEAR *) + 10000H (* UISF_HIDEFOCUS *), 0)
			END
		| WinApi.WM_SYSKEYDOWN:
			IF wParam = WinApi.VK_MENU THEN
				res := WinApi.SendMessageW(wnd, 0127H (* WM_CHANGEUISTATE *),
							2 (* UIS_CLEAR *) + 10000H (* UISF_HIDEFOCUS *) + 20000H (* UISF_HIDEACCEL *), 0)
			END
		| WinApi.WM_CHAR, WinApi.WM_SYSCHAR:
			Kernel.Try(HandleChar, wnd, wParam, lParam);
			Controllers.ResetCurrentPath();
			RETURN 0
		| WinApi.WM_ACTIVATE:
			w := ThisWindow(wnd);
			IF w.hook # NIL THEN w.hook.Activate(wParam # 0) END
		ELSE
		END;
		Controllers.ResetCurrentPath();
		RETURN WinApi.DefWindowProcW(wnd, message, wParam, lParam)
	END DialogHandler;



	(** clipboard handling **)

	PROCEDURE OpenClipboard (doc: Documents.Document);	(* inline into Init? *)
	BEGIN
		NEW(cbViewer);
		dir.Open(cbViewer, doc, {Windows.neverDirty}, "Clipboard", NIL, "", NIL)
	END OpenClipboard;
(*
	PROCEDURE SizeClipboard* (wnd: WinApi.Handle; w, h: LONGINT);
		VAR res: LONGINT; dc: WinApi.Handle;
	BEGIN
		cbViewer.wnd := wnd;
		dc := WinApi.GetDC(wnd);
		cbViewer.port(HostPorts.Port).SetDC(dc, wnd);
(*
		res := WinApi.ShowScrollBar(wnd, WinApi.SBHorz, 1);
		res := WinApi.SetScrollRange(wnd, WinApi.SBHorz, 0, scrollRange, 1);
		res := WinApi.ShowScrollBar(wnd, WinApi.SBVert, 1);
		res := WinApi.SetScrollRange(wnd, WinApi.SBVert, 0, scrollRange, 1);
*)
		Kernel.Try(SizeWin, wnd, w, h);
(*
		cbViewer.UpdateScrollbars(FALSE);
*)
		res := WinApi.ReleaseDC(wnd, dc);
	END SizeClipboard;

	PROCEDURE PaintClipboard* (wnd: WinApi.Handle; VAR ps: WinApi.PaintStruct);
		VAR res: LONGINT; dc: WinApi.Handle;
	BEGIN
		cbViewer.wnd := wnd;
		res := WinApi.SelectClipRgn(ps.dc, 0);
		Kernel.Try(InvalidateWin, wnd, ps.dc, ADR(ps.paint));
		Kernel.Try(PaintWin, wnd, 0, 0)
	END PaintClipboard;

	PROCEDURE ScrollClipboard* (wnd: WinApi.Handle; code, pos: LONGINT; vertical: BOOLEAN);
		VAR res: LONGINT; dc: WinApi.Handle;
	BEGIN
		cbViewer.wnd := wnd;
(*
		dc := WinApi.GetDC(wnd);
		cbViewer.port(HostPorts.Port).SetDC(dc, wnd);
		cbViewer.Scroll(code, pos, vertical);
		cbViewer.SetSize(cbViewer.port.w, cbViewer.port.h);
		cbViewer.UpdateScrollbars(FALSE);
		res := WinApi.ReleaseDC(wnd, dc)
*)
	END ScrollClipboard;
*)

	(** miscellaneous **)

	PROCEDURE UpdateInfo;
		VAR res: INTEGER; str: ARRAY 256 OF CHAR;
	BEGIN
		IF Dialog.memInStatus & ((alloc # Kernel.Allocated()) (* OR (total # Kernel.Used()) *) ) THEN
			alloc := Kernel.Allocated(); total := Kernel.Used();
			str := allocStr$; AppendInt(str, alloc, useSeparators); Append(str, byteStr);
(*
			Append(str, totalStr); AppendInt(str, total, useSeparators); Append(str, byteStr);
*)
			res := WinApi.SetWindowTextW(info, str)
		END
	END UpdateInfo;
(*
	PROCEDURE Check;
		VAR i: INTEGER; s: SET; obj: Kernel.Object;
	BEGIN
		IF Documents.dispMsgHeight = 0 THEN
			IF actMod = NIL THEN actMod := Kernel.modList END;
			IF actMod.refcnt >= 0 THEN
				obj := Kernel.ThisObject(actMod, "~");
				s := BITS(actMod.csize);
				i := ORD(s / SYSTEM.ROT(s, 7) / SYSTEM.ROT(s, -8));
				IF (obj = NIL) OR (obj.fprint # i) OR (obj.offs # ADR(obj^)) THEN
					SYSTEM.PUT(ADR(Documents.dispMsgHeight), (* LONG( *)(* LONG( *)1)
				END
			END;
			actMod := actMod.next
		END
	END Check;
*)

	PROCEDURE Idle*;
		VAR w: Window; tick: Controllers.TickMsg; focus: BOOLEAN;
	BEGIN
		w := dir.Focus(FALSE);
		IF (w # NIL) & ~w.trapped THEN
			w.trapped := TRUE;
			IF w.frame # NIL THEN
				tick.tick := Services.Ticks();
				(*w.ForwardCtrlMsg(tick) ticker moved to Controllers*)
			END;
			w.trapped := FALSE
		END;
		focus := ScrollModPressed(); w := dir.First();
		WHILE w # NIL DO
			IF ~w.trapped THEN
				w.trapped := TRUE;
				w.UpdateScrollbars(focus & (w = fWindow));
				w.trapped := FALSE
			END;
			w := dir.Next(w)
		END;
		IF ~idleTraped THEN
			idleTraped := TRUE;
			IF WinApi.GetAsyncKeyState(1) >= 0 THEN activating := FALSE END;
			IF WinApi.GetAsyncKeyState(2) >= 0 THEN activating := FALSE END;
			UpdateInfo;
(*
			Check;
*)
			idleTraped := FALSE
		END;
		Services.actionHook.Step;
		Windows.dir.Update(NIL)
	END Idle;

	PROCEDURE ActivateMain* (do: BOOLEAN);
	BEGIN
		mainActive := do
	END ActivateMain;


	PROCEDURE ResizeMainWindow* (type, w, h: INTEGER);
		VAR res, m: INTEGER;
	BEGIN
		m := w DIV 2;
		mainW := w - lBorder - rBorder;
		mainH := h - tBorder - bBorder;
		IF Dialog.showsStatus THEN DEC(mainH, statusH) END;
(*
		res := WinApi.DefFrameProcW(main, client, WinApi.WMSize, type, mainW + mainH * 65536);
*)
		IF debug THEN Log.String("resize main window"); Log.Ln END;
		res := WinApi.MoveWindow(client, lBorder, tBorder, mainW, mainH, 1);
		IF Dialog.showsStatus THEN
			IF Dialog.memInStatus THEN
				res := WinApi.MoveWindow(status, 0, h - statusH, m + 1, statusH, 1);
				res := WinApi.MoveWindow(info, m, h - statusH, w - m, statusH, 1);
				res := WinApi.ShowWindow(info, 1)
			ELSE
				res := WinApi.MoveWindow(status, 0, h - statusH, w, statusH, 1);
				res := WinApi.ShowWindow(info, 0)
			END;
			res := WinApi.ShowWindow(status, 1)
		ELSE
			res := WinApi.ShowWindow(status, 0);
			res := WinApi.ShowWindow(info, 0)
		END
	END ResizeMainWindow;

	PROCEDURE SetMainBorderWidth* (l, t, r, b: INTEGER);
		VAR res: INTEGER; rect: WinApi.RECT;
	BEGIN
		lBorder := l; tBorder := t; rBorder := r; bBorder := b;
		res := WinApi.GetClientRect(main, rect);
		ResizeMainWindow(0, rect.right, rect.bottom)
	END SetMainBorderWidth;

	PROCEDURE GetMainBorder* (VAR l, t, r, b: INTEGER);
		VAR res: INTEGER; rect: WinApi.RECT;
	BEGIN
		res := WinApi.GetClientRect(main, rect);
		l := 0; t := 0; r := rect.right; b := rect.bottom;
		IF Dialog.showsStatus THEN DEC(b, statusH) END
	END GetMainBorder;

	PROCEDURE SaveWindowState*;
		VAR res: INTEGER; wp: WinApi.WINDOWPLACEMENT; list: ARRAY 10 OF INTEGER;
	BEGIN
		IF ~fullSize THEN
			wp.length := SIZE(WinApi.WINDOWPLACEMENT);
			res := WinApi.GetWindowPlacement(main, wp);
			IF res # 0 THEN
				list[0] := wp.rcNormalPosition.left;
				list[1] := wp.rcNormalPosition.top;
				list[2] := wp.rcNormalPosition.right;
				list[3] := wp.rcNormalPosition.bottom;
				list[4] := wp.ptMinPosition.x;
				list[5] := wp.ptMinPosition.y;
				list[6] := wp.ptMaxPosition.x;
				list[7] := wp.ptMaxPosition.y;
				list[8] := wp.showCmd;
				IF 1 IN wp.flags THEN list[9] := 1 ELSE list[9] := 0 END;
				StdRegistry.WriteIntList("MainWindow", list)
			ELSE
				res := WinApi.GetLastError(); HALT(120)
			END
		END
	END SaveWindowState;

	PROCEDURE LoadWindowState ();
		VAR res: INTEGER; wp: WinApi.WINDOWPLACEMENT; list: ARRAY 10 OF INTEGER;
			si: WinApi.STARTUPINFOW; rect: WinApi.RECT;
	BEGIN
		list[8] := 0; list[9] := 0;
		IF fullSize THEN
			wp.length := SIZE(WinApi.WINDOWPLACEMENT);
			wp.flags := WinApi.WPF_SETMINPOSITION + WinApi.WPF_RESTORETOMAXIMIZED;
			res := WinApi.SystemParametersInfoW(WinApi.SPI_GETWORKAREA, 0, SYSTEM.ADR(rect), 0);
			wp.rcNormalPosition.left := rect.left;
			wp.rcNormalPosition.top := rect.top;
			wp.rcNormalPosition.right := rect.right;
			wp.rcNormalPosition.bottom := rect.bottom;
			wp.ptMinPosition.x := rect.left;
			wp.ptMinPosition.y := rect.top;
			wp.ptMaxPosition.x := rect.left;
			wp.ptMaxPosition.y := rect.top;
			wp.showCmd := WinApi.SW_HIDE;
			showState := WinApi.SW_SHOW;
			res := WinApi.SetWindowPlacement(main, wp)
		ELSE
			StdRegistry.ReadIntList("MainWindow", list, res);
			IF res = 0 THEN
				wp.length := SIZE(WinApi.WINDOWPLACEMENT);
				wp.flags := {0}; 	(* set min pos *)
				wp.rcNormalPosition.left := list[0];
				wp.rcNormalPosition.top := list[1];
				wp.rcNormalPosition.right := list[2];
				wp.rcNormalPosition.bottom := list[3];
				wp.ptMinPosition.x := list[4];
				wp.ptMinPosition.y := list[5];
				IF list[8] = 0 THEN	(* v1.0..v1.1 *)
					wp.showCmd := list[6];
					wp.ptMaxPosition.x := -1;
					wp.ptMaxPosition.y := -1
				ELSE	(* v1.11.. *)
					wp.ptMaxPosition.x := list[6];
					wp.ptMaxPosition.y := list[7];
					wp.showCmd := list[8];
					IF list[9] > 0 THEN INCL(wp.flags, 1) END	(* restore maximized *)
				END;
				si.cb := SIZE(WinApi.STARTUPINFOW);
				WinApi.GetStartupInfoW(si);
				IF 0 IN si.dwFlags THEN	(* showWindow valid *)
					IF si.wShowWindow > 1 THEN wp.showCmd := si.wShowWindow
					ELSIF wp.showCmd = 2 THEN	(* restore minimized *)
						IF 1 IN wp.flags THEN wp.showCmd := 3 ELSE wp.showCmd := 9 END
					END
				END;
				showState := wp.showCmd; wp.showCmd := WinApi.SW_HIDE;
				res := WinApi.SetWindowPlacement(main, wp)
			END
		END
	END LoadWindowState;

	PROCEDURE CreateMainWindows* (menuBar, winMenu: WinApi.HANDLE; Handler: WinApi.WNDPROC);
		VAR res: INTEGER; class: WinApi.WNDCLASSW; ccs: WinApi.CLIENTCREATESTRUCT;
			dc: WinApi.HANDLE; tm: WinApi.TEXTMETRICW;
	BEGIN
		(* init window classes *)
		class.hCursor := WinApi.LoadCursorW(0, SYSTEM.VAL(WinApi.PtrWSTR, WinApi.IDC_ARROW));
		class.hIcon := WinApi.LoadIconW(instance, SYSTEM.VAL(WinApi.PtrWSTR, 1));
		IF (class.hIcon = 0) OR (class.hIcon = 1) THEN
			class.hIcon := WinApi.LoadIconW(0, SYSTEM.VAL(WinApi.PtrWSTR, WinApi.IDI_APPLICATION))
		END;
		class.lpszMenuName := NIL;
		class.lpszClassName := "Oberon App";
		class.hbrBackground := 0; (* 12 + 1; *)	(* application workspace color *)
		class.style := WinApi.CS_VREDRAW + WinApi.CS_HREDRAW + WinApi.CS_DBLCLKS + WinApi.CS_OWNDC;
		class.hInstance := instance;
		class.lpfnWndProc := Handler;
		class.cbClsExtra := 0;
		class.cbWndExtra := 4;
		res := WinApi.RegisterClassW(class);
		class.hCursor := 0;	(* no class cursor *)
		class.lpszMenuName := NIL;
		class.hbrBackground := 0;	(* no background *)
		class.lpszClassName := "Oberon Dlg";
		class.lpfnWndProc := DialogHandler;
		res := WinApi.RegisterClassW(class);
		class.lpszClassName := "Oberon Aux";
		class.lpfnWndProc := DocWinHandler;
		res := WinApi.RegisterClassW(class);
		docIcon := WinApi.LoadIconW(instance, SYSTEM.VAL(WinApi.PtrWSTR, 2));
		IF (docIcon = 0) OR (docIcon = 1) THEN
			docIcon := WinApi.LoadIconW(0, SYSTEM.VAL(WinApi.PtrWSTR, WinApi.IDI_APPLICATION))
		END;
		dirtyIcon := WinApi.LoadIconW(instance, SYSTEM.VAL(WinApi.PtrWSTR, 5));
		IF (dirtyIcon = 0) OR (dirtyIcon = 1) THEN dirtyIcon := docIcon END;
		class.hIcon := docIcon;
		class.lpszClassName := "Oberon Doc";
		class.lpfnWndProc := DocWinHandler;
		res := WinApi.RegisterClassW(class);
		(* open main windows *)
		IF ~fullSize THEN
			main := WinApi.CreateWindowExW({8, 9}, "Oberon App", Dialog.appName,
													{16..19, 22, 23},	(* maxbox, minbox, frame, sysmenu, title, border *)
													WinApi.CW_USEDEFAULT, WinApi.CW_USEDEFAULT,
													WinApi.CW_USEDEFAULT, WinApi.CW_USEDEFAULT,
													0, menuBar, instance, 0)
		ELSE
			main := WinApi.CreateWindowExW({8, 9}, "Oberon App", Dialog.appName,
													WinApi.WS_MINIMIZEBOX + WinApi.WS_SYSMENU + WinApi.WS_CAPTION,
													WinApi.CW_USEDEFAULT, WinApi.CW_USEDEFAULT,
													WinApi.CW_USEDEFAULT, WinApi.CW_USEDEFAULT,
													0, menuBar, instance, 0)
		END;
		ccs.hWindowMenu := winMenu;
		ccs.idFirstChild := 10000;
		IF noClientScroll THEN
			client := WinApi.CreateWindowExW({}, "MDICLIENT", NIL,
													{25, 30},	(* clipchildren, child *)
													0, 0, 0, 0,
													main, 32767, instance, SYSTEM.ADR(ccs))
		ELSE
			client := WinApi.CreateWindowExW({}, "MDICLIENT", NIL,
													{20, 21, 25, 30},	(* scroll, clipchildren, child *)
													0, 0, 0, 0,
													main, 32767, instance, SYSTEM.ADR(ccs))
		END;
		status := WinApi.CreateWindowExW({}, "STATIC", NIL,
												{7, 23, 30},	(* left, noprefix, border, child *)
												0, 0, 0, 0,
												main, 32766, instance, 0);
		info := WinApi.CreateWindowExW({}, "STATIC", NIL,
											{7, 23, 30},	(* left, noprefix, border, child *)
											0, 0, 0, 0,
											main, 32765, instance, 0);
		res := WinApi.SendMessageW(status, WinApi.WM_SETFONT, font, 1);
		res := WinApi.SendMessageW(info, WinApi.WM_SETFONT, font, 1);
		dc := WinApi.GetDC(status);
		res := WinApi.GetTextMetricsW(dc, tm);
		res := WinApi.ReleaseDC(status, dc);
		statusH := tm.tmHeight;
		showState := WinApi.SW_SHOW;
		LoadWindowState();
		res := WinApi.ShowWindow(client, 1);
		IF Dialog.showsStatus THEN
			res := WinApi.ShowWindow(status, 1);
			IF Dialog.memInStatus THEN
				res := WinApi.ShowWindow(info, 1)
			END
		END;
(*	moved to HostMenus
		res := WinApi.ShowWindow(main, WinApi.SW_SHOW);
		res := WinApi.UpdateWindow(main);
*)
		WinGui.mainWnd := main;
		dc := WinApi.GetDC(main);
		
		unit := SHORT(ENTIER(
			(Ports.inch DIV WinApi.GetDeviceCaps(dc, WinApi.LOGPIXELSY)) * 100 / Dialog.scaleFactor));
		
		WinGui.unit := unit;
		borderW := stdBorderW DIV unit * unit;
		res := WinApi.ReleaseDC(main, dc);

		res := WinApi.GetWindowThreadProcessId(main, mpid);
		
		Dialog.MapString(allocKey, allocStr);
		Dialog.MapString(totalKey, totalStr);
		Dialog.MapString(byteKey, byteStr);

		IF Dialog.appName = "BlackBox" THEN
			(*
			Dialog.ShowStatus(Dialog.appName + " " + Dialog.appVersion)
			*)
		END;

		HostMechanisms.InstallDropTarget(client, NIL)
	END CreateMainWindows;
	
	PROCEDURE RecalcMainWindow;	(* adapted from WinDialog *)
		VAR _: INTEGER; rect: WinApi.RECT;
	BEGIN
		_ := WinApi.GetClientRect(main, rect);
		ResizeMainWindow(0, rect.right, rect.bottom)
	END RecalcMainWindow;
	
	(* adapted from WinDialog.WaitDialogClose *)
	(* simulates modal dialog *)
	PROCEDURE WaitFrontWindowClose;
		VAR res: INTEGER; w: Window; msg: WinApi.MSG;
	BEGIN
		w := dir.First();
		REPEAT
			IF WinApi.PeekMessageW(msg, 0, 0, 0, 1) # 0 THEN
				IF (msg.hwnd = w.wnd) OR (msg.message = WinApi.WM_PAINT) THEN
					res := WinApi.TranslateMessage(msg);
					res := WinApi.DispatchMessageW(msg)
				END
			END
		UNTIL w.wnd = 0
	END WaitFrontWindowClose;
	
	PROCEDURE Init;
		VAR res: INTEGER; d: Directory; dc: WinApi.HANDLE; doc: Documents.Document;
	BEGIN
		(* protect load if not MDI version *)
		IF Kernel.ThisLoadedMod("WinBackends") # NIL THEN
			RETURN
		ELSE
			Dialog.isMDI := TRUE
		END;
		instance := WinApi.GetModuleHandleW(NIL);
		dc := WinApi.GetDC(0);
		unit := Ports.inch DIV WinApi.GetDeviceCaps(dc, WinApi.LOGPIXELSY);
		res := WinApi.ReleaseDC(0, dc);
		scW := WinApi.GetSystemMetrics(0);	(* screen width *)
		scH := WinApi.GetSystemMetrics(1);	(* screen height *)
		NEW(d); d.l := -1; d.t := -1; d.r := -1; d.b := -1; dir := d; Windows.SetDir(d);
		NEW(winAnchor); winAnchor.next := NIL;	(* dummy header *)
		tWindow := NIL; fWindow := NIL; aWindow := NIL; bgWindow := NIL;
		noClientScroll := FALSE; fullSize := FALSE; noAppWin := FALSE;
		whiteBrush := WinApi.GetStockObject(WinApi.WHITE_BRUSH);
		nullPen := WinApi.GetStockObject(WinApi.NULL_PEN);
		font := WinApi.GetStockObject(WinApi.DEFAULT_GUI_FONT);
		cbValid := FALSE; newNumber := 1;
		StdDialog.RecalcMainWindow := RecalcMainWindow;
		WinDialog.WaitFrontWindowClose := WaitFrontWindowClose;
		WinClipboard.InitClipboardDoc(doc); OpenClipboard(doc)
	END Init;

BEGIN
	Init
END WinWindows.
