Oberon/A2/Oberon.Documents.Mod

(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich. Refer to the "General ETH Oberon System Source License" contract available at : http : //www.oberon.ethz.ch/ *) MODULE Documents IN Oberon;	(** portable *) (** jm 18.1.95 *) (** The Documents module forms the basis of the Gadgets document model. &#42;) (* &#9;6.4.94 - VERY SPECIAL DOCUMENT WITH DSC HAVING THE SAME COORDINATES AS THE DOCUMENT &#9;Old version can be found in DocumentsOld.Mod. &#9;This important change allows the contents of the document to experience its change in coordinates, so &#9;that it can optimize updates occordingly. This feature is especially used in TextGadgets0.ModifyFrame &#9;6.4.94 - Introduced a default document type &#9;2.5.94 - Added copy over on selection &#9;2.5.94 - Added deep copy support &#9;30.5.94 - added handling of Documents.This load errors &#9;9.6.94 - default documents are open larger &#9;7.11.94 - removed lib from standard types &#9;3.1.95 - Documents.This renamed to Documents.Open &#9;&#9;- added check to ensure invariant from 6.4.94. I am not sure if this fix is correct or not, at least &#9;&#9;it allows text documents to flow inside a text &#9;&#9;- added "DocumentName" attribute &#9;4.12.95 - fixed name overflow in TitleToFilename &#9;15.12.95 - added historyHook &#9;7.3.96 - removed TitleToFilename &#9;30.12.96 - removed MapName &#42;) IMPORT Texts, Objects, Display, Attributes, Links, Gadgets, Modules, Files, Display3, Effects, Oberon, Fonts, Strings, Out, Input (*fof*) ; CONST &#9;MaxDocTypes &#61; 48; TYPE &#9;Document* &#61; POINTER TO DocumentDesc; &#9;DocumentDesc* &#61; RECORD (Gadgets.FrameDesc) &#9;&#9;name* : ARRAY 128 OF CHAR;	(** Document name. *) &#9;&#9;Load* : PROCEDURE (D : Document);	(** Load document contents from disk. *) &#9;&#9;Store* : PROCEDURE (D : Document);	(** Store document contents to disk. *) &#9;&#9;time : LONGINT &#9;END; &#9;(** Find out what document is located at X, Y on the display. *) &#9;LocateMsg* &#61; RECORD (Display.FrameMsg) &#9;&#9;doc* : Document;	(** Result, NIL if no document found. *) &#9;&#9;X*, Y* : INTEGER; &#9;END; VAR &#9;Id* : INTEGER;	(** 07F7H little-endian magic number/flag identifying document files. *) &#9;historyHook* : PROCEDURE (VAR D : Document); (** Called for each document opened. *) &#9;reg : INTEGER; &#9;DocExt : ARRAY MaxDocTypes, 32 OF CHAR; &#9;DocNewProc : ARRAY MaxDocTypes, 64 OF CHAR; &#9;DocService : ARRAY MaxDocTypes OF BOOLEAN; &#9;errMsg* : ARRAY 256 OF CHAR; (* &#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61; Loading/storing of Document Attachments &#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61; *) (* The attachment format is as follows : &#9;tag document-header F7X 08X Len4 Attributes Library Links &#9;Len4 is the length from after Len4 to the end of Links. &#42;) PROCEDURE LoadAttachments*(VAR R : Files.Rider; VAR attr : Attributes.Attr; VAR link : Links.Link); VAR len : LONGINT; F : Files.File; ch : CHAR; lib : Objects.Library; BEGIN &#9;F : &#61; Files.Base(R); &#9;Files.Read(R, ch); ASSERT(ch &#61; 08X); &#9;Files.ReadLInt(R, len); &#9;Attributes.LoadAttributes(R, attr); &#9;NEW(lib); Objects.OpenLibrary(lib); &#9;Files.Read(R, ch); ASSERT(ch &#61; Objects.LibBlockId); &#9;Objects.LoadLibrary(lib, F, Files.Pos(R), len); &#9;Files.Set(R, F, Files.Pos(R) + len); &#9;Links.LoadLinks(R, lib, link) END LoadAttachments; PROCEDURE StoreAttachments*(VAR R : Files.Rider; attr : Attributes.Attr; link : Links.Link); VAR r : Files.Rider; F : Files.File; patch, len : LONGINT; lib : Objects.Library; M : Objects.BindMsg; BEGIN &#9;F : &#61; Files.Base(R); &#9;Files.Write(R, 0F7X); Files.Write(R, 08X); &#9;patch : &#61; Files.Pos(R); &#9;Files.WriteLInt(R, 0); (* patch *) &#9;Attributes.StoreAttributes(R, attr); &#9;NEW(lib); Objects.OpenLibrary(lib); &#9;M.lib : &#61; lib; Links.BindLinks(link, M); &#9;Objects.StoreLibrary(lib, F, Files.Pos(R), len); &#9;Files.Set(R, F, Files.Pos(R) + len); &#9;Links.StoreLinks(R, lib, link); &#9;len : &#61; Files.Pos(R) - patch - 4; &#9;Files.Set(r, F, patch); &#9;Files.WriteLInt(r, len); END StoreAttachments; (* How to skip attachments : &#9;PROCEDURE SkipAttachments(VAR R : Files.Rider); &#9;VAR F : Files.File; len : LONGINT; &#9;BEGIN &#9;&#9;F : &#61; Files.Base(R); &#9;&#9;Files.Read(R, ch); ASSERT(ch &#61; 08X); &#9;&#9;Files.ReadLInt(R, len); &#9;&#9;Files.Set(R, F, Files.Pos(R) + len) &#9;END SkipAttachments; &#42;) (* &#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61; Loading of Document types &#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61; *) PROCEDURE SplitName (VAR name, MName, PName : ARRAY OF CHAR); VAR i, j : INTEGER; BEGIN i : &#61; 0; &#9;WHILE name&#91;i&#93; # "." DO MName&#91;i&#93; : &#61; name&#91;i&#93;; INC(i) END; &#9;MName&#91;i&#93; : &#61; 0X; INC(i); j : &#61; 0; &#9;WHILE name&#91;i&#93; # 0X DO PName&#91;j&#93; : &#61; name&#91;i&#93;; INC(i); INC(j) END; &#9;PName&#91;j&#93; : &#61; 0X END SplitName; (* Try to load generic document *) PROCEDURE generic(name, newproc : ARRAY OF CHAR; VAR loaderror : BOOLEAN) : Document; &#9;VAR &#9;&#9;D : Document; &#9;&#9;MName, PName : ARRAY 64 OF CHAR; &#9;&#9;Mod : Modules.Module; Cmd : Modules.Command; BEGIN &#9;SplitName(newproc, MName, PName); &#9;Mod : &#61; Modules.ThisMod(MName); &#9;IF Modules.res &#61; 0 THEN &#9;&#9;Cmd : &#61; Modules.ThisCommand(Mod, PName); &#9;&#9;IF Modules.res &#61; 0 THEN Objects.NewObj : &#61; NIL; Cmd; &#9;&#9;&#9;IF (Objects.NewObj # NIL) &#38; (Objects.NewObj IS Document) THEN &#9;&#9;&#9;&#9;D : &#61; Objects.NewObj(Document); COPY(name, D.name); D.Load(D) &#9;&#9;&#9;ELSE loaderror : &#61; TRUE &#9;&#9;&#9;END &#9;&#9;ELSE loaderror : &#61; TRUE &#9;&#9;END &#9;ELSE loaderror : &#61; TRUE &#9;END; &#9;RETURN D END generic; PROCEDURE Generic(name : ARRAY OF CHAR; VAR loaderror : BOOLEAN) : Document; VAR D : Document; newproc : ARRAY 64 OF CHAR; &#9;F : Files.File; R : Files.Rider; tag : INTEGER; BEGIN &#9;D : &#61; NIL; loaderror : &#61; FALSE; &#9;F : &#61; Files.Old(name); &#9;IF F # NIL THEN &#9;&#9;Files.Set(R, F, 0); &#9;&#9;Files.ReadInt(R, tag); &#9;&#9;IF (tag &#61; Id) OR (tag &#61; 0727H) THEN &#9;&#9;&#9;Files.ReadString(R, newproc); &#9;&#9;&#9;D : &#61; generic(name, newproc, loaderror) &#9;&#9;END &#9;END; &#9;RETURN D END Generic; PROCEDURE Cap(ch : CHAR) : CHAR; BEGIN &#9;IF (ch &#62;&#61; "a") &#38; (ch &#60;&#61; "z") THEN RETURN CAP(ch) &#9;ELSE RETURN ch &#9;END END Cap; (** Open the give document with name name. NIL is returned on failure. Unknown document types are opened as text documents. *) PROCEDURE Open*(name : ARRAY OF CHAR) : Document; VAR i, j, colonpos, dotpos : INTEGER; ext : ARRAY 64 OF CHAR; D : Document; &#9;loaderror : BOOLEAN; BEGIN &#9;COPY(name, errMsg); Strings.AppendCh(errMsg, " "); &#9;D : &#61; Generic(name, loaderror); &#9;IF (D &#61; NIL) &#38; &#126;loaderror THEN (* not found *) &#9;&#9;i : &#61; 0; j : &#61; -1; colonpos : &#61; -1; &#9;&#9;WHILE name&#91;i&#93; # 0X DO (* find last period *) &#9;&#9;&#9;IF name&#91;i&#93; &#61; "." THEN j : &#61; i &#9;&#9;&#9;ELSIF name&#91;i&#93; &#61; " : " THEN &#9;&#9;&#9;&#9;IF colonpos &#61; -1 THEN colonpos : &#61; i END; &#9;&#9;&#9;END; &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;dotpos : &#61; j; &#9;&#9;IF colonpos &#62; 1 THEN (* jm *) &#9;&#9;&#9;i : &#61; 0; j : &#61; 0; &#9;&#9;&#9;WHILE i &#60; colonpos DO &#9;&#9;&#9;&#9;IF name&#91;i&#93; &#62; " " THEN ext&#91;j&#93; : &#61; Cap(name&#91;i&#93;); INC(j) END; &#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;END; &#9;&#9;&#9;ext&#91;j&#93; : &#61; 0X; &#9;&#9;&#9;i : &#61; 0; WHILE (i # reg) &#38; (&#126;DocService&#91;i&#93; OR (DocExt&#91;i&#93; # ext)) DO INC(i) END; &#9;&#9;&#9;IF i &#61; reg THEN (* unknown type *) &#9;&#9;&#9;&#9;colonpos : &#61; -1	(* ignore colon and try with extension *) &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;IF colonpos &#60;&#61; 1 THEN &#9;&#9;&#9;j : &#61; dotpos; &#9;&#9;&#9;IF (j &#62;&#61; 0) THEN &#9;&#9;&#9;&#9;i : &#61; 0; INC(j); WHILE (name&#91;j&#93; # 0X) &#38; (i # 31) DO ext&#91;i&#93; : &#61; Cap(name&#91;j&#93;); INC(i); INC(j) END; (* copy extension *) &#9;&#9;&#9;&#9;ext&#91;i&#93; : &#61; 0X; i : &#61; 0; &#9;&#9;&#9;&#9;WHILE (i # reg) &#38; (DocService&#91;i&#93; OR (DocExt&#91;i&#93; # ext)) DO INC(i) END &#9;&#9;&#9;ELSE i : &#61; reg (* no period *) &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;IF i &#61; reg THEN (* nothing, use the default *) COPY("TextDocs.NewDoc", DocNewProc&#91;i&#93;) END; &#9;&#9;D : &#61; generic(name, DocNewProc&#91;i&#93;, loaderror) &#9;END; &#9;Objects.NewObj : &#61; NIL; (* for GC *) &#9;IF (D # NIL) &#38; (D.dsc # NIL) THEN &#9;&#9;IF historyHook # NIL THEN historyHook(D) END; &#9;&#9;RETURN D &#9;ELSE &#9;&#9;IF loaderror THEN &#9;&#9;&#9;Strings.Append(errMsg, Modules.resMsg) &#9;&#9;ELSE &#9;&#9;&#9;Strings.Append(errMsg, " loading document failed") &#9;&#9;END; &#9;&#9;RETURN NIL &#9;END END Open; PROCEDURE Register(ext, newproc : ARRAY OF CHAR; service : BOOLEAN); VAR i : INTEGER; BEGIN &#9;i : &#61; 0; WHILE ext&#91;i&#93; # 0X DO ext&#91;i&#93; : &#61; Cap(ext&#91;i&#93;); INC(i) END; &#9;i : &#61; 0; &#9;WHILE (i # reg) &#38; ((ext # DocExt&#91;i&#93;) OR (service # DocService&#91;i&#93;)) DO INC(i) END; &#9;IF i &#61; reg THEN COPY(ext, DocExt&#91;reg&#93;); COPY(newproc, DocNewProc&#91;reg&#93;); DocService&#91;reg&#93; : &#61; service; INC(reg) &#9;ELSE COPY(newproc, DocNewProc&#91;i&#93;); &#9;END END Register; PROCEDURE RegisterStandardTypes(section : ARRAY OF CHAR; service : BOOLEAN); VAR S : Texts.Scanner; ext, newproc : ARRAY 32 OF CHAR; err : BOOLEAN; BEGIN &#9;Oberon.OpenScanner(S, section); &#9;IF S.class &#61; Texts.Inval THEN &#9;&#9;Out.String("Oberon.Text - "); Out.String(section);  Out.String(" not found");  Out.Ln &#9;ELSE &#9;&#9;err : &#61; FALSE; &#9;&#9;WHILE (S.class IN &#123;Texts.Name, Texts.String&#125;) &#38; &#126;err DO &#9;&#9;&#9;COPY(S.s, ext); Texts.Scan(S); &#9;&#9;&#9;IF (S.class &#61; Texts.Char) &#38; (S.c &#61; "&#61;") THEN &#9;&#9;&#9;&#9;Texts.Scan(S); &#9;&#9;&#9;&#9;IF S.class IN &#123;Texts.Name, Texts.String&#125; THEN &#9;&#9;&#9;&#9;&#9;COPY(S.s, newproc); Texts.Scan(S); &#9;&#9;&#9;&#9;&#9;Register(ext, newproc, service) &#9;&#9;&#9;&#9;ELSE err : &#61; TRUE &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE err : &#61; TRUE &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;err : &#61; err OR (S.class # Texts.Char) OR (S.c # "&#125;"); &#9;&#9;IF err THEN &#9;&#9;&#9;Out.String("Error in "); Out.String(section);  Out.Ln &#9;&#9;END &#9;END END RegisterStandardTypes; (* &#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61; default handler for document frames &#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61; *) PROCEDURE SetMask(F : Display.Frame; M : Display3.Mask); VAR O : Display3.OverlapMsg; BEGIN O.M : &#61; M; O.x : &#61; 0; O.y : &#61; 0; O.F : &#61; F; O.dlink : &#61; NIL; O.res : &#61; -1; F.handle(F, O); END SetMask; PROCEDURE SetMainMask(F : Document); VAR R : Display3.Mask; BEGIN &#9;IF F.dsc # NIL THEN &#9;&#9;IF F.mask &#61; NIL THEN SetMask(F.dsc, NIL) &#9;&#9;ELSE &#9;&#9;&#9;Display3.Copy(F.mask, R); R.x : &#61; 0; R.y : &#61; 0; &#9;&#9;&#9;(* &#9;&#9;&#9;Display3.Intersect(R, F.dsc.X, F.dsc.Y, F.dsc.W,  F.dsc.H); &#9;&#9;&#9;R.x : &#61; -F.dsc.X; R.y : &#61; -(F.dsc.Y + F.dsc.H - 1); Display3.Shift(R); &#9;&#9;&#9;&#42;) &#9;&#9;&#9;SetMask(F.dsc, R) &#9;&#9;END &#9;END END SetMainMask; PROCEDURE ToMain(F : Document; ox, oy : INTEGER; VAR M : Display.FrameMsg); VAR Mdlink, Fdlink : Objects.Object; tx, ty : INTEGER; BEGIN &#9;IF F.dsc # NIL THEN &#9;&#9;tx : &#61; M.x; ty : &#61; M.y; M.x : &#61; ox; M.y : &#61; oy; &#9;&#9;Fdlink : &#61; F.dlink; Mdlink : &#61; M.dlink; &#9;&#9;F.dlink : &#61; M.dlink; M.dlink : &#61; F; F.dsc.handle(F.dsc, M); &#9;&#9;F.dlink : &#61; Fdlink; M.dlink : &#61; Mdlink; &#9;&#9;M.x : &#61; tx; M.y : &#61; ty &#9;END END ToMain; PROCEDURE Absolute(dlink : Objects.Object) : BOOLEAN; VAR A : Objects.AttrMsg; BEGIN &#9;IF (dlink # NIL) &#38; (dlink.handle # NIL) THEN (* NIL test because of Script *) &#9;&#9;A.id : &#61; Objects.get; A.name : &#61; "Absolute"; A.res : &#61; -1; dlink.handle(dlink, A); &#9;&#9;RETURN (A.res &#62;&#61; 0) &#38; (A.class &#61; Objects.Bool) &#38; A.b &#9;ELSE RETURN FALSE &#9;END END Absolute; (* new *) PROCEDURE AdjustDocument(F : Document; VAR M : Display.ModifyMsg); VAR A : Display.ModifyMsg; old, x, y, w, h : INTEGER; R : Display3.Mask; BEGIN &#9;IF Absolute(M.dlink) (*TRUE (* 31 IN F.state *) *) THEN (* in viewer system, may optimize *) &#9;&#9;old : &#61; M.mode; M.mode : &#61; Display.state; Gadgets.framehandle(F, M); M.mode : &#61; old; &#9;&#9;IF F.dsc # NIL THEN &#9;&#9;&#9;(* Adjust main *) &#9;&#9;&#9;A.id : &#61; Display.extend; A.F : &#61; F.dsc; A.mode : &#61; M.mode; &#9;&#9;&#9;A.X : &#61; M.X; A.Y : &#61; M.Y; A.W : &#61; M.W; A.H : &#61; M.H; &#9;&#9;&#9;A.dX : &#61; M.dX; A.dY : &#61; M.dY; A.dW : &#61; M.dW; A.dH : &#61; M.dH; &#9;&#9;&#9;A.dlink : &#61; M.dlink; &#9;&#9;&#9;A.res : &#61; -1; Objects.Stamp(A); &#9;&#9;&#9;ToMain(F, M.x, M.y, A); &#9;&#9;&#9;IF (Gadgets.selected IN F.state) &#38; (M.mode &#61; Display.display) THEN &#9;&#9;&#9;&#9;x : &#61; M.x + F.X; y : &#61; M.y + F.Y; w : &#61; F.W; h : &#61; F.H; &#9;&#9;&#9;&#9;Gadgets.MakeMask(F, x, y, M.dlink, R); &#9;&#9;&#9;&#9;Display3.FillPattern(R, Display3.blue, Display3.selectpat, 0, 0, x, y, w, h, Display.paint); &#9;&#9;&#9;END; &#9;&#9;END &#9;ELSE (* unoptimized *) &#9;&#9;IF (F.dsc # NIL) &#38; (M.stamp # F.stamp) THEN F.stamp : &#61; M.stamp; &#9;&#9;&#9;(* Adjust main *) &#9;&#9;&#9;A.id : &#61; Display.extend; A.F : &#61; F.dsc; A.mode : &#61; Display.state; &#9;&#9;&#9;A.X : &#61; M.X; A.Y : &#61; M.Y; A.W : &#61; M.W; A.H : &#61; M.H; &#9;&#9;&#9;A.dX : &#61; M.dX; A.dY : &#61; M.dY; A.dW : &#61; M.dW; A.dH : &#61; M.dH; &#9;&#9;&#9;A.dlink : &#61; M.dlink; &#9;&#9;&#9;A.res : &#61; -1; Objects.Stamp(A); &#9;&#9;&#9;ToMain(F, M.x, M.y, A) &#9;&#9;END; &#9;&#9;Gadgets.framehandle(F, M) &#9;END END AdjustDocument; (* -- docviewer main frame changed; have to adjust docviewer size *) PROCEDURE AdjustChildDocument(F : Document; VAR M : Display.ModifyMsg); VAR A : Display.ModifyMsg; BEGIN &#9;IF M.stamp # F.stamp THEN F.stamp : &#61; M.stamp; &#9;&#9;A.id : &#61; Display.extend; A.F : &#61; F; A.mode : &#61; Display.display; &#9;&#9;A.X : &#61; M.X; A.Y : &#61; M.Y; A.W : &#61; M.W; A.H : &#61; M.H; &#9;&#9;A.dX : &#61; M.dX; A.dY : &#61; M.dY; A.dW : &#61; M.dW; A.dH : &#61; M.dH; &#9;&#9;Display.Broadcast(A) &#9;END END AdjustChildDocument; PROCEDURE check(F : Document); BEGIN &#9;IF F.dsc # NIL THEN &#9;&#9;IF (F.X # F.dsc.X) OR (F.Y # F.dsc.Y) OR (F.W # F.dsc.W) OR (F.H # F.dsc.H) THEN &#9;&#9;&#9;F.dsc.X : &#61; F.X; F.dsc.Y : &#61; F.Y; F.dsc.W : &#61; F.W; F.dsc.H : &#61; F.H; &#9;&#9;END &#9;END END check; PROCEDURE RestoreDocument(F : Document; R : Display3.Mask; ox, oy, x, y, w, h : INTEGER; VAR M : Display.DisplayMsg); VAR D : Display.DisplayMsg; &#9;PROCEDURE ClipAgainst(VAR x, y, w, h : INTEGER; x1, y1, w1, h1 : INTEGER); &#9;VAR r, t, r1, t1 : INTEGER; &#9;BEGIN &#9;&#9;r : &#61; x + w - 1; r1 : &#61; x1 + w1 - 1; t : &#61; y + h - 1; t1 : &#61; y1 + h1 - 1; &#9;&#9;IF x &#60; x1 THEN x : &#61; x1 END; &#9;&#9;IF y &#60; y1 THEN y : &#61; y1 END; &#9;&#9;IF r &#62; r1 THEN r : &#61; r1 END; &#9;&#9;IF t &#62; t1 THEN t : &#61; t1 END; &#9;&#9;w : &#61; r - x + 1; h : &#61; t - y + 1; &#9;END ClipAgainst; BEGIN check(F); &#9;Oberon.RemoveMarks(x, y, w, h); &#9;IF M.id &#61; Display.area THEN &#9;&#9;IF F.dsc # NIL THEN &#9;&#9;&#9;(* display main frame *) &#9;&#9;&#9;D.device : &#61; Display.screen; D.id : &#61; Display.area; D.F : &#61; F.dsc; D.u : &#61; M.u; &#9;&#9;&#9;D.v : &#61; M.v; D.w : &#61; M.w; D.h : &#61; M.h; &#9;&#9;&#9;ClipAgainst(D.u, D.v, D.w, D.h, 0, -F.dsc.H +1, F.dsc.W, F.dsc.H); &#9;&#9;&#9;D.dlink : &#61; M.dlink; D.res : &#61; -1; Objects.Stamp(D); &#9;&#9;&#9;ToMain(F, ox, oy, D); &#9;&#9;ELSE &#9;&#9;&#9;Display3.FilledRect3D(R, Display3.FG, Display3.FG, Display3.BG, x, y, w, h, 1, Display.replace); &#9;&#9;&#9;Display3.String(R, Display3.FG, x + 5, y + h - 20, Fonts.Default, "Document not found", Display.paint); &#9;&#9;END; &#9;ELSE &#9;&#9;IF F.dsc # NIL THEN &#9;&#9;&#9;D.device : &#61; Display.screen; D.id : &#61; Display.full; D.F : &#61; F.dsc; D.dlink : &#61; M.dlink; D.res : &#61; -1; Objects.Stamp(D); &#9;&#9;&#9;ToMain(F, ox, oy, D) &#9;&#9;ELSE &#9;&#9;&#9;Display3.FilledRect3D(R, Display3.FG, Display3.FG, Display3.BG, x, y, w, h, 1, Display.replace); &#9;&#9;&#9;Display3.String(R, Display3.FG, x + 5, y + h -  20, Fonts.Default, "Document not found", Display.paint); &#9;&#9;END; &#9;END; &#9;IF Gadgets.selected IN F.state THEN &#9;&#9;Display3.FillPattern(R, Display3.blue, Display3.selectpat, 0, 0, x, y, w, h, Display.paint); &#9;END END RestoreDocument; PROCEDURE Copy*(VAR M : Objects.CopyMsg; from, to : Document); VAR C : Objects.CopyMsg; BEGIN &#9;Gadgets.CopyFrame(M, from, to); &#9;IF from.dsc # NIL THEN &#9;&#9;C.id : &#61; M.id; Objects.Stamp(C); from.dsc.handle(from.dsc, C); &#9;&#9;to.dsc : &#61; C.obj(Gadgets.Frame) &#9;ELSE to.dsc : &#61; NIL &#9;END; &#9;to.Load : &#61; from.Load; &#9;to.Store : &#61; from.Store; &#9;COPY(from.name, to.name); END Copy; PROCEDURE DocumentAttr(F : Document; VAR M : Objects.AttrMsg); BEGIN &#9;IF M.id &#61; Objects.get THEN &#9;&#9;IF M.name &#61; "Gen" THEN HALT(99) &#9;&#9;ELSIF M.name &#61; "DocumentName" THEN &#9;&#9;&#9;M.class : &#61; Objects.String; COPY(F.name, M.s); M.res : &#61; 0 &#9;&#9;ELSE Gadgets.framehandle(F, M) &#9;&#9;END &#9;ELSIF M.id &#61; Objects.set THEN &#9;&#9;IF M.name &#61; "DocumentName" THEN &#9;&#9;&#9;IF M.class &#61; Objects.String THEN COPY(M.s, F.name); M.res : &#61; 0 END; &#9;&#9;ELSE Gadgets.framehandle(F, M) &#9;&#9;END &#9;ELSIF M.id &#61; Objects.enum THEN &#9;&#9;M.Enum("DocumentName"); Gadgets.framehandle(F, M) &#9;END END DocumentAttr; PROCEDURE Neutralize(F : Document); VAR main : Gadgets.Frame; S : Display.SelectMsg; BEGIN &#9;IF F.dsc # NIL THEN &#9;&#9;main : &#61; F.dsc(Gadgets.Frame); &#9;&#9;IF Gadgets.selected IN main.state THEN &#9;&#9;&#9;S.F : &#61; main; S.res : &#61; -1; S.x : &#61; 0; S.y : &#61; 0; &#9;&#9;&#9;S.id : &#61; Display.reset; F.time : &#61; -1; &#9;&#9;&#9;ToMain(F, 0, 0, S); &#9;&#9;&#9;Gadgets.Update(main) &#9;&#9;END &#9;END END Neutralize; PROCEDURE HandleSelect(F : Document; VAR M : Oberon.InputMsg); VAR main : Gadgets.Frame; S : Display.SelectMsg; N : Oberon.ControlMsg; keysum : SET; C : Objects.CopyMsg; BEGIN &#9;IF F.dsc &#61; NIL THEN RETURN END; &#9;main : &#61; F.dsc(Gadgets.Frame); &#9;S.F : &#61; main; S.res : &#61; -1; S.x : &#61; 0; S.y : &#61; 0; &#9;IF Gadgets.selected IN main.state THEN (* do nothing if already selected *) &#9;&#9;(* S.id : &#61; Display.reset; F.time : &#61; 0; *) &#9;ELSE &#9;&#9;N.id : &#61; Oberon.neutralize; N.F : &#61; NIL; N.res : &#61; -1; ToMain(F, 0, 0, N); &#9;&#9;S.id : &#61; Display.set; F.time : &#61; Oberon.Time; &#9;&#9;ToMain(F, M.x, M.y, S); &#9;&#9;Gadgets.Update(main); keysum : &#61; M.keys; &#9;&#9;REPEAT Effects.TrackMouse(M.keys, M.X, M.Y, Effects.Arrow); keysum : &#61; keysum + M.keys UNTIL M.keys &#61; &#123;&#125;; &#9;&#9;M.res : &#61; 0; &#9;&#9;IF keysum &#61; &#123;0, 2&#125; THEN (* RL delete selection *) &#9;&#9;&#9;(* nothing *) &#9;&#9;ELSIF keysum &#61; &#123;0, 1&#125; THEN (* RM copy to focus *) &#9;&#9;&#9;C.id : &#61; Objects.shallow; C.obj : &#61; NIL; Objects.Stamp(C); F.dsc.handle(F.dsc, C); &#9;&#9;&#9;IF C.obj # NIL THEN &#9;&#9;&#9;&#9;(* &#9;&#9;&#9;&#9;C.obj(Gadgets.Frame).state : &#61; C.obj(Gadgets.Frame).state - &#123;Gadgets.noselect, Gadgets.nodelete, Gadgets.noresize, Gadgets.nomove&#125;; &#9;&#9;&#9;&#9;&#42;) &#9;&#9;&#9;&#9;Gadgets.Integrate(C.obj) &#9;&#9;&#9;END &#9;&#9;END &#9;END; END HandleSelect; PROCEDURE Handler*(F : Objects.Object; VAR M : Objects.ObjMsg); VAR x, y, w, h : INTEGER; F0 : Document; R : Display3.Mask; &#9;N : Oberon.ControlMsg; tmp : SET; tM : Display.DisplayMsg; &#9;obj : Objects.Object; keys : SET; (*fof*) BEGIN &#9;WITH F : Document DO &#9;&#9;IF M IS Display.FrameMsg THEN &#9;&#9;&#9;WITH M : Display.FrameMsg DO &#9;&#9;&#9;&#9;IF (M.F &#61; NIL) OR (M.F &#61; F) THEN	(* message addressed to this frame *) &#9;&#9;&#9;&#9;&#9;x : &#61; M.x + F.X; y : &#61; M.y + F.Y; w : &#61; F.W; h : &#61; F.H; (* calculate display coordinates *) &#9;&#9;&#9;&#9;&#9;IF M IS Display.DisplayMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH M : Display.DisplayMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF M.device &#61; Display.screen THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF (M.id &#61; Display.full) OR (M.F &#61; NIL) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Gadgets.MakeMask(F, x, y, M.dlink, R); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;RestoreDocument(F, R, M.x, M.y, x, y, w, h, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF M.id &#61; Display.area THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Gadgets.MakeMask(F, x, y, M.dlink, R); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Display3.AdjustMask(R, x + M.u, y + h - 1 + M.v, M.w, M.h); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;RestoreDocument(F, R, M.x, M.y, x, y, w, h, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF M.device &#61; Display.printer THEN ToMain(F, M.x, M.y, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M IS Oberon.InputMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH M : Oberon.InputMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF (M.id &#61; Oberon.track) &#38; &#126;(Gadgets.selected IN F.state) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9; &#9;Input.KeyState(keys); (* fof *) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF &#126;Gadgets.InActiveArea(F, M) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF (M.keys &#61; &#123;0&#125;) &#38; (Input.SHIFT IN keys) THEN HandleSelect(F, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE Gadgets.framehandle(F, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ToMain(F, M.x, M.y, M); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF (M.res &#60; 0) &#38; (M.keys &#61; &#123;0&#125;) &#38; (Input.SHIFT IN keys) THEN HandleSelect(F, M) END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF (M.res &#60; 0) &#38; &#126;Gadgets.InActiveArea(F, M) THEN Gadgets.framehandle(F, M) END &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF &#126;(Gadgets.selected IN F.state) THEN ToMain(F, M.x, M.y, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE Gadgets.framehandle(F, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M IS Display.ModifyMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;IF M.F &#61; F THEN AdjustDocument(F, M(Display.ModifyMsg)); &#9;&#9;&#9;&#9;&#9;&#9;ELSE Gadgets.framehandle(F, M) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M IS LocateMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH M : LocateMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;Gadgets.MakeMask(F, x, y, M.dlink, R); &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF Effects.Inside(M.X, M.Y, x, y, w, h) &#38; Display3.Visible(R, M.X, M.Y, 1, 1) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;M.doc : &#61; F; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ToMain(F, M.x, M.y, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M IS Display.LocateMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH M : Display.LocateMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF (M.loc &#61; NIL) &#38; Effects.Inside(M.X, M.Y, x, y, w, h) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ToMain(F, M.x, M.y, M); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF M.loc &#61; NIL THEN M.loc : &#61; F; M.u : &#61; M.X - x; M.v : &#61; M.Y - (y + h - 1); M.res : &#61; 0 END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M IS Display.SelectMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH M : Display.SelectMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF M.id &#61; Display.set THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Neutralize(F); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;N.id : &#61; Oberon.neutralize; N.F : &#61; NIL; N.res : &#61; -1; ToMain(F, M.x, M.y, N) &#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF M.id &#61; Display.reset THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSIF M.id &#61; Display.get THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF (((M.time-F.time) &#60; 0) OR (M.time &#61; -1)) &#38; (Gadgets.selected IN F.dsc(Gadgets.Frame).state) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;M.time : &#61; F.time; M.sel : &#61; F; M.obj : &#61; F.dsc &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF M.F # NIL THEN Gadgets.framehandle(F, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE ToMain(F, M.x, M.y, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M IS Oberon.ControlMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH M : Oberon.ControlMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;ToMain(F, M.x, M.y, M); &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF M.id &#61; Oberon.neutralize THEN Neutralize(F) END &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M IS Display3.UpdateMaskMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH M : Display3.UpdateMaskMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;NEW(F.mask); Display3.Open(F.mask); Display3.Add(F.mask, 0, -F.H+1, F.W, F.H); &#9;&#9;&#9;&#9;&#9;&#9;&#9;SetMainMask(F); &#9;&#9;&#9;&#9;&#9;&#9;&#9;M.res : &#61; 0; &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M IS Display3.OverlapMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH M : Display3.OverlapMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;F.mask : &#61; M.M; SetMainMask(F); &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M IS Display3.UpdateMaskMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH M : Display3.UpdateMaskMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF F.mask &#61; NIL THEN Gadgets.MakeMask(F, x, y, M.dlink, R) END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;SetMainMask(F) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M IS Gadgets.UpdateMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH M : Gadgets.UpdateMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF M.obj &#61; F.dsc THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Gadgets.MakeMask(F, x, y, M.dlink, R); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;tM.device : &#61; Display.screen; tM.id : &#61; Display.full; tM.dlink : &#61; M.dlink; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;RestoreDocument(F, R, M.x, M.y, x, y, w, h, tM); &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF Gadgets.lockedsize IN F.dsc(Gadgets.Frame).state THEN INCL(F.state, Gadgets.lockedsize) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE EXCL(F.state, Gadgets.lockedsize) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE ToMain(F, M.x, M.y, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M.F # NIL THEN Gadgets.framehandle(F, M) &#9;&#9;&#9;&#9;&#9;ELSE ToMain(F, M.x, M.y, M) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;ELSE (* not for this frame but perhaps for a child *) &#9;&#9;&#9;&#9;&#9;IF M IS Display3.UpdateMaskMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH M : Display3.UpdateMaskMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF M.F &#61; F.dsc THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;IF F.mask &#61; NIL THEN Gadgets.MakeMask(F, M.x + F.X, M.y + F.Y, M.dlink, R) END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;SetMainMask(F) &#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE ToMain(F, M.x, M.y, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M IS Display.ConsumeMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;WITH M : Display.ConsumeMsg DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF FALSE &#38; &#126;(30 IN F.state) &#38; (M.obj IS Document) THEN (* prevent consumption *) &#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE ToMain(F, M.x, M.y, M) &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF M IS Display.ModifyMsg THEN &#9;&#9;&#9;&#9;&#9;&#9;IF M.F &#61; F.dsc THEN AdjustChildDocument(F, M(Display.ModifyMsg)) &#9;&#9;&#9;&#9;&#9;&#9;ELSE ToMain(F, M.x, M.y, M) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSE ToMain(F, M.x, M.y, M) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;(* Object messages *) &#9;&#9;ELSIF M IS Objects.AttrMsg THEN DocumentAttr(F, M(Objects.AttrMsg)) &#9;&#9;ELSIF M IS Objects.LinkMsg THEN &#9;&#9;&#9;WITH M : Objects.LinkMsg DO &#9;&#9;&#9;&#9;IF (M.id &#61; Objects.get) &#38; (M.name &#61; "Model") THEN &#9;&#9;&#9;&#9;&#9;M.obj : &#61; F.dsc; M.res : &#61; 0 &#9;&#9;&#9;&#9;ELSE Gadgets.framehandle(F, M) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;ELSIF M IS Objects.FileMsg THEN &#9;&#9;&#9;WITH M : Objects.FileMsg DO &#9;&#9;&#9;&#9;IF M.id &#61; Objects.store THEN (* store private data here *) &#9;&#9;&#9;&#9;&#9;IF F.lib.name &#61; "" THEN (* private library *) &#9;&#9;&#9;&#9;&#9;&#9;Files.WriteInt(M.R, 1); &#9;&#9;&#9;&#9;&#9;&#9;Files.WriteString(M.R, F.name); &#9;&#9;&#9;&#9;&#9;&#9;Files.WriteSet(M.R, &#123;&#125;); &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.framehandle(F, M) &#9;&#9;&#9;&#9;&#9;ELSE (* public library *) &#9;&#9;&#9;&#9;&#9;&#9;Files.WriteInt(M.R, 2); &#9;&#9;&#9;&#9;&#9;&#9;Files.WriteString(M.R, F.name); &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.WriteRef(M.R, F.lib, F.dsc); &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.framehandle(F, M) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;ELSIF M.id &#61; Objects.load THEN (* load private data here *) &#9;&#9;&#9;&#9;&#9;Files.ReadInt(M.R, x); &#9;&#9;&#9;&#9;&#9;IF x &#61; 1 THEN (* private library *) &#9;&#9;&#9;&#9;&#9;&#9;Files.ReadString(M.R, F.name); &#9;&#9;&#9;&#9;&#9;&#9;Files.ReadSet(M.R, tmp); &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.framehandle(F, M); &#9;&#9;&#9;&#9;&#9;&#9;F.Load(F) &#9;&#9;&#9;&#9;&#9;ELSIF x &#61; 2 THEN (* public library *) &#9;&#9;&#9;&#9;&#9;&#9;Files.ReadString(M.R, F.name); &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.ReadRef(M.R, F.lib, obj); &#9;&#9;&#9;&#9;&#9;&#9;Gadgets.framehandle(F, M); &#9;&#9;&#9;&#9;&#9;&#9;IF (obj # NIL) &#38; (obj IS Gadgets.Frame) THEN F.dsc : &#61; obj(Display.Frame) &#9;&#9;&#9;&#9;&#9;&#9;ELSE F.Load(F) &#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;ELSIF M IS Objects.BindMsg THEN &#9;&#9;&#9;WITH M : Objects.BindMsg DO &#9;&#9;&#9;&#9;Gadgets.framehandle(F, M); &#9;&#9;&#9;&#9;IF (M.lib.name # "") &#38; (F.dsc # NIL) THEN (* public library, bind document contents *) &#9;&#9;&#9;&#9;&#9;F.dsc.handle(F.dsc, M); &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;END &#9;&#9;ELSIF M IS Objects.CopyMsg THEN &#9;&#9;&#9;WITH M : Objects.CopyMsg DO &#9;&#9;&#9;&#9;IF M.stamp &#61; F.stamp THEN M.obj : &#61; F.dlink	(* copy msg arrives again *) &#9;&#9;&#9;&#9;ELSE	(* first time copy message arrives *) &#9;&#9;&#9;&#9;&#9;NEW(F0); F.stamp : &#61; M.stamp; F.dlink : &#61; F0; Copy(M, F, F0); M.obj : &#61; F0 &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;ELSIF M IS Objects.FindMsg THEN &#9;&#9;&#9;WITH M : Objects.FindMsg DO &#9;&#9;&#9;&#9;Gadgets.framehandle(F, M); &#9;&#9;&#9;&#9;IF (F.dsc # NIL) &#38; (M.obj &#61; NIL) THEN &#9;&#9;&#9;&#9;&#9;F.dsc.handle(F.dsc, M) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;ELSE	(* unknown msg, framehandler might know it *) &#9;&#9;&#9;Gadgets.framehandle(F, M) &#9;&#9;END &#9;END END Handler; PROCEDURE New*; VAR F : Document; BEGIN NEW(F); F.handle : &#61; Handler; F.W : &#61; 250; F.H : &#61; 200; Objects.NewObj : &#61; F; END New; (** Initialize document D with main as contents. *) PROCEDURE Init*(D : Document; main : Gadgets.Frame); &#9;VAR f : Files.File; M : Display.ModifyMsg; BEGIN &#9;D.dsc : &#61; main; &#9;IF main # NIL THEN &#9;&#9;IF (main.lib # NIL) &#38; (main.lib.name # "") THEN (* public object ! *) &#9;&#9;&#9;D.X : &#61; main.X; D.Y : &#61; main.Y; D.W : &#61; main.W; D.H : &#61; main.H &#9;&#9;ELSE &#9;&#9;&#9;M.X : &#61; D.X; M.Y : &#61; D.Y; M.W : &#61; D.W; M.H : &#61; D.H; M.x : &#61; 0; M.y : &#61; 0; &#9;&#9;&#9;M.dX : &#61; M.X - main.X; M.dY : &#61; M.Y - main.Y; M.dW : &#61; M.W - main.W; M.dH : &#61; M.H - main.H; &#9;&#9;&#9;M.F : &#61; main; M.id : &#61; Display.extend; M.mode : &#61; Display.state; M.res : &#61; -1; Objects.Stamp(M); &#9;&#9;&#9;main.handle(main, M); &#9;&#9;&#9;main.X : &#61; D.X; main.Y : &#61; D.Y; main.W : &#61; D.W; main.H : &#61; D.H &#9;&#9;END; &#9;&#9;INCL(D.state, Gadgets.lockedcontents); &#9;&#9; IF Gadgets.lockedsize IN main.state THEN INCL(D.state, Gadgets.lockedsize) END &#9;END; &#9;f : &#61; Files.Old(D.name); &#9;IF f # NIL THEN Files.GetName(f, D.name) END END Init; (** Returns the marked document (with F1). NIL is returned when no document is marked. The visibility of the Oberon pointer is ignored. *) PROCEDURE MarkedDoc* : Document; VAR M : LocateMsg; V : Display.Frame; BEGIN &#9;IF TRUE (* Oberon.Pointer.on *) THEN &#9;&#9;M.X : &#61; Oberon.Pointer.X; M.Y : &#61; Oberon.Pointer.Y; &#9;&#9;M.F : &#61; NIL; M.doc : &#61; NIL; &#9;&#9;V : &#61; Oberon.MarkedViewer; &#9;&#9;IF V # NIL THEN &#9;&#9;&#9;M.res : &#61; -1; M.x : &#61; 0; M.y : &#61; 0; &#9;&#9;&#9;V.handle(V, M) &#9;&#9;ELSE &#9;&#9;&#9;Display.Broadcast(M) &#9;&#9;END; &#9;&#9;RETURN M.doc &#9;ELSE RETURN NIL &#9;END END MarkedDoc; PROCEDURE InitM; BEGIN &#9;reg : &#61; 0; &#9;Register("Text", "TextDocs.NewDoc", FALSE); &#9;Register("Tool", "TextDocs.NewDoc", FALSE); &#9;Register("Mod", "TextDocs.NewDoc", FALSE); &#9;Register("Panel", "PanelDocs.NewDoc", FALSE); &#9;Register("Pict", "RembrandtDocs.NewDoc", FALSE); &#9;RegisterStandardTypes("Gadgets.Documents", FALSE); &#9;RegisterStandardTypes("Gadgets.DocumentServices", TRUE) END InitM; BEGIN Id : &#61; 07F7H; InitM END Documents. (** Remarks : 1. Documents Documents are nothing more than collections of objects, saved together  in the same file. Such object collections require additional  functionality that are not provided by the objects in the collection  themselves. This additional functionality are provided by the document  gadgets. Document gadgets act as a wrapper for a object/gadget  collection, giving it a filename, icon, menu bar and printing  capability. They are a type of container having a single child called  the document main frame. The main frame of a document gadget is  remembered in the dsc field of a document. The document gadget has  exactly the same size as its main frame. The Documents.Init procedure  "merges" the document with its main frame. 	Each document class has a  generator procedure. Just as the generator procedures of other  gadgets, calling the generator of a document creates an "empty" instance of that document class. By filling in the name record field of a document, and calling its Load method, the document will "fill" its contents from the file with that name. Correspondingly, calling the Store method stores the document under that name to disk. 2. Document Format All documents are provided with a standard header on disk so that they can be recreated or opened when just the filename is known. The header has the following format : &#9;Tag DocumentGeneratorProcedure X Y W H. &#9;Tag &#61; 0F7X 07X. &#9;DocumentGeneratorProcedure &#61; &#123;alpha&#125; 0X. (* Generator name *) &#9;X, Y, W, H &#61; INTEGER. (* Prefered document position and size. *) The document header is followed by the byte stream content of the document. DocumentGeneratorProcedure is called by Documents.Open to create an empty instance of the document gadget, which is then filled by a call to the Load method (as described above). To provide compatibility with the non-Oberon world that does not use such an identification header, an internal table of the Documents module pairs file extensions with document generator procedures. Should no document header be present, the file extension is used to file the (hopefully) correct document generator. The Load method of this document must then load the headerless file. It is allowed (but not recommended) to store a document without a header, should the extension table be set up correctly. The extension table can be extended by adding an entry to the Documents section of the Oberon registry. Eeach entry is a "Extension&#61;Generator" pair. 3. Menus Each document requires a menu bar with commands associated with the document type when opened with Desktops.OpenDoc. This menubar is gathered from the links "SystemMenu", "UserMenu" and "DeskMenu" provided by the document when the Desktops.OpenDoc command is executed. The menu can be constructed with the procedure Desktops.NewMenu or can be taken from a public library. The string given as parameter to procedure NewMenu must contain a sequence of Oberon commands. By immediately following a menu command with a word in square brackets, that word will be used as the menu bar button caption. A typical menu string might look as follows : &#9;"MyDoc.Search&#91;Search&#93; MyDoc.Save&#91;Store&#93;" Note that the Desktops module automatically adds additional buttons like &#91;Close&#93;, &#91;Grow&#93;, &#91;Min&#93; and &#91;Copy&#93;. For more flexibility, documents may also defined their own menu bars by "exporting" them as public objects from a public library. The public library should contain three menubars for the Desktop, System track and User track respectively. These menus should have the names "DeskMenu", "SystemMenu" and "UserMenu" respectively. For example, the text documents have such a library (called "TextDocs.Lib"). When the library is missing the default menubars are used. Programmers must add support for this feature in their Document handlers. The desktop uses the LinkMsg to request the document to return its menu bar. You should always return a DEEP COPY of the menu-bar from the library. Best is to lock the menubars and to set the Border of the Panel to 0. Note that the menu bar can have any height and content. For example (copied from PanelDocs.Mod) : &#9;IF M IS Objects.LinkMsg THEN &#9;&#9;WITH M : Objects.LinkMsg DO &#9;&#9;&#9;IF (M.id &#61; Objects.get) &#38; (M.name &#61; "DeskMenu") THEN &#9;&#9;&#9;&#9;M.obj : &#61; Gadgets.CopyPublicObject("PanelDocs.DeskMenu", TRUE); &#9;&#9;&#9;&#9;IF M.obj &#61; NIL THEN M.obj : &#61; Desktops.NewMenu(Menu) END; &#9;&#9;&#9;&#9;M.res : &#61; 0 &#9;&#9;&#9;ELSIF (M.id &#61; Objects.get) &#38; (M.name &#61; "SystemMenu") THEN &#9;&#9;&#9;&#9;M.obj : &#61; Gadgets.CopyPublicObject("PanelDocs.SystemMenu", TRUE); &#9;&#9;&#9;&#9;IF M.obj &#61; NIL THEN M.obj : &#61; Desktops.NewMenu(Menu) END; &#9;&#9;&#9;&#9;M.res : &#61; 0 &#9;&#9;&#9;ELSIF (M.id &#61; Objects.get) &#38; (M.name &#61; "UserMenu") THEN &#9;&#9;&#9;&#9;M.obj : &#61; Gadgets.CopyPublicObject("PanelDocs.UserMenu", TRUE); &#9;&#9;&#9;&#9;IF M.obj &#61; NIL THEN M.obj : &#61; Desktops.NewMenu(Menu) END; &#9;&#9;&#9;&#9;M.res : &#61; 0 &#9;&#9;&#9;ELSE Documents.Handler(D, M) &#9;&#9;&#9;END &#9;&#9;END &#9;ELSE ... 4. Icon A document can indicate through its "Icon" attribute what public object should be regarded as its pictorial icon representation. The document should return a string attribute in the form "L.O", where L identifies the public library, and O the object in that library. The gadget identified this way is then packed by the desktop inside an icon gadget when Desktops.MakeIcon is executed. 5. Load failure A document can indicate a failure to load by setting D.dsc to NIL before returning from the Load method. 6. Example Code Examples of how documents are programmed can be found in the files DocumentSkeleton.Mod, OpenDemo.Mod and OpenDemo2.Mod. 7. Uniform Resource Locator (URL) notation URL are unique references to documents located on the network. An URL is identified as a protocol specifier followed by a document location and name : &#9;"protocol : //location/name" Typical protocols are http, mailto, ftp and so forth. The Documents module handles documents with URL-like names in a special way. Should the Open procedure be requested to open a document name with a protocol name followed by a " : ", the protocol name is looked up in  extension table instead of the filename extension. That is, protocols have precedence over filename extensions. &#42;)