MODULE Views;
(**
	project	= "BlackBox2.0, diffs from 1.7.2 marked by green"
	organization	= "www.oberon.ch, blackbox.oberon.org"
	contributors	= "Oberon microsystems, Anton Dmitriev, Ketmar Dark"
	version	= "System/Rsrc/About"
	copyright	= "System/Rsrc/About"
	license	= "The 2-Clause BSD License"
	changes	= "
	- 2019.11, ad, 
	AD: Amendment proposal: RootFrames may be embedded into Frames (2019/11)
	Previously, RootFrames could not be installed into other frames; they were meant to be connected directly to ports, and referenced directly by Windows.Window and instantiated by Documents.Document.GetNewFrame. A RootFrame was always the root of a tree of frames inside one window. RootFrames were never closed by Views, they were persistent throughout a window/documet lifetime, unlike other frames that were created and closed by Views at any time at it's own discretion, and were nowhere close to persistent.
	My amendment allows for RootFrames to be installed into other frames, possibly other RootFrames. This, I figured, is by far the easiest and most organic (to BlackBox framework) way to allow windows to be displayed inside other windows (as well as some other pleasant perks, see Windows.Layout, TylerWindows.Layout, TylerWindows.Pane). I figured this out after two other experiments. 1) with Host-os object, in my case, GTK Drawing Areas. This path depends heavily on GTK which is buggy, unpredictable and asynchronous => complex, and produces Host-specific unportable code. 2) with ProxyPorts: a ProxyPort is a Port that directs all drawing operations onto another actual port. This path provides for portable BlackBox code; however, I ran into trouble with rounding errors. A guest window (the one displayed inside another window) is embedded in some view, and it's location and size are determined in Universal Coordinates. So, when actually restoring the guest window on the host window, the conversions would be as follows: (Guest view inside guest window, UCs) => ProxyPort, pixels => Host view inside host window, UCs => host window's port, pixels. Somewhere along the lines of these coversions an extra line of "orphaned" pixels would occur under some conditions: a line that would not be "owned" and restored by either guest or host window. This of course is not acceptable in a UI, so I abandoned this path. I still think, though, that the orphaed pixel problem could be fixed by a more thorough researcher than myself, and having a working ProxyPort might open new possibilities in building UIs, including over-the-network UIs.
	I went the third path and amended Views to allow RootFrames to be embedded into other frames. On top of solving the task that was in front of me - a tiling window manager for BB - it brought some other benefits. 
	1) A Host-independent implementation of scrollbars in which scrollbars are first-class BlackBox views - that is, they receive messages just as any other BB view, and the same restoration mechanism is used. This allows for the same host-independent scrollbars to be used in custom views and in windows. To hell with GTK! 
	2) A window may now have arbitrary structure and number of views that are part of the window, but not part of the document; these are first-class BB views, they receive all messages and BB restoration mechanism is used for them. This allows to build things like window toolbars in the BlackBox way: a toolbar is an arbitrary Views.View extension; even a Forms.Form can be opened as a "pane" (toolbar). Now how about that! A particular case of such panes is a non-modal dialog; one such dialog that I built is the File Close dialog (presented to the user when he attempts to close a "dirty" document). 
	All this to say, I'm happy with the third path I eventually chose, and I'm looking forward for toolbars and nonmodal dialogs to appear as extesions and applications of my work.
	
	So, under my amendment, the frame tree can now have multiple RootFrames. The topmost RootFrame is called UltimateRoot in module text.
	The views structure is determined by applications and is thus permanent, from the framework's perspective: the application decides when a view is created, when it is removed, and where it is embedded. Module Views has no knowledge and makes no assumptions about the creation, removal and embedding of views. Views is agnostic of the view graph. However, the framework knows everything about the visualization structure; a frame is the central element of the visualization structure. The app tells Views what visualization structure it wants by calling Views.InstallFrame. When a frame (a view) becomes obscured (overlaid by another frame/view or scrolled out inside a window or it's container), Views removes the frame. And this is the problem: under my amendment, RootFrames may be embedded, and Views may want to remove a RootFrame. This would break, well, everything: Windows and Documents assert that RootFrames are persistent. Here is why. For instance, when a TextViews.View is opened in a window, the frame tree has a RootFrame (displaying the Document), and a frame for the TextView, and frames for each embedded visible(!) view. This structure is preserved even when the window is obscured by other windows, or when it is minimized. This structure is necessary for critical things in the framework to work: for example, when the Find dialog is opened, the dialog itself is the front window, and the text is the target window; if the Find dialog obscures the target window, the target's frame structure has to be preserved, otherwise things that depend on Controllers.Focus(Controllers.targetPath) will not work: Controllers.Focus() sends messages into the target window, and if there is no frame tree, well, there's no one to receive and process the messages, thus, no focus frame/view.
	Merely "uninstalling" (disembedding) a RootFrame and preventing it from being removed and Closed doesn't work either, and here's why. Even if a RootFrame is "uninstalled" (unreachable from it's previous - or any - embedding frame), it is still connected to a Port. Thus, any attempt to draw inside that frame tree will succeed - resulting in obscured windows drawing over an overlaying window - mess! Thus when a RootFrame is "uninstalled", any drawing in it's frame tree should be suppressed. 
	This constitutes a new "condition" for a RootFrame: a RootFrame becomes obscured when it is uninstalled from it's embedding view. When a RootFrame is obscured, it's rider's clipping rectangle is set to an empty rectangle (0x0); all Update operations are stored in the Update Regions (in the usual way), and Restore operations make sure that the clipping rectangle is not altered. Thus, drawing into obscured RootFrames is suppressed in a manner transparent to the clients.
	(My other attempt to solve this problem was to temporarily disconnect obscured RootFrames from their ports, but this results in traps because clients expect a RootFrame to be properly connected to a port)
	I figured it's not necessary to introduce a new constant to mark and identify the obscured state; it is always deductible from the rest of RootFrame state; however, it is possible to introduce such a constant for the .state field in order to (possibly) increase effectiveness: it is cheaper to check the .state field than call a procedure that deduces the obscured state.
	Now, under my amendments, RootFrames have to be closed explicitly by calling CloseRoot (to be replaced with root.Close). Formerly, RootFrames were closed by Views whenever their clipping rectangle was set to (0, 0); this was done as part of window closing. Now RootFrames may be embedded into other frames, and having and empty clipping rectangle does not necessarily signal end-of-life for RootFrame; so an explicit way was introduced to tell Views that a RootFrame has reached an end of it's life.
	On messaging. It was the assertion of Views (and all it's clients) that Broadcasts only run from a RootFrame down to the underlying frame tree, which did not include any other RootFrames. I have preserved this assumption: messages are not broadcast into embedded RootViews. (How do they ever get there? Well, the message would have to be broadcast into the (embedded) window that holds reference to the (embedded) RootFrame - which actually is the case anyway under the current implementation of Windows and Sequencers).
	As far as CtrlMessages are concerned - well, it is not decided by the framework anyway where to forward a CtrlMessage, so no assumptions are broken here either: it is up to the receiver of a CtrlMessage to decide where to forward it, if at all.
	The assumptions of procedures HostOf and RootOf have been preserved: they work their way up the frame tree until they encounter a RootFrame, at which point they stop - just as was the case before my amendments. One could say they stay "within" one window, even when it is embedded in another window.
	The restoration procedures are the ones that have been changed the most: embedded RootFrames are restored as part of their embedde's restoration process.
	Usage:
	To conect a RootFrame to a view: PROCEDURE SetRoot
	To install a RootFrame into another frame: PROCEDURE InstallRoot. It is similar in purpose and implementation to InstallFrame, but unlike InstallFrame it takes a RootFrame, not a view, and does not create a new frame.
	Traditional usage: SetRoot followed by AdaptRoot
	Amended RootFrame usage: SetRoot, then AdaptRoot whenever the position or size of the RootFrame changes; then InstallRoot to embed the RootFrame into another frame. (SetRoot also needs to be called when a RootFrame becomes/unbecomes focus.)Amendment proposal: RootFrames may be embedded into Frames 
	- 2019.12, ad, allow root views to request transparent background; Ports.background is used under the top root view; transparent background is provided for root views embedded in other views.
	- YYYYMMDD, nn, ...
	"
	issues	= "
	- fundamentally, using purely host-specific area-invalidation would be better, but that would be major change
	"

**)

	IMPORT SYSTEM, Kernel,
		Log, Dialog, Files, Services, Fonts, Stores, Converters, Ports, Sequencers, Models;

	CONST
		(** View.Background color **)
		transparent* = 0FF000000H;

		(** Views.CopyModel / Views.CopyOf shallow **)
		deep* = FALSE; shallow* = TRUE;

		(** Update, UpdateIn rebuild **)
		keepFrames* = FALSE; rebuildFrames* = TRUE;

		(** Deposit, QualifiedDeposit, Fetch w, h **)
		undefined* = 0;
		
		(** OldView, RegisterView ask **)
		dontAsk* = FALSE; ask* = TRUE;

		(* method numbers (UNSAFE!) *)
		(* copyFrom = 1; *)
		copyFromModelView = 7; copyFromSimpleView = 8;

		(* Frame.state *)
		new = 0; open = 1; closed = 2;
		
		maxN = 30;	(* max number of rects used to approximate a region *)

		minVersion = 0; maxVersion = 0;
		
		(* actOp *)
		handler = 1; restore = 2; externalize = 3;

		markBorderSize = 2;

		clean* = Sequencers.clean; 
		notUndoable* = Sequencers.notUndoable;
		invisible* = Sequencers.invisible; 

		magic = -19780528;	(* used to SetRect and SetOffset on an obscured RootFrame, and to identify an obscured RootFrame; could be eliminated or replaced with a Frame.state constant *)
		(* this is magic offset for new, but not yet "mapped" windows from tiler *)
		hMagic = 0 - 03FFFFFFH;

	TYPE
	
		(** views **)

		View* = POINTER TO ABSTRACT RECORD (Stores.Store)
			context-: Models.Context;	(** stable context # NIL **)
			era: INTEGER;
			guard: INTEGER;	(* = TrapCount()+1 if view is addressee of ongoing broadcast *)
			bad: SET
		END;

		Alien* = POINTER TO LIMITED RECORD (View)
			store-: Stores.Alien
		END;

		Title* = ARRAY 64 OF CHAR;

		TrapAlien = POINTER TO RECORD (Stores.Store) END;


		(** frames **)

		Frame* = POINTER TO ABSTRACT RECORD (Ports.Frame)
			l-, t-, r-, b-: INTEGER;	(** l < r, t < b **)
			view-: View;	(** opened => view # NIL, view.context # NIL, view.seq # NIL **)
			front-, mark-: BOOLEAN;
			state: BYTE;
			x, y: INTEGER;	(* origin in coordinates of environment *)	(** origin relative to host; gx, gy is relative to port! *)
			gx0, gy0: INTEGER;	(* global origin w/o local subpixel scrolling compensation *)
			sx, sy: INTEGER;	(* cumulated local sub-pixel scrolling compensation *)
			next, down, up, focus: Frame;
			level: INTEGER	(* used for partial z-ordering *)
		END;
		(** Views.Frame, unlike Ports.Frame, support the notion of nesting and hosting, cf.  Views.InstallFrame Views.HostOf, etc. *)

		Message* = ABSTRACT RECORD
			view-: View	(** view # NIL **)
		END;

		NotifyMsg* = EXTENSIBLE RECORD (Message)
			id0*, id1*: INTEGER;
			opts*: SET
		END;
		
		NotifyHook = POINTER TO RECORD (Dialog.NotifyHook) END;
		
		UpdateCachesMsg* = EXTENSIBLE RECORD (Message) END;
		
		ScrollClassMsg* = RECORD (Message)
			allowBitmapScrolling*: BOOLEAN (** OUT, preset to FALSE **)
		END;


		(** property messages **)

		PropMessage* = ABSTRACT RECORD END;


		(** controller messages **)

		CtrlMessage* = ABSTRACT RECORD END;

		CtrlMsgHandler* = PROCEDURE (op: INTEGER; f, g: Frame; VAR msg: CtrlMessage; 
			VAR mark, front, req: BOOLEAN
		);

		UpdateMsg = RECORD (Message)
			scroll, rebuild, all: BOOLEAN;
			l, t, r, b, dx, dy: INTEGER
		END;


		Rect = RECORD
			v: View;
			rebuild: BOOLEAN;
			l, t, r, b: INTEGER
		END;

		Region = POINTER TO RECORD
			n: INTEGER;
			r: ARRAY maxN OF Rect
		END;

		RootFrame* = POINTER TO RECORD (Frame)
			flags-: SET;
			update: Region	(* allocated lazily by SetRoot *)
		END;
		
		StdFrame = POINTER TO RECORD (Frame) END;


		(* view producer/consumer decoupling *)

		QueueElem = POINTER TO RECORD
			next: QueueElem;
			view: View
		END;
		
		GetSpecHook* = POINTER TO ABSTRACT RECORD END;
		ViewHook* = POINTER TO ABSTRACT RECORD END;
		MsgHook* = POINTER TO ABSTRACT RECORD END;
		

	VAR
		HandleCtrlMsg-: CtrlMsgHandler;

		domainGuard: INTEGER;	(* = TrapCount()+1 if domain is addressee of ongoing domaincast *)
		
		actView: View;
		actFrame: RootFrame;
		actOp: INTEGER;

		copyModel: Models.Model;	(* context for (View)CopyFrom; reset by TrapCleanup *)

		queue: RECORD
			len: INTEGER;
			head, tail: QueueElem
		END;
		
		getSpecHook: GetSpecHook;
		viewHook: ViewHook;
		msgHook: MsgHook;
		
		rootToInstall: RootFrame;	(* parameter InstallRoot => InstallFrame *)
		
		
	PROCEDURE Overwritten (v: View; mno: INTEGER): BOOLEAN;
		VAR base, actual: PROCEDURE;
	BEGIN
		SYSTEM.GET(SYSTEM.TYP(View) - 4 * (mno + 1), base);
		SYSTEM.GET(SYSTEM.TYP(v) - 4 * (mno + 1), actual);
		RETURN actual # base
	END Overwritten;

	(** Hooks **)
	
	PROCEDURE (h: GetSpecHook) GetExtSpec* (s: Stores.Store; VAR loc: Files.Locator; 
											VAR name: Files.Name; VAR conv: Converters.Converter), NEW, ABSTRACT;
	PROCEDURE (h: GetSpecHook) GetIntSpec* (VAR loc: Files.Locator; VAR name: Files.Name; 
											VAR conv: Converters.Converter), NEW, ABSTRACT;

	PROCEDURE SetGetSpecHook*(h: GetSpecHook);
	BEGIN
		getSpecHook := h
	END SetGetSpecHook;
	
	PROCEDURE (h: ViewHook) OldView* (loc: Files.Locator; VAR name: Files.Name; 
				VAR conv: Converters.Converter): View, NEW, ABSTRACT;
	PROCEDURE (h: ViewHook) Open* (s: View; title: ARRAY OF CHAR;
				loc: Files.Locator; name: Files.Name; conv: Converters.Converter; 
				asTool, asAux, noResize, allowDuplicates, neverDirty: BOOLEAN), NEW, ABSTRACT;
	PROCEDURE (h: ViewHook) RegisterView* (s: View; loc: Files.Locator; 
				name: Files.Name; conv: Converters.Converter), NEW, ABSTRACT;

	PROCEDURE SetViewHook*(h: ViewHook);
	BEGIN
		viewHook := h
	END SetViewHook;
	
	PROCEDURE (h: MsgHook) Omnicast* (VAR msg: ANYREC), NEW, ABSTRACT;
	PROCEDURE (h: MsgHook) RestoreDomain* (domain: Stores.Domain), NEW, ABSTRACT;

	PROCEDURE SetMsgHook*(h: MsgHook);
	BEGIN
		msgHook := h
	END SetMsgHook;

	
	(** Model protocol **)

	PROCEDURE (v: View) CopyFromSimpleView- (source: View), NEW, EMPTY;
	PROCEDURE (v: View) CopyFromModelView- (source: View; model: Models.Model), NEW, EMPTY;

	PROCEDURE (v: View) ThisModel* (): Models.Model, NEW, EXTENSIBLE;
	BEGIN
		RETURN NIL
	END ThisModel;


	(** Store protocol **)

	PROCEDURE (v: View) CopyFrom- (source: Stores.Store);
		VAR tm, fm: Models.Model; c: Models.Context;
	BEGIN
		tm := copyModel; copyModel := NIL;
		WITH source: View DO
			v.era := source.era;
			actView := NIL;
			IF tm = NIL THEN	(* if copyModel wasn't preset then use deep copy as default *)
				fm := source.ThisModel();
				IF fm # NIL THEN tm := Stores.CopyOf(fm)(Models.Model) END
			END;
			actView := v;
			IF Overwritten(v, copyFromModelView) THEN	(* new View *)
				ASSERT(~Overwritten(v, copyFromSimpleView), 20);
				c := v.context;
				v.CopyFromModelView(source, tm);
				ASSERT(v.context = c, 60)
			ELSE	(* old or simple View *)
				(* IF tm # NIL THEN v.InitModel(tm) END *)
				c := v.context;
				v.CopyFromSimpleView(source);
				ASSERT(v.context = c, 60)
			END
		END
	END CopyFrom;

	PROCEDURE (v: View) Internalize- (VAR rd: Stores.Reader), EXTENSIBLE;
		VAR thisVersion: INTEGER;
	BEGIN
		v.Internalize^(rd);
		IF rd.cancelled THEN RETURN END;
		rd.ReadVersion(minVersion, maxVersion, thisVersion)
	END Internalize;

	PROCEDURE (v: View) Externalize- (VAR wr: Stores.Writer), EXTENSIBLE;
	BEGIN
		v.Externalize^(wr);
		wr.WriteVersion(maxVersion)
	END Externalize;


	(** embedding protocol **)

	PROCEDURE (v: View) InitContext* (context: Models.Context), NEW, EXTENSIBLE;
	BEGIN
		ASSERT(context # NIL, 21);
		ASSERT((v.context = NIL) OR (v.context = context), 22);
		v.context := context
	END InitContext;

	PROCEDURE (v: View) GetBackground* (VAR color: Ports.Color), NEW, EMPTY;
	PROCEDURE (v: View) ConsiderFocusRequestBy- (view: View), NEW, EMPTY;
	PROCEDURE (v: View) Neutralize*, NEW, EMPTY;


	(** Frame protocol **)

	PROCEDURE (v: View) GetNewFrame* (VAR frame: Frame), NEW, EMPTY;
	PROCEDURE (v: View) Restore* (f: Frame; l, t, r, b: INTEGER), NEW, ABSTRACT;
	PROCEDURE (v: View) RestoreMarks* (f: Frame; l, t, r, b: INTEGER), NEW, EMPTY;


	(** handlers **)

	PROCEDURE (v: View) HandleModelMsg- (VAR msg: Models.Message), NEW, EMPTY;
	PROCEDURE (v: View) HandleViewMsg- (f: Frame; VAR msg: Message), NEW, EMPTY;
	PROCEDURE (v: View) HandleCtrlMsg* (f: Frame; VAR msg: CtrlMessage; VAR focus: View), NEW, EMPTY;
	PROCEDURE (v: View) HandlePropMsg- (VAR msg: PropMessage), NEW, EMPTY;


	(** Alien **)

	PROCEDURE (a: Alien) Externalize- (VAR wr: Stores.Writer);
	BEGIN
		HALT(100)
	END Externalize;

	PROCEDURE (a: Alien) Internalize- (VAR rd: Stores.Reader);
	BEGIN
		HALT(100)
	END Internalize;

	PROCEDURE (a: Alien) CopyFromSimpleView- (source: View);
	BEGIN
		a.store := Stores.CopyOf(source(Alien).store)(Stores.Alien); Stores.Join(a, a.store)
	END CopyFromSimpleView;

	PROCEDURE (a: Alien) Restore* (f: Frame; l, t, r, b: INTEGER);
		VAR u, w, h: INTEGER;
	BEGIN
		u := f.dot; a.context.GetSize(w, h);
		f.DrawRect(0, 0, w, h, Ports.fill, Ports.grey25);
		f.DrawRect(0, 0, w, h, 2 * u, Ports.grey75);
		f.DrawLine(0, 0, w - u, h - u, u, Ports.grey75);
		f.DrawLine(w - u, 0, 0, h - u, u, Ports.grey75)
	END Restore;
	

	(** TrapAlien **)

	PROCEDURE (v: TrapAlien) Internalize (VAR rd: Stores.Reader);
	BEGIN
		v.Internalize^(rd);
		rd.TurnIntoAlien(3)
	END Internalize;
	
	PROCEDURE (v: TrapAlien) Externalize (VAR rd: Stores.Writer);
	END Externalize;
	
	PROCEDURE (v: TrapAlien) CopyFrom (source: Stores.Store), EMPTY;


	(** Frame **)

	PROCEDURE (f: Frame) Close* (), NEW, EMPTY;


	(* Rect, Region *)

	PROCEDURE Union (VAR u: Rect; r: Rect);
	BEGIN
		IF r.v # u.v THEN u.v := NIL END;
		IF r.rebuild THEN u.rebuild := TRUE END;
		IF r.l < u.l THEN u.l := r.l END;
		IF r.t < u.t THEN u.t := r.t END;
		IF r.r > u.r THEN u.r := r.r END;
		IF r.b > u.b THEN u.b := r.b END
	END Union;

	PROCEDURE Add (rgn: Region; v: View; rebuild: BOOLEAN; gl, gt, gr, gb: INTEGER);
		(* does not perfectly maintain invariant of non-overlapping approx rects ... *)
		VAR q: Rect; i, j, n: INTEGER; x: ARRAY maxN OF BOOLEAN;
	BEGIN
		q.v := v; q.rebuild := rebuild; q.l := gl; q.t := gt; q.r := gr; q.b := gb;
		n := rgn.n + 1;
		i := 0;
		WHILE i < rgn.n DO
			x[i] := (gl < rgn.r[i].r) & (rgn.r[i].l < gr) & (gt < rgn.r[i].b) & (rgn.r[i].t < gb);  
			(* q INTERSECTS with r[i] *)
			IF x[i] THEN Union(q, rgn.r[i]); DEC(n) END;
			INC(i)
		END;
		IF n > maxN THEN
			(* n = maxN + 1 -> merge q with arbitrarily picked rect and Add *)
			Union(q, rgn.r[maxN - 1]); Add(rgn, v, q.rebuild, q.l, q.t, q.r, q.b)
		ELSE
			i := 0; WHILE (i < rgn.n) & ~x[i] DO INC(i) END;
			rgn.r[i] := q; INC(i); WHILE (i < rgn.n) & ~x[i] DO INC(i) END;
			j := i; WHILE (i < rgn.n) & x[i] DO INC(i) END;
			WHILE i < rgn.n DO	(* ~x[i] *)
				rgn.r[j] := rgn.r[i]; INC(j); INC(i);
				WHILE (i < rgn.n) & x[i] DO INC(i) END
			END;
			rgn.n := n
		END
	END Add;

	PROCEDURE AddRect (root: RootFrame; f: Frame; l, t, r, b: INTEGER; rebuild: BOOLEAN);
		VAR rl, rt, rr, rb: INTEGER; i: INTEGER;
	BEGIN
		INC(l, f.gx); INC(t, f.gy); INC(r, f.gx); INC(b, f.gy);
		rl := root.l + root.gx; rt := root.t + root.gy; rr := root.r + root.gx; rb := root.b + root.gy;
		IF l < rl THEN l := rl END;
		IF t < rt THEN t := rt END;
		IF r > rr THEN r := rr END;
		IF b > rb THEN b := rb END;
		IF (l < r) & (t < b) THEN
			Add(root.update, f.view, rebuild, l, t, r, b);
			i := 0;
			WHILE (i < root.update.n)
				& (~root.update.r[i].rebuild OR (root.update.r[i].v # NIL)) DO INC(i) END;
			IF i < root.update.n THEN
				Add(root.update, root.view, TRUE, rl, rt, rr, rb)
				(* Updates+Rebuilds the whole root frame *)
			END
		END
	END AddRect;


	(** miscellaneous **)

	PROCEDURE RestoreDomain* (domain: Stores.Domain);
	BEGIN
		ASSERT(msgHook # NIL, 100);
		msgHook.RestoreDomain(domain)
	END RestoreDomain;

	PROCEDURE MarkBorder* (host: Ports.Frame; view: View; l, t, r, b: INTEGER);
		VAR s: INTEGER;
	BEGIN
		IF view # NIL THEN 
			s := markBorderSize * host.dot;
			host.MarkRect(l - s, t - s, r + s, b + s, s, Ports.dim50, Ports.show)
		END
	END MarkBorder;
	


	(** views **)

	PROCEDURE SeqOf (v: View): Sequencers.Sequencer;
		VAR (*c: Models.Context;*) d: Stores.Domain; seq: Sequencers.Sequencer; any: ANYPTR;
	BEGIN
		d := v.Domain(); seq := NIL;
		IF d # NIL THEN
			any := d.GetSequencer();
			IF (any # NIL) & (any IS Sequencers.Sequencer) THEN
				seq := any(Sequencers.Sequencer)
			END
		END;
		RETURN seq
	END SeqOf;


	PROCEDURE Era* (v: View): INTEGER;
	(** pre: v # NIL *)
	(** post:
		v.ThisModel() # NIL
			in-synch(v) iff Era(v) = Models.Era(v.ThisModel())
	**)
	BEGIN
		ASSERT(v # NIL, 20);
		RETURN v.era
	END Era;

	PROCEDURE BeginScript* (v: View; name: Stores.OpName; OUT script: Stores.Operation);
	(** pre: v # NIL *)
	(** post: (script # NIL) iff (v.seq # NIL) **)
		VAR seq: Sequencers.Sequencer;
	BEGIN
		ASSERT(v # NIL, 20);
		seq := SeqOf(v);
		IF seq # NIL THEN seq.BeginScript(name, script)
		ELSE script := NIL
		END
	END BeginScript;

	PROCEDURE Do* (v: View; name: Stores.OpName; op: Stores.Operation);
	(** pre: v # NIL, op # NIL, ~op.inUse **)
		VAR seq: Sequencers.Sequencer;
	BEGIN
		ASSERT(v # NIL, 20); ASSERT(op # NIL, 21); (* ASSERT(~op.inUse, 22); *)
		seq := SeqOf(v);
		IF seq # NIL THEN seq.Do(v, name, op) ELSE op.Do END
	END Do;

	PROCEDURE LastOp* (v: View): Stores.Operation;
	(** pre: v # NIL **)
		VAR seq: Sequencers.Sequencer;
	BEGIN
		ASSERT(v # NIL, 20);
		seq := SeqOf(v);
		IF seq # NIL THEN RETURN seq.LastOp(v) ELSE RETURN NIL END
	END LastOp;

	PROCEDURE Bunch* (v: View);
	(** pre: v # NIL **)
		VAR seq: Sequencers.Sequencer;
	BEGIN
		ASSERT(v # NIL, 20);
		seq := SeqOf(v); ASSERT(seq # NIL, 21);
		seq.Bunch(v)
	END Bunch;

	PROCEDURE StopBunching* (v: View);
	(** pre: v # NIL **)
		VAR seq: Sequencers.Sequencer;
	BEGIN
		ASSERT(v # NIL, 20);
		seq := SeqOf(v);
		IF seq # NIL THEN seq.StopBunching END
	END StopBunching;

	PROCEDURE EndScript* (v: View; script: Stores.Operation);
	(** pre: (script # NIL) iff (v.seq # NIL) **)
		VAR seq: Sequencers.Sequencer;
	BEGIN
		ASSERT(v # NIL, 20);
		seq := SeqOf(v);
		IF seq # NIL THEN ASSERT(script # NIL, 21); seq.EndScript(script)
		ELSE ASSERT(script = NIL, 22)
		END
	END EndScript;


	PROCEDURE BeginModification* (type: INTEGER; v: View);
		VAR seq: Sequencers.Sequencer;
	BEGIN
		ASSERT(v # NIL, 20);
		seq := SeqOf(v);
		IF seq # NIL THEN seq.BeginModification(type, v) END
	END BeginModification;

	PROCEDURE EndModification* (type: INTEGER; v: View);
		VAR seq: Sequencers.Sequencer;
	BEGIN
		ASSERT(v # NIL, 20);
		seq := SeqOf(v);
		IF seq # NIL THEN seq.EndModification(type, v) END
	END EndModification;

	PROCEDURE SetDirty* (v: View);
		VAR seq: Sequencers.Sequencer;
	BEGIN
		ASSERT(v # NIL, 20);
		seq := SeqOf(v);
		IF seq # NIL THEN seq.SetDirty(TRUE) END
	END SetDirty;


	PROCEDURE Domaincast* (domain: Stores.Domain; VAR msg: Message);
		VAR g: INTEGER; seq: ANYPTR;
	BEGIN
		IF domain # NIL THEN
			seq := domain.GetSequencer();
			IF seq # NIL THEN
				msg.view := NIL;
				g := Kernel.trapCount + 1;
				IF domainGuard > 0 THEN ASSERT(domainGuard # g, 20) END;
				domainGuard := g;
				seq(Sequencers.Sequencer).Handle(msg);
				domainGuard := 0
			END
		END
	END Domaincast;

	PROCEDURE Broadcast* (v: View; VAR msg: Message);
		VAR seq: Sequencers.Sequencer; g: INTEGER;
	BEGIN
		ASSERT(v # NIL, 20);
		msg.view := v;
		seq := SeqOf(v);
		IF seq # NIL THEN
			g := Kernel.trapCount + 1;
			IF v.guard > 0 THEN ASSERT(v.guard # g, 21) END;
			v.guard := g;
			seq.Handle(msg);
			v.guard := 0
		END
	END Broadcast;


	PROCEDURE Update* (v: View; rebuild: BOOLEAN);
		VAR upd: UpdateMsg; 
	BEGIN
		ASSERT(v # NIL, 20);
		upd.scroll := FALSE; upd.rebuild := rebuild; upd.all := TRUE;
		Broadcast(v, upd)
	END Update;

	PROCEDURE UpdateIn* (v: View; l, t, r, b: INTEGER; rebuild: BOOLEAN);
		VAR upd: UpdateMsg;
	BEGIN
		ASSERT(v # NIL, 20);
		upd.scroll := FALSE; upd.rebuild := rebuild; upd.all := FALSE;
		upd.l := l; upd.t := t; upd.r := r; upd.b := b;
		Broadcast(v, upd)
	END UpdateIn;

	PROCEDURE Scroll* (v: View; dx, dy: INTEGER);
		VAR scroll: UpdateMsg;
	BEGIN
		ASSERT(v # NIL, 20); ASSERT(v.Domain() # NIL, 21);
		RestoreDomain(v.Domain());
		scroll.scroll := TRUE; scroll.dx := dx; scroll.dy := dy;
		Broadcast(v, scroll)
	END Scroll;

	PROCEDURE CopyOf* (v: View; shallow: BOOLEAN): View;
		VAR w, a: View; op: INTEGER; b: Alien;
	BEGIN
		ASSERT(v # NIL, 20);
		IF ~(handler IN v.bad) THEN
			a := actView; op := actOp; actView := NIL; actOp := handler;
			IF shallow THEN copyModel := v.ThisModel() END;
			actView := v;
			w := Stores.CopyOf(v)(View);
			actView := a; actOp := op
		ELSE
			NEW(b); w := b; w.bad := {handler..externalize}
		END;
		IF shallow THEN Stores.Join(w, v) END;
		RETURN w
	END CopyOf;

	PROCEDURE CopyWithNewModel* (v: View; m: Models.Model): View;
		VAR w, a: View; op: INTEGER; b: Alien; fm: Models.Model;
	BEGIN
		ASSERT(v # NIL, 20);
		fm := v.ThisModel(); ASSERT(fm # NIL, 21);
		ASSERT(m # NIL, 22);
		ASSERT(Services.SameType(m, fm), 23);
		IF ~(handler IN v.bad) THEN
			a := actView; op := actOp; actView := v; actOp := handler;
			copyModel := m;
			w := Stores.CopyOf(v)(View);
			actView := a; actOp := op
		ELSE
			NEW(b); w := b; w.bad := {handler..externalize}
		END;
		RETURN w
	END CopyWithNewModel;

	PROCEDURE ReadView* (VAR rd: Stores.Reader; OUT v: View);
		VAR st: Stores.Store; a: Alien;
	BEGIN
		rd.ReadStore(st);
		IF st = NIL THEN
			v := NIL
		ELSIF st IS Stores.Alien THEN
			NEW(a);
			a.store := st(Stores.Alien); Stores.Join(a, a.store);
			v := a
		ELSE
			v := st(View)
		END
	END ReadView;

	PROCEDURE WriteView* (VAR wr: Stores.Writer; v: View);
		VAR a: TrapAlien; av: View; op: INTEGER;
	BEGIN
		IF v = NIL THEN wr.WriteStore(v)
		ELSIF externalize IN v.bad THEN NEW(a); wr.WriteStore(a)
		ELSIF v IS Alien THEN wr.WriteStore(v(Alien).store)
		ELSE
			av := actView; op := actOp; actView := v; actOp := externalize;
			wr.WriteStore(v);
			actView := av; actOp := op
		END
	END WriteView;


	(* frames *)
	
	PROCEDURE SetClip (f: Frame; l, t, r, b: INTEGER);
		VAR u: INTEGER;
	BEGIN
		ASSERT(f.rider # NIL, 20); ASSERT(l <= r, 21); ASSERT(t <= b, 22);
		u := f.unit;
		f.rider.SetRect((l + f.gx) DIV u, (t + f.gy) DIV u, (r + f.gx) DIV u, (b + f.gy) DIV u);
		f.l := l; f.t := t; f.r := r; f.b := b
	END SetClip;

	PROCEDURE Close (f: Frame);	(* AD: RootFrames may only be closed by Views' clients thru .Close; in Views, Close is never called on a RootFrame except from RootFrame.Close. Instead, when RootFrames need to be 'closed', they are 'obscured', cf. Obscure *)
	BEGIN 
		IF f.state # closed (* recursion guard from RootFrame.Close *) THEN
			f.Close
		END;
		f.state := closed;
		f.up := NIL; f.down := NIL; f.next := NIL; f.view := NIL;
		f.ConnectTo(NIL);
	END Close;

	PROCEDURE IsObscured* (r: RootFrame): BOOLEAN;
	(** Returns TRUE if r is obscured - that is, 
		not visible because overlayed by other frames *)
	BEGIN
		ASSERT(r # NIL, 20); ASSERT(r.rider # NIL, 21);
		RETURN (r.gx = magic) & (r.gy = magic)
	END IsObscured;

	PROCEDURE Obscure* (r: RootFrame);
	(** Instead of being closed (in response to Update(?, rebuildFrame)), root frames are 'obscured': disconnected from their embedding frame and siblings, and moved to a magic offset. Obscure is soecifically for use in StdTiles *)
	BEGIN
		r.up := NIL;
		r.rider.SetRect(magic, magic, magic, magic);
		r.SetOffset(magic, magic)
	END Obscure;	(* it is the responsibility of some caller to r.next := NIL *) 


	PROCEDURE AdaptFrameTo (f: Frame; orgX, orgY: INTEGER);
	(** Adapt f's proper coordinates (global and relative position, clipping rectangle) and coordinates of it's embedded frames to f's origin (orgX, orgY) with respect to f's environment (port or embedding frame) *)
		VAR e, g, p, q: Frame; port: Ports.Port;
			w, h,  pl, pt, pr, pb,  gl, gt, gr, gb,  gx, gy: INTEGER;
	BEGIN 
		(* pre: environment (i.e. parent frame / port) has already been set up *)
		ASSERT(f.view # NIL, 20); ASSERT(f.view.context # NIL, 21);
		f.x := orgX; f.y := orgY;	(* set new origin *)
		g := f.up; 
		IF g # NIL THEN	(* parent frame is environment *)
			f.gx0 := g.gx + orgX; f.gy0 := g.gy + orgY;
			f.SetOffset(f.gx0 - f.sx, f.gy0 - f.sy);
			pl := g.gx + g.l; pt := g.gy + g.t; pr := g.gx + g.r; pb := g.gy + g.b
		ELSE	(* port is environment *)
			f.gx0 := orgX; f.gy0 := orgY;
				(* assumes that when port is environment, f.gx = 0, f.gy = 0 *)
			f.SetOffset(f.gx0 - f.sx, f.gy0 - f.sy);	(* i.e. f.gx := f.gx0 - f.sx *)
			port := f.rider.Base();
			port.GetSize(w, h);
			pl := 0; pt := 0; pr := w * f.unit; pb := h * f.unit
		END;
		(* (pl, pt, pr, pb) is parent clipping rectangle, in global coordinates, and in units *)
		gx := f.gx; gy := f.gy; f.view.context.GetSize(w, h); 
		gl := gx; gt := gy; gr := gx + w; gb := gy + h;
		(* (gl, gt, gr, gb) is desired clipping rectangle, in global coordinates, and in units *)
		IF gl < pl THEN gl := pl END;
		IF gt < pt THEN gt := pt END;
		IF gr > pr THEN gr := pr END;
		IF gb > pb THEN gb := pb END;
		IF (gl >= gr) OR (gt >= gb) THEN gr := gl; gb := gt END;
		SetClip(f, gl - gx + f.sx, gt - gy + f.sy, gr - gx + f.sx, gb - gy + f.sy);
		(* (f.l, f.t, f.r, f.b) is final clipping rectangle, in local coordinates, and in units *)
		e := f.down; f.down := NIL; p := NIL;
		WHILE e # NIL DO	(* adapt child frames *)
			q := e.next; e.next := NIL;
			AdaptFrameTo(e, e.x, e.y);
			IF e.l = e.r THEN	(* empty child frame: remove *)
				WITH e: RootFrame DO Obscure(e) ELSE Close(e) END
			ELSE	(* insert in new frame list *)
				IF p = NIL THEN f.down := e ELSE p.next := e END;
				p := e
			END;
			e := q
		END
		(* post: frame is set; child frames are set, nonempty, and clipped to frame *)
	END AdaptFrameTo;

	PROCEDURE SetRoot* (root: RootFrame; view: View; front: BOOLEAN; flags: SET);
	(** Connects root and view (if they are not yet connected) and sets auxiliary attributes front (indicating that this frame/view is on the front path) and flags (interpretation is left to clients). *)
	BEGIN
		ASSERT(root # NIL, 20); ASSERT(root.rider # NIL, 21);
		ASSERT(view # NIL, 22); ASSERT(view.context # NIL, 23);
		ASSERT(view.Domain() # NIL, 24);
		ASSERT(root.state IN {new, open}, 25);
		root.view := view;
		root.front := front; root.mark := TRUE; root.flags := flags;
		root.state := open;
		IF root.update = NIL THEN NEW(root.update); root.update.n := 0 END
	END SetRoot;

	PROCEDURE AdaptRoot* (root: RootFrame);
	BEGIN
		ASSERT(root # NIL, 20);
		IF root.state = open THEN AdaptFrameTo(root, root.x, root.y) END
	END AdaptRoot;

	(** A RootFrame is 'ultimate' if it is not embedded in any other frame. Returns the ultimate root that f is underneath, or f itself, if f is ultimate root. *)
	PROCEDURE UltimateRootOf* (f: Frame): RootFrame;
	BEGIN
		WHILE f.up # NIL DO f := f.up END;
		ASSERT(f IS RootFrame, 100);
		RETURN f(RootFrame)
	END UltimateRootOf;

	PROCEDURE UpdateRoot* (root: RootFrame; l, t, r, b: INTEGER; rebuild: BOOLEAN);
	BEGIN
		ASSERT(root # NIL, 20); ASSERT(root.state = open, 21);
		AddRect(UltimateRootOf(root), root, l, t, r, b, rebuild) 
	END UpdateRoot;

	PROCEDURE RootOf* (f: Frame): RootFrame;
	BEGIN
		ASSERT(f # NIL, 20); ASSERT(f.state = open, 21);
		WHILE ~(f IS RootFrame) DO f := f.up END;
		RETURN f(RootFrame)
	END RootOf;

	PROCEDURE HostOf* (f: Frame): Frame;
		VAR host: Frame;
	BEGIN
		ASSERT(f # NIL, 20);
		IF f IS RootFrame THEN host := NIL ELSE host := f.up END;
		RETURN host
	END HostOf;

	PROCEDURE HostOfRoot* (f: RootFrame): Frame;
	BEGIN RETURN f.up
	END HostOfRoot;

	PROCEDURE IsPrinterFrame* (f: Frame): BOOLEAN;
		VAR p: Ports.Port;
	BEGIN
		ASSERT(f # NIL, 20); ASSERT(f.state = open, 21);
		p := f.rider.Base();
		RETURN Ports.IsPrinterPort(p)
	END IsPrinterFrame;

	PROCEDURE InstallFrame* (host: Frame; view: View; x, y, level: INTEGER; focus: BOOLEAN);
		VAR e, f, g: Frame; w, h,  l, t, r, b: INTEGER; m: Models.Model; std: StdFrame;
			msg: UpdateCachesMsg; a: View; op: INTEGER; 
	BEGIN
		ASSERT(host # NIL, 20); ASSERT(host.state = open, 21);
		ASSERT(view # NIL, 22); ASSERT(view.context # NIL, 23);
		ASSERT(view.Domain() # NIL, 24);
		e := NIL; g := host.down; WHILE (g # NIL) & (g.view # view) DO e := g; g := g.next END;
		IF g = NIL THEN	(* frame for view not yet in child frame list *)
			view.context.GetSize(w, h);
			IF w > MAX(INTEGER) DIV 2 THEN w := MAX(INTEGER) DIV 2 END;
			IF h > MAX(INTEGER) DIV 2 THEN h := MAX(INTEGER) DIV 2 END;
			l := x; t := y; r := x + w; b := y + h;
			(* (l, t, r, b) is child frame rectangle, in local coordinates, and in units *)
			IF (l < host.r) & (t < host.b) & (r > host.l) & (b > host.t) THEN	(* visible *)
				IF rootToInstall # NIL THEN
					g := rootToInstall; rootToInstall := NIL
				ELSE
					g := NIL; view.GetNewFrame(g); 
					IF g = NIL THEN NEW(std); g := std END;
					ASSERT(~(g IS RootFrame), 100)
				END;
				e := NIL; f := host.down; WHILE (f # NIL) & (f.level <= level) DO e := f; f := f.next END;
				IF e = NIL THEN g.next := host.down; host.down := g ELSE g.next := e.next; e.next := g END;
				g.down := NIL; g.up := host; g.level := level;
				g.view := view;
				g.ConnectTo(host.rider.Base());
				g.state := open;
				AdaptFrameTo(g, x, y);
				IF ~(handler IN view.bad) THEN
					a := actView; op := actOp; actView := view; actOp := handler;
					view.HandleViewMsg(g, msg);
					actView := a; actOp := op
				END;
				m := view.ThisModel();
				IF m # NIL THEN view.era := Models.Era(m) END;
			END
		ELSE
			IF g.level # level THEN	(* adapt to modified z-order *)
				IF e = NIL THEN host.down := g.next ELSE e.next := g.next END;
				e := NIL; f := host.down; WHILE (f # NIL) & (f.level <= level) DO e := f; f := f.next END;
				IF e = NIL THEN g.next := host.down; host.down := g ELSE g.next := e.next; e.next := g END;
				g.level := level
			END;
			AdaptFrameTo(g, x, y)
			(* possibly optimize: don't call Adapt if x=g.x, y=g.y, "host.era=g.era" *)
		END;
		IF (g # NIL) & ~(g IS RootFrame) & (g.state = open) THEN
			(* AD: for RootFrame,  .front and .mark setting is done in SetRoot *)
			IF focus THEN
				g.front := host.front; g.mark := host.mark
			ELSE
				g.front := FALSE; g.mark := FALSE
			END
		END
	END InstallFrame;
	
	PROCEDURE RemoveAll (f: Frame);
		VAR g, p: Frame;
	BEGIN 
		g := f.down;
		WHILE g # NIL DO p := g.next; RemoveAll(g);
			WITH g: RootFrame DO g.next := NIL; Obscure(g) ELSE Close(g) END;
			g := p
		END;
		f.down := NIL
	END RemoveAll;

	PROCEDURE RemoveFrame* (host, f: Frame);
	(* f.up = host => f.up # NIL: f is never an UltimateRoot *)
		VAR g, h: Frame;
	BEGIN
		ASSERT(host # NIL, 20); 
		ASSERT(host.state = open, 21);
		ASSERT(f # NIL, 22); ASSERT(f.up = host, 23);
		g := host.down; h := NIL;
		WHILE (g # NIL) & (g # f) DO h := g; g := g.next END;
		ASSERT(g = f, 24);
		IF h = NIL THEN host.down := f.next ELSE h.next := f.next END;
		RemoveAll(f); WITH f: RootFrame DO f.next := NIL; Obscure(f) ELSE Close(f) END
	END RemoveFrame;

	PROCEDURE RemoveFrames* (host: Frame; l, t, r, b: INTEGER);
		VAR f, g: Frame; gl, gt, gr, gb: INTEGER;
	BEGIN
		ASSERT(host # NIL, 20); 
		ASSERT(host.state = open, 21);
		IF l < host.l THEN l := host.l END;
		IF t < host.t THEN t := host.t END;
		IF r > host.r THEN r := host.r END;
		IF b > host.b THEN b := host.b END;
		IF (l < r) & (t < b) THEN
			gl := l + host.gx; gt := t + host.gy; gr := r + host.gx; gb := b + host.gy;
			f := host.down;
			WHILE f # NIL DO
				g := f; f := f.next;
				IF (gl < g.r + g.gx) & (g.l + g.gx < gr) & (gt < g.b + g.gy) & (g.t + g.gy < gb) THEN
					RemoveFrame(host, g)
				END
			END
		END
	END RemoveFrames;
	
	PROCEDURE MergeUpdateRegions (to, from: Region);
		VAR i: INTEGER;
	BEGIN 
		i := 0; WHILE i < from.n DO
			Add(to, from.r[i].v, from.r[i].rebuild, from.r[i].l, from.r[i].t, from.r[i].r, from.r[i].b);
			INC(i)
		END;
		from.n := 0;
	END MergeUpdateRegions;

	PROCEDURE InstallRoot* (f: Frame; root: RootFrame; x, y, level: INTEGER; focus: BOOLEAN);
	(** Similar to InstallFrame, but for RootFrames. Installs root into f at position x, y at level, or, if root is installed in f already, adjusts it's position, level and focus path flag. If focus is set, root becomes the focus path in f. *)
	BEGIN ASSERT(root # NIL, 20); ASSERT(f # NIL, 26);
		ASSERT(root.state = open, 21); ASSERT(root.view # NIL, 22);
		ASSERT(f.state = open, 23); ASSERT(f.rider # NIL, 24); 
		IF root.rider = NIL THEN
			root.ConnectTo(f.rider.Base())
		ELSE
			ASSERT(root.rider.Base() = f.rider.Base(), 25)
		END;
		rootToInstall := root; 
		root.up := f (* AdaptFrameTo depends on this!! *);
		root.SetOffset(f.gx + x, f.gy + y);
		AdaptRoot(root);
		InstallFrame(f, root.view, x, y, level, focus); 
		MergeUpdateRegions(UltimateRootOf(root).update, root.update);
		ASSERT(root.update.n = 0, 60);
		rootToInstall := NIL
	END InstallRoot;

	PROCEDURE ThisFrame* (host: Frame; view: View): Frame;
		VAR g: Frame;
	BEGIN
		ASSERT(host # NIL, 20); 
		ASSERT(host.state = open, 21);
		g := host.down; WHILE (g # NIL) & (g.view # view) DO g := g.next END;
		RETURN g
	END ThisFrame;

	PROCEDURE FrameAt* (host: Frame; x, y: INTEGER): Frame;
	(** return frontmost sub-frame of host that contains (x, y) **)
		VAR g, h: Frame;
	BEGIN
		ASSERT(host # NIL, 20); 
		ASSERT(host.state = open, 21);
		g := host.down; h := NIL; INC(x, host.gx); INC(y, host.gy);
		WHILE g # NIL DO
			IF (g.gx + g.l <= x) & (x < g.gx + g.r) & (g.gy + g.t <= y) & (y < g.gy + g.b) THEN
				h := g
			END;
			g := g.next
		END;
		RETURN h
	END FrameAt;

	PROCEDURE ShiftFrames (f: Frame; dx, dy: INTEGER);
		VAR g, h: Frame;
	BEGIN
		g := f.down;
		WHILE g # NIL DO
			h := g; g := g.next;
			AdaptFrameTo(h, h.x + dx, h.y + dy);
			IF h.l = h.r THEN RemoveFrame(f, h) END
		END
	END ShiftFrames;

	PROCEDURE UpdateExposedArea (f: Frame; dx, dy: INTEGER);
		VAR root: RootFrame;
	BEGIN
		root := UltimateRootOf(f);
		IF dy > 0 THEN
			AddRect(root, f, f.l, f.t, f.r, f.t + dy, keepFrames);
			IF dx > 0 THEN
				AddRect(root, f, f.l, f.t + dy, f.l + dx, f.b, keepFrames)
			ELSE
				AddRect(root, f, f.r + dx, f.t + dy, f.r, f.b, keepFrames)
			END
		ELSE
			AddRect(root, f, f.l, f.b + dy, f.r, f.b, keepFrames);
			IF dx > 0 THEN
				AddRect(root, f, f.l, f.t, f.l + dx, f.b + dy, keepFrames)
			ELSE
				AddRect(root, f, f.r + dx, f.t, f.r, f.b + dy, keepFrames)
			END
		END
	END UpdateExposedArea;

	PROCEDURE ScrollFrame (f: Frame; dx, dy: INTEGER);
		VAR g: Frame; u, dx0, dy0: INTEGER; bitmapScrolling: BOOLEAN; msg: ScrollClassMsg;
	BEGIN
		g := f.up;
		bitmapScrolling := Dialog.isMDI;  (* temporary until bitmap scrolling been fixed *)
		IF (g # NIL) THEN
			WHILE bitmapScrolling & (g.up # NIL) DO
				msg.allowBitmapScrolling := FALSE; g.view.HandleViewMsg(g, msg);
				bitmapScrolling := bitmapScrolling & msg.allowBitmapScrolling;
				g  := g.up
			END
			(* by default, only root documents and their root views may be bitmap-scrolled: (f.up = NIL) OR (f.up.up = NIL) *)
		END;
		IF bitmapScrolling THEN
			u := f.unit; dx0 := dx; dy0 := dy;
			INC(dx, f.sx); INC(dy, f.sy); DEC(f.l, f.sx); DEC(f.t, f.sy); DEC(f.r, f.sx); DEC(f.b, f.sy);
			f.sx := dx MOD u; f.sy := dy MOD u;
			DEC(dx, f.sx); DEC(dy, f.sy); INC(f.l, f.sx); INC(f.t, f.sy); INC(f.r, f.sx); INC(f.b, f.sy);
			f.SetOffset(f.gx0 - f.sx, f.gy0 - f.sy);
			ShiftFrames(f, dx0, dy0);
			f.Scroll(dx, dy);
			UpdateExposedArea(f, dx, dy)
		ELSE AddRect(UltimateRootOf(f), f, f.l, f.t, f.r, f.b, keepFrames)
			;ShiftFrames(f, dx, dy)	(* need better calculation of shift vector ^ *)
			(* experimental: don't dispose/close frames until you absolutely have to; rather, shift subframes; when shifting, all frames that are moved 'beyond' f's clip rect should be disposed and closed. (I hope they are; maybe they're just moved beyond the clip rect?) *)
		END
	END ScrollFrame;
	

	(* AD: it looks to me like root views (typically Documents) don't get their .era initialized (v.era := Models.Era(m) is done in InstallFrame, which is never called on root frames; instead, SetRoot is called on root frames, but it doesn't initialize v.era). So, my question is then, How are model messages delivered to root views, and why doesn't BMM↓ trap in halt 100? *)
	PROCEDURE BroadcastModelMsg* (f: Frame; VAR msg: Models.Message);
		VAR v, a: View; send: BOOLEAN; op: INTEGER;
	BEGIN
		ASSERT(f # NIL, 20); ASSERT(f.state = open, 21);
		v := f.view;
		IF ~(handler IN v.bad) THEN
			a := actView; op := actOp; actView := v; actOp := handler;
			IF msg.model # NIL THEN
				IF (msg.model = v.ThisModel()) & (msg.era > v.era) THEN
					send := (msg.era - v.era = 1);
					v.era := msg.era;
					IF ~send THEN
						Log.synch := FALSE;
						HALT(100)
					END
				ELSE send := FALSE
				END
			ELSE send := TRUE
			END;
			IF send THEN
				WITH msg: Models.NeutralizeMsg DO
					v.Neutralize
				ELSE
					 v.HandleModelMsg(msg)
				END
			END;
			actView := a; actOp := op
		END;
		f := f.down; 
		WHILE f # NIL DO
			IF ~(f IS RootFrame) THEN
				BroadcastModelMsg(f, msg)
			END;
			f := f.next
		END
	END BroadcastModelMsg;

	PROCEDURE HandleUpdateMsg (f: Frame; VAR msg: UpdateMsg);
		VAR root: RootFrame; g: Frame; l, t, r, b,  dx, dy: INTEGER;
	BEGIN
		root := UltimateRootOf(f);
		IF msg.scroll THEN
			IF root.update.n = 0 THEN
				ScrollFrame(f, msg.dx, msg.dy)
			ELSE
				AddRect(root, f, f.l, f.t, f.r, f.b, msg.rebuild)
			END
		ELSE
			IF msg.all THEN
				IF f # root THEN g := f.up ELSE g := root END;
				dx := f.gx - g.gx; dy := f.gy - g.gy;
				AddRect(root, g, f.l + dx, f.t + dy, f.r + dx, f.b + dy, msg.rebuild)
					(* AD: why does it add embedding frame (and view!) to rebuild region if msg.all = TRUE? I guess that's in order to let the embedding view NOT install a frame for view. So this is a little more efficient than doing rebuildFrames on v's contaier. Besides, v may not know it's container. This is cool about Views! Good job developers! However, this is not mentioned in the documentation, and may lead to unexpected behaviour smtms *)
			ELSE
				l := msg.l; t := msg.t; r := msg.r; b := msg.b;
				IF l < f.l THEN l := f.l END;
				IF t < f.t THEN t := f.t END;
				IF r > f.r THEN r := f.r END;
				IF b > f.b THEN b := f.b END;
				AddRect(root, f, l, t, r, b, msg.rebuild)
			END
		END
	END HandleUpdateMsg;

	PROCEDURE BroadcastViewMsg* (f: Frame; VAR msg: Message);
		VAR v, a: View; op: INTEGER;
	BEGIN
		ASSERT(f # NIL, 20); ASSERT(f.state = open, 21);
		v := f.view;
		IF (msg.view = v) OR (msg.view = NIL) THEN
			WITH msg: UpdateMsg DO
				HandleUpdateMsg(f, msg)
			ELSE
				IF ~(handler IN v.bad) THEN
					a := actView; op := actOp; actView := v; actOp := handler;
					v.HandleViewMsg(f, msg);
					actView := a; actOp := op
				END
			END
		END;
		IF msg.view # v THEN
			f := f.down; 
			WHILE f # NIL DO
				IF ~(f IS RootFrame) THEN BroadcastViewMsg(f, msg) END;
				f := f.next
			END
		END
	END BroadcastViewMsg;
	

	PROCEDURE ForwardCtrlMsg* (f: Frame; VAR msg: CtrlMessage);
		CONST pre = 0; translate = 1; backoff = 2; final = 3;
		VAR v, focus, a: View; g, h: Frame; op: INTEGER; req: BOOLEAN;
	BEGIN
		ASSERT(f # NIL, 20); ASSERT(f.state = open, 21); ASSERT(f.rider # NIL, 22);
		v := f.view;
		focus := NIL; IF f IS RootFrame THEN g := NIL ELSE g := f.up END; req := FALSE;
		HandleCtrlMsg(pre, f, g, msg, f.mark, f.front, req); 
		IF ~(handler IN v.bad) THEN
			a := actView; op := actOp; actView := v; actOp := handler;
			v.HandleCtrlMsg(f, msg, focus);
			actView := a; actOp := op
		END;
		IF focus # NIL THEN	(* propagate msg to another view *)
			(*
			IF (f.focus # NIL) & (f.focus.view = focus) & (f.focus.up # NIL) THEN (* cache hit *)
				(* the field .focus may point to a frame that used to display focus, but was later removed. Such a .focus is invalid, and sending the event down to that frame would result user seeing f.view become unresponsive - because all controller messages would be sent down to the wrong frame. This does not happen because (f.focus.view = focus) is checked: when a frame is removed, it's .view is reset, and thus this checks that the cached frame has not been removed (as well as that the cached frame displays the focus view). With my amendments, however, now RootFrames may be embedded, and thus .focus might point to a RootFrame. RootFrames are never removed, and their .view always points to their view, it's never reset to NIL. There is no easy way to check if the RootFrame has actually been removed (maybe RootFrame.up = NIL would help?); thus, I just 'mistrust' all RootFrames: the third condition ~(f.focus IS RootFrame) guarantees that for RootFrames the cache is never used, but an actual frame is fetched from f.down  *)
				h := f.focus
			ELSE	(* cache miss *)
				h := f.down; WHILE (h # NIL) & (h.view # focus) DO h := h.next END
			END;
			*)
			h := f.down; WHILE (h # NIL) & (h.view # focus) DO h := h.next END;
			IF h # NIL THEN 
				HandleCtrlMsg(translate, f, h, msg, f.mark, f.front, req);
				f.focus := h; ForwardCtrlMsg(h, msg); 
				HandleCtrlMsg(backoff, f, g, msg, f.mark, f.front, req)
			END
		ELSE
			HandleCtrlMsg(final, f, g, msg, f.mark, f.front, req)
		END;
		IF req & (g # NIL) THEN g.view.ConsiderFocusRequestBy(f.view) END
	END ForwardCtrlMsg;

	PROCEDURE RestoreFrame (f: Frame; l, t, r, b: INTEGER);
		VAR rd: Ports.Rider; g: Frame; v, a: View; op: INTEGER;
			u, w, h,  cl, ct, cr, cb,  dx, dy: INTEGER; col: Ports.Color; 
	BEGIN	WITH f: RootFrame DO ASSERT(~IsObscured(f), 20) ELSE END;
		IF l < f.l THEN l := f.l END;
		IF t < f.t THEN t := f.t END;
		IF r > f.r THEN r := f.r END;
		IF b > f.b THEN b := f.b END;
		IF (l < r) & (t < b) THEN	(* non-empty rectangle to be restored *)
			v := f.view; rd := f.rider; u := f.unit;
			rd.GetRect(cl, ct, cr, cb);	(* save clip rectangle *)
			rd.SetRect((f.gx + l) DIV u, (f.gy + t) DIV u, (f.gx + r) DIV u, (f.gy + b) DIV u);
			IF ~(restore IN v.bad) THEN
				a := actView; op := actOp; actView := v; actOp := restore;
				col := transparent; v.GetBackground(col);
				IF col # transparent THEN f.DrawRect(l, t, r, b, Ports.fill, col) END;
				v.Restore(f, l, t, r, b);
				g := f.down;
				WHILE g # NIL DO	(* loop over all subframes to handle overlaps *)
					dx := f.gx - g.gx; dy := f.gy - g.gy;
					
					RestoreFrame(g, l + dx, t + dy, r + dx, b + dy);
					
					g := g.next
				END;
				v.RestoreMarks(f, l, t, r, b);
				actView := a; actOp := op
			END;
			IF v.bad # {} THEN
				IF externalize IN v.bad THEN
					u := f.dot; v.context.GetSize(w, h);
					f.DrawLine(0, 0, w - u, h - u, u, Ports.grey75);
					f.DrawLine(w - u, 0, 0, h - u, u, Ports.grey75)
				END;
				f.MarkRect(l, t, r, b, Ports.fill, Ports.dim25, Ports.show)
			END;
			IF cl # magic THEN
				rd.SetRect(cl, ct, cr, cb)	(* restore current clip rectangle *)
			END
		END
	END RestoreFrame;

	PROCEDURE RestoreRoot* (root: RootFrame; l, t, r, b: INTEGER);
	(** Restore frame tree starting with root[l, t, r, b] *)
		VAR port: Ports.Port; rd: Ports.Rider; ur: RootFrame; dx, dy: INTEGER;
			u,  gl, gt, gr, gb: INTEGER; col: Ports.Color;
	BEGIN
		ASSERT(root # NIL, 20); ASSERT(root.state = open, 21);
		ASSERT(~IsObscured(root), 23);
		IF root.up # NIL THEN
			ur := UltimateRootOf(root); ASSERT(~IsObscured(ur), 24);
			dx := root.gx - ur.gx; dy := root.gy - ur.gy; 
			INC(l, dx); INC(t, dy); INC(r, dx); INC(b, dy);
			root := ur
		END;
		IF l < root.l THEN l := root.l END;
		IF t < root.t THEN t := root.t END;
		IF r > root.r THEN r := root.r END;
		IF b > root.b THEN b := root.b END;
		IF (l < r) & (t < b) THEN
			u := root.unit;
			gl := l + root.gx; gt := t + root.gy; gr := r + root.gx; gb := b + root.gy;
			rd := root.rider; port := rd.Base();
			actFrame := root;
			IF ~IsPrinterFrame(root) THEN 
				(* if a root is obscured, it SHOULD NOT open a buffer! An obscured root will have it's frame tree built, but will not be drawn because it's rider's clipping rectangle is set to zero rect (and cliprects of it's embedees are contained within their embedder's cliprect). However, if a buffer is open, in my case, a GTK buffer, it is pre-filled by GTK with widget's (DrawingArea's) default color. When the buffer is closed, this background color box appears on top of Blackbox's window. (It is in my case quickly overwritten by BB views, but the grey box flickers and makes it look upprofessional) *)
				port.OpenBuffer(gl DIV u, gt DIV u, gr DIV u, gb DIV u) 
			END;
			col := transparent; root.view.GetBackground(col);
			IF (col = transparent) & (root.up = NIL) THEN col := Ports.background END;
			RestoreFrame(root, l, t, r, b);
			IF ~IsPrinterFrame(root) THEN port.CloseBuffer END;
			actFrame := NIL
		END
	END RestoreRoot;

	(* k8: slightly more efficient variant of RestoreFrame *)
	PROCEDURE RestoreFrame2 (f: Frame; l, t, r, b: INTEGER; depth: INTEGER);
	VAR
		rd: Ports.Rider;
		g: Frame;
		v, a: View;
		op: INTEGER;
		dx, dy: INTEGER;
		col: Ports.Color;
	BEGIN
		WITH f: RootFrame DO ASSERT(~IsObscured(f), 20) ELSE END;
		IF l < f.l THEN l := f.l END;
		IF t < f.t THEN t := f.t END;
		IF r > f.r THEN r := f.r END;
		IF b > f.b THEN b := f.b END;
		IF (l < r) & (t < b) THEN	(* non-empty rectangle to be restored *)
			v := f.view; rd := f.rider;
			IF ~(restore IN v.bad) THEN
				a := actView; op := actOp; actView := v; actOp := restore;
				col := transparent; v.GetBackground(col);
				v.Restore(f, l, t, r, b);
				g := f.down;
				WHILE g # NIL DO	(* loop over all subframes to handle overlaps *)
					dx := f.gx - g.gx; dy := f.gy - g.gy;
					RestoreFrame2(g, l + dx, t + dy, r + dx, b + dy, depth + 1);
					g := g.next
				END;
				v.RestoreMarks(f, l, t, r, b);
				actView := a; actOp := op
			END
		END
	END RestoreFrame2;

	(* k8: this is actually an internal procedure, but some modules need to call it.
		DO NOT call it from your code, otherwise Bad Things will happen! *)
	(* this procedure sets empty clip rect, and restores the frame. it is intended to be used
		when a new window just created and needs to have its frame tree built. *)
	PROCEDURE BuildFrameTree* (root: RootFrame; magicMove: BOOLEAN);
	VAR frm: Frame;
	BEGIN
		ASSERT(root # NIL, 20); ASSERT(root.state = open, 21);
		ASSERT(root.rider # NIL, 22); ASSERT(root.up = NIL, 23);
		IF (root.l < root.r) & (root.t < root.b) THEN
			(* need to move obscured root, otherwise restoration will fail *)
			ASSERT(hMagic + (root.r - root.l) > hMagic, 100);
			ASSERT(hMagic + (root.b - root.t) > hMagic, 101);
			IF magicMove THEN root.SetOffset(hMagic, hMagic) END;
			root.rider.SetRect(hMagic, hMagic, hMagic, hMagic);
			(* now we can restore it *)
			RestoreFrame2(root, root.l, root.t, root.r, root.b, 0);
			(* find and set focused frame; we need this to make PollFocusMsg work *)
			IF root.focus = NIL THEN
				frm := root.down;
				WHILE (frm # NIL) & (frm.focus # NIL) DO frm := frm.next END;
				IF frm = NIL THEN frm := root.down END;
				IF (frm # NIL) & (frm.view # NIL) THEN
					root.view.ConsiderFocusRequestBy(frm.view)
				END;
			END
			(* no need to move the windowd back, we need "unreally obscured" flag *)
		END
	END BuildFrameTree;

	PROCEDURE HasAnyFrames* (root: RootFrame): BOOLEAN;
	BEGIN ASSERT(root # NIL, 20); RETURN (root.down # NIL)
	END HasAnyFrames;

	PROCEDURE ThisCand (f: Frame; v: View): Frame;
	(* find frame g with g.view = v *)
		VAR g: Frame;
	BEGIN
		WHILE (f # NIL) & (f.view # v) DO
			g := ThisCand(f.down, v);
			IF g # NIL THEN f := g ELSE f := f.next END
		END;
		RETURN f
	END ThisCand;

	PROCEDURE ValidateRoot* (root: RootFrame);
	(** Composite all regions under root requested for update with Views.Update and restore the resulting composition *)
		VAR rgn: Region; f: Frame; v: View; i, n: INTEGER; ur: RootFrame;
	BEGIN
		ASSERT(root # NIL, 20); ASSERT(root.state = open, 21);
		ur := UltimateRootOf(root); 
		IF ur # root THEN ASSERT(root.update.n = 0, 100); root := ur END;
		rgn := root.update; n := rgn.n; rgn.n := 0; i := 0;
		WHILE i < n DO
			IF rgn.r[i].rebuild THEN
				v := rgn.r[i].v;
				IF v # NIL THEN f := ThisCand(root, v) ELSE f := NIL END;
				IF f = NIL THEN f := root END;
				RemoveFrames(f, rgn.r[i].l - f.gx, rgn.r[i].t - f.gy, rgn.r[i].r - f.gx, rgn.r[i].b - f.gy)
			END;
			INC(i)
		END;
		i := 0;
		WHILE i < n DO
			RestoreRoot(root, rgn.r[i].l - root.gx, rgn.r[i].t - root.gy, rgn.r[i].r - root.gx, rgn.r[i].b - root.gy);
			INC(i)
		END
	END ValidateRoot;

	PROCEDURE MarkBordersIn (f: Frame);
		VAR g: Frame; w, h: INTEGER;
	BEGIN
		g := f.down;
		WHILE g # NIL DO
			g.view.context.GetSize(w, h);
			MarkBorder(f, g.view, g.x, g.y, g.x + w, g.y + h);
			MarkBordersIn(g);
			g := g.next
		END
	END MarkBordersIn;

	PROCEDURE MarkBorders* (root: RootFrame);
	BEGIN
		MarkBordersIn(root)
	END MarkBorders;

	PROCEDURE ReadFont* (VAR rd: Stores.Reader; OUT f: Fonts.Font);
		VAR version: INTEGER;
			fingerprint, size: INTEGER; typeface: Fonts.Typeface; style: SET; weight: INTEGER;
	BEGIN
		rd.ReadVersion(0, 0, version);
		rd.ReadInt(fingerprint);
		rd.ReadXString(typeface); rd.ReadInt(size); rd.ReadSet(style); rd.ReadXInt(weight);
		f := Fonts.dir.This(typeface, size, style, weight); ASSERT(f # NIL, 60);
		IF f.IsAlien() THEN
			Stores.Report("#System:AlienFont", typeface, "", "")
		END
	END ReadFont;

	PROCEDURE WriteFont* (VAR wr: Stores.Writer; f: Fonts.Font);
	BEGIN
		ASSERT(f # NIL, 20);
		wr.WriteVersion(0);
		wr.WriteInt(0);
		wr.WriteXString(f.typeface); wr.WriteInt(f.size);
		wr.WriteSet(f.style); wr.WriteXInt(f.weight)
	END WriteFont;


	(** view/file interaction **)

	PROCEDURE Old* (ask: BOOLEAN; VAR loc: Files.Locator;
												VAR name: Files.Name; VAR conv: Converters.Converter): View;
		VAR v: View;
	BEGIN
		ASSERT(ask OR (loc # NIL), 20);
		ASSERT(ask OR (name # ""), 21);
		IF ask THEN
			ASSERT(getSpecHook # NIL, 101);
			getSpecHook.GetIntSpec(loc, name, conv)
		END;
		IF (loc # NIL) & (name # "") THEN
			ASSERT(viewHook # NIL, 100);
			v := viewHook.OldView(loc, name, conv)
		ELSE v := NIL
		END;
		RETURN v
	END Old;
	
	PROCEDURE OldView* (loc: Files.Locator; name: Files.Name): View;
		VAR conv: Converters.Converter;
	BEGIN
		conv := NIL;
		RETURN Old(dontAsk, loc, name, conv)
	END OldView;

	PROCEDURE Register* (view: View; ask: BOOLEAN; VAR loc: Files.Locator;
					VAR name: Files.Name; VAR conv: Converters.Converter; OUT res: INTEGER);
	BEGIN
		ASSERT(viewHook # NIL, 100);
		ASSERT(getSpecHook # NIL, 101);
		ASSERT(view # NIL, 20);
		ASSERT(ask OR (loc # NIL), 22); ASSERT(ask OR (name # ""), 23);
		IF ask OR (loc = NIL) OR (name = "") OR (loc.res = 77) THEN
			getSpecHook.GetExtSpec(view, loc, name, conv)
		END;
		IF (loc # NIL) & (name # "") THEN
			viewHook.RegisterView(view, loc, name, conv); res := loc.res
		ELSE res := 7
		END
	END Register;

	PROCEDURE RegisterView* (view: View; loc: Files.Locator; name: Files.Name);
		VAR res: INTEGER; conv: Converters.Converter;
	BEGIN
		conv := NIL;
		Register(view, dontAsk, loc, name, conv, res)
	END RegisterView;

	(** direct view opening **)

	PROCEDURE Open* (view: View; loc: Files.Locator; name: Files.Name; conv: Converters.Converter);
	BEGIN
		ASSERT(view # NIL, 20); ASSERT((loc = NIL) = (name = ""), 21);
		ASSERT(viewHook # NIL, 100);
		viewHook.Open(view, name, loc, name, conv, FALSE, FALSE, FALSE, FALSE, FALSE)
	END Open;

	PROCEDURE OpenView* (view: View);
	BEGIN
		ASSERT(view # NIL, 20);
		Open(view, NIL, "", NIL)
	END OpenView;

	PROCEDURE OpenAux* (view: View; title: Title);
	BEGIN
		ASSERT(view # NIL, 20); ASSERT(viewHook # NIL, 100);
		IF title = "" THEN title := "#System:untitled" END;
		viewHook.Open(view, title, NIL, "", NIL, FALSE, TRUE, FALSE, TRUE, TRUE)
	END OpenAux;


	(** view producer/consumer decoupling **)

	PROCEDURE Deposit* (view: View);
		VAR q: QueueElem;
	BEGIN
		ASSERT(view # NIL, 20);
		NEW(q); q.view := view;
		IF queue.head = NIL THEN queue.head := q ELSE queue.tail.next := q END;
		queue.tail := q; INC(queue.len)
	END Deposit;

	PROCEDURE Fetch* (OUT view: View);
		VAR q: QueueElem;
	BEGIN
		q := queue.head; ASSERT(q # NIL, 20);
		DEC(queue.len); queue.head := q.next;
		IF queue.head = NIL THEN queue.tail := NIL END;
		view := q.view
	END Fetch;

	PROCEDURE Available* (): INTEGER;
	BEGIN
		RETURN queue.len
	END Available;

	PROCEDURE ClearQueue*;
	BEGIN
		queue.len := 0; queue.head := NIL; queue.tail := NIL;
		actView := NIL	(* HACK! prevents invalidation of view due to trap in Dialog.Call *)
	END ClearQueue;


	(** attach controller framework **)

	PROCEDURE InitCtrl* (p: CtrlMsgHandler);
	BEGIN
		ASSERT(HandleCtrlMsg = NIL, 20); HandleCtrlMsg := p
	END InitCtrl;

	PROCEDURE (h: NotifyHook) Notify (id0, id1: INTEGER; opts: SET);
		VAR msg: NotifyMsg;
	BEGIN
		ASSERT(msgHook # NIL, 100);
		msg.id0 := id0; msg.id1 := id1; msg.opts := opts;
		msgHook.Omnicast(msg)
	END Notify;
	
	PROCEDURE Omnicast* (VAR msg: ANYREC);
	BEGIN
		msgHook.Omnicast(msg)
	END Omnicast;

	PROCEDURE HandlePropMsg* (v: View; VAR msg: PropMessage);
		VAR a: View; op: INTEGER;
	BEGIN
		IF ~(handler IN v.bad) THEN
			a := actView; op := actOp; actView := v; actOp := handler;
			v.HandlePropMsg(msg);
			actView := a; actOp := op
		END
	END HandlePropMsg;
	
	
	(* view invalidation *)
	
	PROCEDURE IsInvalid* (v: View): BOOLEAN;
	BEGIN
		RETURN v.bad # {}
	END IsInvalid;
	
	PROCEDURE RevalidateView* (v: View);
	BEGIN
		v.bad := {};
		Update(v, keepFrames)
	END RevalidateView;
	
	PROCEDURE TrapCleanup;
	BEGIN
		copyModel := NIL;
		IF actView # NIL THEN
			INCL(actView.bad, actOp);
			IF actFrame # NIL THEN
				UpdateRoot(actFrame, actFrame.l, actFrame.t, actFrame.r, actFrame.b, keepFrames);
				actFrame := NIL
			END;
			Update(actView, keepFrames);
			actView := NIL
		END
	END TrapCleanup;
	
	PROCEDURE Init;
		VAR h: NotifyHook;
	BEGIN
		NEW(h); Dialog.SetNotifyHook(h);
		domainGuard := 0; ClearQueue;
		Kernel.InstallTrapChecker(TrapCleanup)
	END Init;
	
BEGIN
	Init
END Views.