Host/Mod/TabFrames.odc

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

**)

	IMPORT
		SYSTEM, Kernel, WinApi, WinCtl, Fonts, Ports, Views,
		Controllers, Dialog, HostFonts, HostPorts, StdTabViews;

	CONST
		DEL = 07X;
		dlgWindowExtra = 30;

	TYPE
		Info = RECORD
			wnd, ctrl: WinApi.HANDLE;
			x1, y1, w1, h1, x2, y2, w2, h2: INTEGER;
			hasFocus: BOOLEAN
		END;
		
		Tab = POINTER TO RECORD (StdTabViews.Frame)
			i: Info;
			dispLeft, dispTop, dispWidth, dispHeight: INTEGER;
		END;

		Directory = POINTER TO RECORD (StdTabViews.FrameDirectory) END;

	VAR
		instance: WinApi.HANDLE;
		SubclassCtl: PROCEDURE(wnd: WinApi.HANDLE);
	
	(* auxiliary procedures *)
	
	PROCEDURE GetSize (f: StdTabViews.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
	END GetSize;
	
	PROCEDURE Adapt (f: StdTabViews.Frame; VAR i: Info);
		VAR res, x, y, w, h, cx, cy, cw, ch: 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);
				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;
					res := WinApi.MoveWindow(i.ctrl, x, y, w, h, 0)
				END
			END
		END
	END Adapt;

	PROCEDURE Open (f: StdTabViews.Frame; class, name: WinApi.PtrSTR; style, ex: SET; VAR i: Info);
		VAR res, x, y, w, h, cx, cy, cw, ch: INTEGER; p: HostPorts.Port; s: SET; r: Ports.Rider;
	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 *)
			s := {27, 30};	(* disabled, child *)
			i.wnd := WinApi.CreateWindowExA({}, "Oberon Tab", "", s, cx, cy, cw, ch, p.wnd, 0, instance, 0);
			IF i.wnd # 0 THEN
				DEC(x, cx); DEC(y, cy);
				res := WinApi.SetWindowLongA(i.wnd, dlgWindowExtra, SYSTEM.VAL(INTEGER, f));
				i.ctrl := WinApi.CreateWindowExA(ex, class, name, style, x, y, w, h, i.wnd, 0, instance, 0);
				IF i.ctrl # 0 THEN
					IF (SubclassCtl # NIL) THEN SubclassCtl(i.ctrl) END;
					IF f.font # NIL THEN
						res := WinApi.SendMessageA(i.ctrl, WinApi.WM_SETFONT, f.font(HostFonts.Font).dev.id, 0)
					END;
					Adapt(f, i);
					res := WinApi.ShowWindow(i.wnd, 1);
					res := WinApi.ShowWindow(i.ctrl, 1);
				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;
		END
	END Open;
	
	PROCEDURE (f: Tab) Print (l, r, t, b: INTEGER), NEW;
		VAR 
			w, h, sw, asc, dsc, fw, len, sel, i, dm, x, y, top: INTEGER; 
			font: Fonts.Font; tv: StdTabViews.View; s: Dialog.String; v: Views.View; 
	BEGIN
		IF f.font # NIL THEN font := f.font ELSE font := HostFonts.dlgFont END;
		font.GetBounds(asc, dsc, fw); y := asc + dsc; x := 0; dm := 0;(*y DIV 10;*) top := 0;
		f.view.context.GetSize(w, h); f.DrawRect(0, 0, w, h, 1, Ports.black);
		tv := f.view(StdTabViews.View); sel := tv.Index();
		len := tv.NofTabs();
		IF len > 0 THEN 
			i := 0; 
			tv.GetItem(i, s, v); Dialog.MapString(s, s);
			WHILE (i < len) & (s # "") & (v # NIL) DO
				tv.GetItem(i, s, v); Dialog.MapString(s, s);
				sw := SHORT(ENTIER(font.StringWidth(s) * 1.20)) + 2*dsc;
				IF (x + sw + 2*dm) > w THEN 
					f.DrawLine(0, y + 2*dm, w, y + 2*dm, 1, Ports.black);	
					x := 0; top := y + 2*dm; y := y + 2*dm + asc + dsc
				END;
				IF i = sel THEN 
					f.DrawRect(x, top, x + sw + 2*dm, y + 2*dm, Ports.fill, Ports.black);
					f.DrawString(x + dm + dsc, y - dsc + dm, Ports.white, s, font)
				ELSE
					f.DrawRect(x, top, x + sw + 2*dm, y + 2*dm, 1, Ports.black);
					f.DrawString(x + dm + dsc, y - dsc + dm, Ports.black, s, font)
				END;
				x := x + sw + 2*dm;
				INC(i);
			END;
			f.DrawLine(0, y + 2*dm, w, y + 2*dm, 1, Ports.black);			
			f.dispLeft := f.unit; f.dispTop := y + 2*dm + f.unit;
			f.dispWidth := w - 2*f.unit; f.dispHeight := h - (y + 2*dm + f.unit);
		END;
		
		(*
		IF f.font # NIL THEN font := f.font ELSE font := HostFonts.dlgFont END;
		font.GetBounds(asc, dsc, fw); y := asc + dsc; x := 0; dm := y DIV 10; top := 0;
		f.view.context.GetSize(w, h); f.DrawRect(0, 0, w, h, 1, Ports.black);
		tv := f.view(StdTabViews.View); sel := tv.Index();
		len := tv.NofTabs();
		IF len > 0 THEN 
			tv.GetItem(sel, lbl, v); Dialog.MapString(lbl, lbl);
			s := " *" + lbl$ + "*"; sv := v;
			i := 0; 
			WHILE (i < len) & (lbl # "") & (v # NIL) DO
				IF i # sel THEN tv.GetItem(i, lbl, v); Dialog.MapString(lbl, lbl); s := s + " | " +  lbl END;
				INC(i);
			END;
			f.DrawString(dm, y + dm, Ports.black, s, font);
			f.DrawLine(0, y + 2*dm, w, y + 2*dm, 1, Ports.black);			
			f.dispLeft := f.unit; f.dispTop := y + 2*dm + f.unit;
			f.dispWidth := w - 2*f.unit; f.dispHeight := h - (y + 2*dm + f.unit);
		END;
		*)
	END Print;

	PROCEDURE SendKey (ch: SHORTCHAR; 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.SendMessageA(wnd, WinApi.WM_KEYDOWN, code, 0);
			res := WinApi.SendMessageA(wnd, WinApi.WM_KEYUP, code, 0)
		ELSE
			res := WinApi.SendMessageA(wnd, WinApi.WM_CHAR, ORD(ch), 0)
		END
	END SendKey;

	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;
	BEGIN
		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.SendMessageA(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.SendMessageA(wnd, m, hc, pt.x + pt.y * 65536)
		ELSE
			res := WinApi.GetClassLongA(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.SendMessageA(wnd, m, b, pt.x + pt.y * 65536)
		END;
		REPEAT
			res := WinApi.GetMessageA(msg, 0, 0, 0);
			IF (msg.message >= WinApi.WM_MOUSEMOVE) & (msg.message <= WinApi.WM_MBUTTONDBLCLK)
			THEN
				b := msg.wParam
			END;
			res := WinApi.TranslateMessage(msg);
			res := WinApi.DispatchMessageA(msg)
		UNTIL b MOD 4 = 0
	END HandleMouse;

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

	(* Tab *)

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

	PROCEDURE (f: Tab) 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: Tab) 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;
		res := WinApi.UpdateWindow(f.i.ctrl);
	END Update;
	
	PROCEDURE (f: Tab) UpdateList;
		VAR 
			res, i, sel, len: INTEGER; 
			ti: WinCtl.TCITEMA;
			s: Dialog.String; ss: ARRAY 256 OF SHORTCHAR;
			rect: WinApi.RECT;
			v: Views.View;
			tv: StdTabViews.View;
			hrgn, hrgn1: WinApi.HRGN;
	BEGIN
		tv := f.view(StdTabViews.View); sel := tv.Index();
		res := WinApi.SendMessageA(f.i.ctrl, WinCtl.TCM_DELETEALLITEMS, 0, 0); 
		len := tv.NofTabs();
		(* Add the tabs to the tabcontrol *)
		IF len > 0 THEN
			i := 0; tv.GetItem(i, s, v); Dialog.MapString(s, s); ss := SHORT(s$);
			WHILE (i < len) & (ss # "") & (v # NIL) DO
				ti.mask := WinCtl.TCIF_TEXT + WinCtl.TCIF_IMAGE; 
				ti.pszText := ss; ti.iImage := -1;
				res := WinApi.SendMessage(f.i.ctrl, WinCtl.TCM_INSERTITEM, i, SYSTEM.ADR(ti));
				INC(i); IF i < len THEN tv.GetItem(i, s, v); Dialog.MapString(s, s); ss := SHORT(s$) END
			END
		END;
		(* Get the display area *)
		rect.left := 0; rect.top := 0; rect.right := f.i.w2; rect.bottom := f.i.h2;
		res := WinApi.SendMessageA(f.i.ctrl, WinCtl.TCM_ADJUSTRECT, WinApi.FALSE, SYSTEM.ADR(rect));
		(* set the area which the control should redraw *)
		hrgn := WinApi.CreateRectRgn(0, 0, f.i.w2, f.i.h2);
		hrgn1 := WinApi.CreateRectRgn(rect.left, rect.top, rect.right, rect.bottom);
		res := WinApi.CombineRgn(hrgn, hrgn, hrgn1, WinApi.RGN_DIFF);
		res := WinApi.SetWindowRgn(f.i.ctrl, hrgn, WinApi.FALSE);
		IF res = 0 THEN res := WinApi.DeleteObject(hrgn) END;
		res := WinApi.DeleteObject(hrgn1);
		(* set the area which the contained views should draw *)
		f.dispLeft := rect.left * f.unit; f.dispTop := rect.top * f.unit;
		f.dispWidth := (rect.right - rect.left) * f.unit; f.dispHeight := (rect.bottom - rect.top) * f.unit;
		(* Set the size and the domain of the views *)
		IF len > 0 THEN
			i := 0; tv.GetItem(i, s, v); ss := SHORT(s$);
			WHILE (i < len) & (ss # "") & (v # NIL) DO
				v.context.SetSize(f.dispWidth, f.dispHeight);
				INC(i); IF i < len THEN  tv.GetItem(i, s, v); ss := SHORT(s$) END
			END
		END;
		(* Reset selection *)
		res := WinApi.SendMessageA(f.i.ctrl, WinCtl.TCM_SETCURSEL, sel, 0);
		f.Update
	END UpdateList;
	
	PROCEDURE (f: Tab) Paint (il, it, ir, ib: INTEGER), NEW;
		VAR res, l, t, r, b, w, h: INTEGER; ri: HostPorts.Rider; rect: WinApi.RECT;
	BEGIN
		res := WinApi.GetWindowRect(f.i.wnd, rect);
		w := rect.right  - rect.left; h := rect.bottom - rect.top;
		ri := f.rider(HostPorts.Rider); 
		ri.port.CloseBuffer;
		IF it < f.dispTop THEN (* Paint the top part *)
			l := (il DIV f.unit) - 1; IF l < 0 THEN l := 0 END; r := (ir DIV f.unit) + 1; IF r > w THEN r := w END;
			rect.left := l; rect.top := 0; rect.right := r; rect.bottom := f.dispTop DIV f.unit;
			res := WinApi.InvalidateRect(f.i.wnd, rect, 0); res := WinApi.InvalidateRect(f.i.ctrl, rect, 0);
			res := WinApi.UpdateWindow(f.i.wnd); res := WinApi.UpdateWindow(f.i.ctrl)
		END;
		IF il < f.dispLeft THEN (* Paint the left part *)
			t := (it DIV f.unit) - 1; IF t < 0 THEN t := 0 END; b := (ib DIV f.unit) + 1; IF b > h THEN b := h END;
			rect.left := 0; rect.top := t; rect.right := f.dispLeft DIV f.unit; rect.bottom := b;
			res := WinApi.InvalidateRect(f.i.wnd, rect, 0); res := WinApi.InvalidateRect(f.i.ctrl, rect, 0);
			res := WinApi.UpdateWindow(f.i.wnd); res := WinApi.UpdateWindow(f.i.ctrl)
		END;
		IF ib > f.dispTop + f.dispHeight THEN (* Paint the bottom part *)
			l := (il DIV f.unit) - 1; IF l < 0 THEN l := 0 END; r := (ir DIV f.unit) + 1; IF r > w THEN r := w END;
			rect.left := l; rect.top := (f.dispTop + f.dispHeight) DIV f.unit; rect.right := r; rect.bottom := h;
			res := WinApi.InvalidateRect(f.i.wnd, rect, 0); res := WinApi.InvalidateRect(f.i.ctrl, rect, 0);
			res := WinApi.UpdateWindow(f.i.wnd); res := WinApi.UpdateWindow(f.i.ctrl)
		END;
		IF ir > f.dispLeft + f.dispWidth THEN (* Paint the right part *)
			t := (it DIV f.unit) - 1; IF t < 0 THEN t := 0 END; b := (ib DIV f.unit) + 1; IF b > h THEN b := h END;
			rect.left := (f.dispLeft + f.dispWidth) DIV f.unit; rect.top := t; rect.right := w; rect.bottom := b;
			res := WinApi.InvalidateRect(f.i.wnd, rect, 0); res := WinApi.InvalidateRect(f.i.ctrl, rect, 0);
			res := WinApi.UpdateWindow(f.i.wnd); res := WinApi.UpdateWindow(f.i.ctrl)
		END
	END Paint;
	
	PROCEDURE (f: Tab) Restore (l, t, r, b: INTEGER);
		VAR 
			icex: WinCtl.INITCOMMONCONTROLSEX;
			ok: WinApi.BOOL;
	BEGIN
		IF f.i.ctrl = 0 THEN	(* lazy allocation *)
			icex.dwSize := SIZE(WinCtl.INITCOMMONCONTROLSEX);
			icex.dwICC := WinCtl.ICC_TAB_CLASSES;
			ok :=  WinCtl.InitCommonControlsEx(icex);
			Open(f, WinCtl.WC_TABCONTROL, "", 
				WinApi.WS_CLIPSIBLINGS + WinApi.WS_CHILD + WinApi.WS_VISIBLE + WinCtl.TCS_MULTILINE,
				{},  f.i);
			f.UpdateList;
		END;
		f.Update;
		IF f.i.wnd # 0 THEN f.Paint(l, t, r, b) ELSE f.Print(l, t, r, b) END
	END Restore;

	PROCEDURE (f: Tab) KeyDown (ch: CHAR);
		VAR res: INTEGER;
	BEGIN
		ASSERT(~f.disabled, 100);
		IF ch = " " THEN
			res := WinApi.SendMessageA(f.i.ctrl, WinApi.WM_KEYDOWN, ORD(ch), 0);
			res := WinApi.SendMessageA(f.i.ctrl, WinApi.WM_KEYUP, ORD(ch), 0)
		ELSE SendKey(SHORT(ch), f.i.ctrl)
		END
	END KeyDown;
	
	PROCEDURE (f: Tab) 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: Tab) Mark (on, focus: BOOLEAN);
	BEGIN
		Mark(on, f.front, f.i)
	END Mark;
	
	PROCEDURE (f: Tab) InDispArea (x, y: INTEGER): BOOLEAN;
	BEGIN
		IF ((x >= f.dispLeft) & (x <= f.dispLeft + f.dispWidth) &
			(y >= f.dispTop) & (y <= f.dispTop + f.dispHeight)) THEN
			RETURN TRUE
		ELSE
			RETURN FALSE
		END
	END InDispArea;
	
	PROCEDURE (f: Tab) GetDispSize (OUT x, y, w, h: INTEGER);
		VAR
			rect: WinApi.RECT;
			res: INTEGER;
	BEGIN
		IF ~Views.IsPrinterFrame(f) THEN
			rect.left := 0; rect.top := 0; rect.right := f.i.w2; rect.bottom := f.i.h2;
			res := WinApi.SendMessageA(f.i.ctrl, WinCtl.TCM_ADJUSTRECT, WinApi.FALSE, SYSTEM.ADR(rect));
			f.dispLeft := rect.left * f.unit; f.dispTop := rect.top * f.unit;
			f.dispWidth := (rect.right - rect.left) * f.unit; f.dispHeight := (rect.bottom - rect.top) * f.unit
		END;
		x := f.dispLeft; y := f.dispTop; w := f.dispWidth; h := f.dispHeight		
	END GetDispSize;
	
	
	(* Directory *)

	PROCEDURE (d: Directory) GetTabSize (VAR w, h: INTEGER);
	BEGIN
		IF w = Views.undefined THEN w := 150 * Ports.point END;
		IF h = Views.undefined THEN h := 100 * Ports.point END
	END GetTabSize;
	
	PROCEDURE (d: Directory) New (): StdTabViews.Frame;
		VAR f: Tab;
	BEGIN
		NEW(f); RETURN f
	END New;
	
	(* control window class *)
	
	(* Used for common controls, not standard controls *)
	PROCEDURE HandleNotifyMsg (wnd: WinApi.HANDLE; wParam, lParam: INTEGER);
		VAR  c: StdTabViews.Frame; pnmhdr: WinApi.PtrNMHDR;
	BEGIN
		c := SYSTEM.VAL(StdTabViews.Frame, WinApi.GetWindowLongA(wnd, dlgWindowExtra));
		pnmhdr := SYSTEM.VAL(WinApi.PtrNMHDR, lParam);
		WITH c: Tab DO
			IF c.i.ctrl #0 THEN
				IF pnmhdr.code = WinCtl.TCN_SELCHANGE THEN;
					c.SetIndex(WinApi.SendMessageA(c.i.ctrl, WinCtl.TCM_GETCURSEL, 0, 0))
				END
			END
		END
	END HandleNotifyMsg;
	
	PROCEDURE [2] CtrlHandler (wnd: WinApi.HANDLE; message, wParam, lParam: INTEGER): INTEGER;
	BEGIN
		IF message = WinApi.WM_NOTIFY THEN
			Kernel.Try(HandleNotifyMsg, wnd, wParam, lParam);
			RETURN 0
		END;
		RETURN WinApi.DefWindowProcA(wnd, message, wParam, lParam)
	END CtrlHandler;

	PROCEDURE Install;
		VAR dir: Directory;
	BEGIN
		NEW(dir); StdTabViews.SetFrameDir(dir)
	END Install;

	PROCEDURE InitClass;
		VAR class: WinApi.WNDCLASS; res: INTEGER;
	BEGIN
		class.hCursor := WinApi.NULL;
		class.hIcon := WinApi.NULL;
		class.lpszMenuName := NIL;
		class.lpszClassName := "Oberon Tab";
		class.hbrBackground := 0; (* no background *)
		class.style := {};
		class.hInstance := instance;
		class.lpfnWndProc := CtrlHandler;
		class.cbClsExtra := 0;
		class.cbWndExtra := dlgWindowExtra + 4;
		res := WinApi.RegisterClassA(class)
	END InitClass;
	
	PROCEDURE Init*;
	BEGIN
		StdTabViews.setFocus := TRUE;
		instance := WinApi.GetModuleHandleA(NIL);
		InitClass;
		Install;
	END Init;
	
END HostTabFrames.