№Syntax10.Scn.Fnt№џџџ`!`рЅParcElemsAllocрТ€  §|џџџpА­VersionElemsAllocBeg#Syntax10.Scn.Fnt NoWeb WebWebNoWeb#Syntax10.Scn.Fnt(* web browser not installed *)Web pА­VersionElemsAllocEndўџџаћqInfoElemsAllocVSyntax10.Scn.FntвѕџџџS№IStampElemsAlloc24 Oct 97Н"Title": NetNewsElems "Author": Hamader Peter "Abstract": implements the text elements for the netnews client "Keywords": netnews internet ArticleElems GroupElems ServerElems "Version": 1.2 "From": ? "Until":  "Changes": instead if inserting an article several times, a LinkElem is inserted pointing to the already inserted ArticleElem "Hints": be sure you have set the VersionElems properly яГџџџpА­#Syntax10.Scn.Fnt Web NoWebWebWeb NoWeb pА­;џџџџ€8РдFoldElemsNewИџџџџ€8Рдџџџџ€8Рд=ГџџџpА­#Syntax10.Scn.Fnt Web NoWebWebWeb NoWeb QpА­‡џџџџ€8Рдџџџџ€8Рд#џџџџ€8Рд+џџџџ€8Рд'џџџџ€8Рдџџџџ€8Рд%џџџџ€8РдЌџџџџ€8Рд'џџџџ€8Рд/џџџџ€8Рдџџџџ€8РдЮџџџџ€8Рдџџџџ€8Рд-џџџџ€8Рдџџџџ€8Рд^џџџџ€8РдГџџџpА­#Syntax10.Scn.Fnt Web NoWebWebWeb NoWeb ]pА­џџџџ€8Рдџџџџ€8РдАџџџџ€8РдSyntax10i.Scn.FntЫџџџ€8Рд$Syntax10i.Scn.Fnt misc. procedures )џџџџ€8Рд:џџџџ€8Рд9џџџџ€8Рд„џџџџ€8Рд"џџџџ€8Рд(џџџџ€8Рдџџџџ€8Рд!џџџџ€8Рдџџџџ€8Рд!џџџџ€8Рд+џџџџ€8Рд,џџџџ€8Рд$џџџџ€8Рд*џџџџ€8РдLџџџџ€8Рдџџџџ€8Рд4џџџџ€8РдАџџџџ€8Рдџџџџ€8Рд1џџџџ€8Рд8џџџџ€8Рдxџџџџ€8Рд1џџџџ€8Рдсџџџџ€8Рд+џџџџ€8РдБџџџџ€8Рд%џџџџ€8Рдяџџџџ€8Рд;џџџџ€8РдDџџџџ€8Рд(џџџџ€8РдNџџџџ€8РдCџџџџ€8Рдђџџџџ€8Рд=џџџџ€8Рд5џџџџ€8Рд?џџџџ€8Рд.џџџџ€8РдGџџџџ€8РдІџџџџ€8РдAџџџџ€8РдDџџџџ€8РдRџџџџ€8Рд%џџџџ€8Рд;џџџџ€8Рдџџџџ€8Рдџџџџ€8РдЌџџџ€8Рд$Syntax10i.Scn.Fnt11 procedures concerned with (abstract class) Elem Eџџџџ€8РдXџџџџ€8Рд@џџџџ€8РдTџџџџ€8Рд8џџџџ€8РдLџџџџ€8Рд8џџџџ€8РдLџџџџ€8РдFџџџџ€8РдGџџџџ€8Рдџџџџ€8Рд€џџџџ€8Рд+џџџџ€8Рд]џџџџ€8Рдџџџџ€8Рдџџџџ€8Рд6џџџџ€8Рдvџџџџ€8Рдџџџџ€8Рд>џџџџ€8Рд\џџџџ€8Рдџџџџ€8Рд6џџџџ€8Рдwџџџџ€8Рдџџџџ€8РдЖџџџ€8Рд$Syntax10i.Scn.Fnt'' procedures concerned wih ArticleElems :џџџџ€8Рдрџџџџ€8Рд1џџџџ€8Рдzџџџџ€8Рд"џџџџ€8Рдхџџџџ€8Рд<џџџџ€8Рдшџџџџ€8Рд*џџџџ€8Рдюџџџџ€8РдLџџџџ€8Рд‡џџџџ€8Рд?џџџџ€8РдСџџџџ€8РдGџџџџ€8Рдnџџџџ€8Рд?џџџџ€8Рдљџџџџ€8РдNџџџџ€8Рдfџџџџ€8Рд&џџџџ€8РдŽџџџџ€8Рд#џџџџ€8РдЋџџџџ€8РдЈџџџџ€8Рдйџџџџ€8Рд{џџџџ€8РдЩџџџџ€8Рдџџџџ€8РдLџџџџ€8Рдџџџџ€8РдЗџџџ€8Рд$Syntax10i.Scn.Fnt&& procedures concerned with GroupElems @џџџџ€8Рдчџџџџ€8Рд-џџџџ€8РдЮsџџџџ€8Рдбџџџџ€8Рд#џџџџ€8Рд џџџџ€8РдЋ@f­",c"о!Ю!3%д в-ћ;Ыџџџџ€8Рд0џџџџ€8Рдцџџџџ€8РдЈџџџџ€8Рдœџџџџ€8РдJџџџџ€8Рдтџџџџ€8Рдoџџџџ€8РдЬџџџџ€8Рдкџџџџ€8РдIџџџџ€8Рд*џџџџ€8Рд^џџџџ€8Рдуџџџџ€8РдХџџџџ€8Рд7џџџџ€8Рд•џџџџ€8Рдџџџџ€8Рдџџџџ€8Рдџџџџ€8Рд0џџџџ€8Рдџџџџ€8Рдџџџџ€8Рдџџџџџ€8Рдџџџџ€8Рд2џџџџ€8Рд‰џџџџ€8РдEџџџџ€8Рд‹џџџџ€8Рд=џџџџ€8Рд[џџџџ€8Рд=џџџџ€8РдКџџџџ€8РдLџџџџ€8Рдˆџџџџ€8Рд)џџџџ€8РдKџџџџ€8Рд!џџџџ€8Рд˜џџџџ€8Рдlџџџџ€8Рдњџџџџ€8Рдџџџџ€8РдFџџџџ€8Рдџџџџ€8РдЖџџџ€8Рд$Syntax10i.Scn.Fnt'' procedures concerned with ServerElems )џџџџ€8Рдтџџџџ€8Рд(џџџџ€8Рд€џџџџ€8РдKџџџџ€8Рд.џџџџ€8Рд3џџџџ€8РдШџџџџ€8РдFџџџџ€8Рд™џџџџ€8Рд>џџџџ€8Рд•џџџџ€8Рд>џџџџ€8РдSџџџџ€8РдMџџџџ€8Рд‰џџџџ€8Рд{џџџџ€8Рдџџџџ€8Рдџџџџ€8РдJџџџџ€8Рдџџџџ€8РдГџџџpА­#Syntax10.Scn.Fnt Web NoWebWebWeb NoWeb Аџџџ€8Рд$Syntax10i.Scn.Fnt-- procedures concerned with news/nntp schemes ,џџџџ€8Рд џџџџ€8Рдyџџџџ€8Рдnџџџџ€8Рдцџџџџ€8РдKџџџџ€8Рдyџџџџ€8Рдџџџџ€8РдЅџџџџ€8РдЙџџџџ€8Рдџџџџ€8РдpА­4џџџџ€8Рдzџџџџ€8Рд[џџџџ€8Рдaџџџџ€8Рд™џџџџ€8Рдlџџџџ€8Рд•џџџџ€8Рдoџџџџ€8РдМџџџџ€8Рд^џџџџ€8РдЁџџџџ€8Рдaџџџџ€8РдЈџџџџ€8Рдџџџџ€8РдOџџџџ€8Рд,џџџџ€8Рд,џџџџ€8Рд9џџџџ€8Рдџџџџ€8РдГџџџpА­#Syntax10.Scn.Fnt Web NoWebWebWeb NoWeb #pА­UГџџџpА­#Syntax10.Scn.Fnt Web NoWebWebWeb NoWeb žpА­џџџџ€8Рд1атMODULE NetNewsElems;(* web browser installed *)  (* for a detailed description of types and constants see 'NetNews.Text' *) IMPORT NN := NNTP, FE := FoldElems, T := Texts, Display, Oberon, TF := TextFrames, Input, Files, S := Strings, O := Oberon, Viewers, MV := MenuViewers, Fonts, Web, Kernel, ME := MarkElems, LE := LinkElems, Out; CONST  CR = 0DX; TAB = 9X; middleKey = 1; x0 = 0; y0 = 2; pixel = 10000; elemW = 17*LONG(pixel); elemH = 13*LONG(pixel); leftMode* = {FE.expLeft, FE.colLeft}; tempMode* = {FE.tempLeft, FE.findLeft}; colMode* = {FE.colRight, FE.colLeft}; invisW = 1 * pixel; nameLen* = 128; numLen* = 16; subjectLen* = 256; refLen* = 2048; invalidArticle = MIN(LONGINT); new* = 0; old* = 1; persistent* = 2; red* = 7; black* = Display.white; green* = 8; iniFile* = "NetNews.Profile"; normalHD* = 0; fullHD* = 1; shortHD* = 2; stdMaxArticles = 100; stdFnt = "Syntax10.Scn.Fnt"; stdKWDFnt = "Syntax10b.Scn.Fnt"; stdQuotedFnt = "Syntax10i.Scn.Fnt"; stdHost = "news.uni-linz.ac.at"; stdFrom = "n@o.o.n.e"; stdReplyTo = "n@o.o.n.e"; stdOrganization = "NONE"; xMailer* = "NetNews for Oberon V1.2"; stdFile = "Client.News"; stdDir = ""; stdSignature = "Signature.Text"; stdHTSize = 101; charSetSize = 128; noMarkElem = -1;  TYPE  String* = POINTER TO ARRAY OF CHAR; Elem* = POINTER TO ElemDesc; ArticleElem* = POINTER TO ArticleElemDesc; GroupElem* = POINTER TO GroupElemDesc; ServerElem* = POINTER TO ServerElemDesc; Preferences* = POINTER TO PreferencesDesc; HashTableEntry = POINTER TO HashTableEntryDesc; Data* = POINTER TO DataDesc;  NewsLoader = POINTER TO NewsLoaderDesc; NNTPLoader = POINTER TO NNTPLoaderDesc; RangeList = POINTER TO ARRAY OF RECORD beg, end: LONGINT END; KeyWordList = ARRAY 6, 32 OF CHAR; DataDesc* = RECORD(NN.DataDesc)  elem*: Elem; END;  ElemDesc* = RECORD(FE.ElemDesc)  name-: String; numTabs-:INTEGER END;  ArticleElemDesc* = RECORD(ElemDesc)  subject, references, from: String; number: LONGINT; type: SHORTINT; valid, copied: BOOLEAN; next, copy: ArticleElem; markElemKey: LONGINT END;  GroupElemDesc* = RECORD(ElemDesc)  noArticles-, firstArticle-, lastArticle-, curArticle: LONGINT; list: ArticleElem; oldArticles: RangeList; aText: T.Text; aReader: T.Reader; first: BOOLEAN END;  ServerElemDesc* = RECORD(ElemDesc)  conn-: NN.Connection; port: INTEGER END;  PreferencesDesc* = RECORD  allArticles-: BOOLEAN; maxArticles-: INTEGER; threadArticles-: BOOLEAN; showFromEntry-: BOOLEAN; useConnection-: BOOLEAN; server-: RECORD stdHost-: ARRAY 64 OF CHAR; stdPort-: INTEGER; END; article-: RECORD headerLen-: SHORTINT; font-, keyWordFnt-, quotedFnt-: Fonts.Font; END; posting-: RECORD from-, replyTo-, organization-, signature-: ARRAY subjectLen OF CHAR; END; stdFile-, stdDir-: ARRAY nameLen OF CHAR END;  HashTableEntryDesc = RECORD  key: String; next: HashTableEntry END;  HashTable = RECORD  used: LONGINT; table: POINTER TO ARRAY OF HashTableEntry; dummy: HashTableEntry END;   NewsLoaderDesc = RECORD (Web.LoaderDesc) END; NNTPLoaderDesc = RECORD (Web.LoaderDesc) END;  VAR  articlePat, groupPat, serverPat: ARRAY 6 OF Display.Pattern; w: T.Writer; pref-: Preferences; kwdList: KeyWordList; persistentArticles: HashTable; markElemKey: LONGINT;   PROCEDURE Max (x, y: LONGINT): LONGINT;  BEGIN IF x > y THEN RETURN x ELSE RETURN y END END Max;  PROCEDURE LIntToStr (i: LONGINT; VAR s: ARRAY OF CHAR);  (* converts 'i' to 'str' *) VAR j, sign: INTEGER; num: LONGINT; BEGIN IF i = 0 THEN s[0] := "0"; s[1] := 0X ELSIF ABS(i) # i THEN sign := -1; i := ABS(i) ELSE sign := 1 END; j := 0; num := i; WHILE num > 0 DO num := num DIV 10; INC(j); END; s[j] := 0X; DEC(j); FOR j := j TO 0 BY -1 DO s[j] := CHR((i MOD 10)+48); i := i DIV 10 END; IF sign = -1 THEN S.Insert("-", 0, s) END END LIntToStr;  PROCEDURE WriteElem (e: T.Elem);  BEGIN T.WriteElem(w, e) END WriteElem;  PROCEDURE Write (ch: CHAR);  BEGIN T.Write(w, ch) END Write;  PROCEDURE WriteLn;  BEGIN T.WriteLn(w) END WriteLn;  PROCEDURE WriteString (s: ARRAY OF CHAR);  BEGIN T.WriteString(w, s) END WriteString;  PROCEDURE WriteInt(i, j: LONGINT);  BEGIN T.WriteInt(w, i, j) END WriteInt;  PROCEDURE ReadLn (VAR r: T.Reader; VAR ch: CHAR; VAR line: ARRAY OF CHAR);  (* reads from the position of 'r' to the end of the line and returns the read characters in 'line' *) VAR i: INTEGER; BEGIN i := 0; WHILE ~r.eot & (ch # CR) & (i < LEN(line)-2) DO line[i] := ch; T.Read(r, ch); INC(i) END; line[i] := 0X END ReadLn;  PROCEDURE SkipLine (VAR r: T.Reader; VAR ch: CHAR); (* reads from the position of r to the end of the current line and returns the lasr character in 'ch' *) BEGIN WHILE ~r.eot & (ch # CR) DO T.Read(r, ch) END END SkipLine;  PROCEDURE EOL;  BEGIN WriteLn; T.Append(O.Log, w.buf) END EOL;  PROCEDURE Copy* (src: ARRAY OF CHAR; VAR dst: String);  (* copies 'src' to the dynamical allocated String 'dst' *) BEGIN NEW(dst, S.Length(src)+1); COPY(src, dst^) END Copy;  PROCEDURE GetServerElem* (e: Elem): ServerElem;  (* returns the ServerElem which preceeds e, if its mode is left and expanded, NIL otherwhise *) VAR r: T.Reader; pos: LONGINT; BEGIN pos := T.ElemPos(e); IF pos > 0 THEN T.OpenReader(r, T.ElemBase(e), pos-1); T.ReadPrevElem(r); WHILE (r.elem # NIL) & ~(r.elem IS ServerElem) DO T.ReadPrevElem(r) END; IF (r.elem # NIL) & (r.elem IS ServerElem) & (r.elem(ServerElem).mode = FE.expLeft) THEN RETURN r.elem(ServerElem) END END; RETURN NIL END GetServerElem;  PROCEDURE SECanSwitch (e: Elem): BOOLEAN;  (* returns whether the server associated with 'e' is busy or not *) VAR se: ServerElem; BEGIN se := GetServerElem(e); ASSERT(se # NIL); RETURN ~se.conn.busy END SECanSwitch;  PROCEDURE ComputeTabNum* (e: Elem);  (* computes the number of TAB's after the last CR in front of 'e' *) CONST delta = 10; VAR a: FE.Elem; r: T.Reader; pos, startPos: LONGINT; ch: CHAR; t: T.Text; BEGIN pos := T.ElemPos(e); t := T.ElemBase(e); startPos := pos - delta; ch := 0X; WHILE (startPos > 0) & (ch#CR) DO T.OpenReader(r, t, startPos); T.Read(r, ch); WHILE ~r.eot & (T.Pos(r) 32) DO name[i] := ch; T.Read(r, ch); INC(i) END; name[i] := 0X; END NewName;  PROCEDURE DoNothing (req: NN.Request);  (* status response callback procedure; nothing to do *) BEGIN END DoNothing;  PROCEDURE HashAddr (key: ARRAY OF CHAR; htLen: LONGINT): LONGINT;  (* computes a hash address from 'key' and for a hash table with length 'htLen' *) VAR h: LONGINT; i: INTEGER; BEGIN i := 0; h := 0; WHILE key[i] # 0X DO h := ((h*charSetSize)+ORD(key[i])) MOD htLen; INC(i) END; RETURN h END HashAddr;  PROCEDURE InitHashTable (VAR ht: HashTable; size: LONGINT);  (* initializes the hash table 'ht' with size 'size' and dummy element 'ht.dummy' *) VAR i: LONGINT; BEGIN ht.used := 0; NEW(ht.table, size); NEW(ht.dummy); ht.dummy.next := ht.dummy; FOR i := 0 TO size-1 DO NEW(ht.table[i]); ht.table[i].next := ht.dummy; ht.table[i].key := NIL END END InitHashTable;  PROCEDURE HashInsert (key: ARRAY OF CHAR; VAR ht: HashTable);  (* inserts 'key' into hash table 'ht' *) VAR addr: LONGINT; cur, newEntry: HashTableEntry; BEGIN addr := HashAddr(key, LEN(ht.table^)); IF ht.table[addr].key = NIL THEN Copy(key, ht.table[addr].key); INC(ht.used) ELSE Copy(key, ht.dummy.key); cur := ht.table[addr]; WHILE cur.next.key^ < key DO cur := cur.next END; IF (cur.next.key^ > key) OR (cur.next = ht.dummy) THEN NEW(newEntry); newEntry.next := cur.next; cur.next := newEntry; newEntry.key := ht.dummy.key; ht.dummy.key := NIL; INC(ht.used) END END END HashInsert;  PROCEDURE IsInHashTable (key: ARRAY OF CHAR; ht: HashTable): BOOLEAN;  (* searches 'key' in hash table 'ht' *) VAR addr: LONGINT; cur: HashTableEntry; BEGIN addr := HashAddr(key, LEN(ht.table^)); IF ht.table[addr].key = NIL THEN RETURN FALSE ELSIF ht.table[addr].key^ = key THEN RETURN TRUE ELSE cur := ht.table[addr]; Copy(key, ht.dummy.key); WHILE cur.next.key^ < key DO cur := cur.next END; RETURN (cur.next.key^ = key) & (cur.next # ht.dummy) END END IsInHashTable;  PROCEDURE NoNotify (t: T.Text; op: INTEGER; beg, end: LONGINT);  (* dummy notifier to suppress screen uptate *) BEGIN END NoNotify;  PROCEDURE ShowText* (name, menu: ARRAY OF CHAR; text: T.Text; VAR v: MV.Viewer);  (* displays the text 'text' in a menuviewer with menu 'menu' and title 'name' *) VAR menuF, mainF: TF.Frame; x, y: INTEGER; BEGIN menuF := TF.NewMenu(name, menu); mainF := TF.NewText(text, 0); O.AllocateUserViewer(O.Mouse.X, x, y); v := MV.New(menuF, mainF, TF.menuH, x, y) END ShowText;  PROCEDURE FindNNE (t: T.Text; name: ARRAY OF CHAR): Elem;  VAR r: T.Reader; BEGIN T.OpenReader(r, t, 0); T.ReadElem(r); WHILE ~r.eot & (r.elem # NIL) DO IF (r.elem IS Elem) & (r.elem(Elem).mode IN leftMode) & (r.elem(Elem).name^=name) THEN RETURN r.elem(Elem) END; T.ReadElem(r) END; RETURN NIL END FindNNE;    PROCEDURE (e: Elem) HandlePrepSwitchMsg* (VAR m: FE.PrepSwitchMsg);  (* handle a PrepSwitchMsg; abstract method *) BEGIN HALT(99) END HandlePrepSwitchMsg;  PROCEDURE (e: Elem) HandleIdentifyMsg* (VAR m: T.IdentifyMsg);  (* handle a IdentifyMsg; abstract method *) BEGIN HALT(99) END HandleIdentifyMsg;  PROCEDURE (e: Elem) HandleCopyMsg* (VAR m: T.CopyMsg);  (* handle a CopyMsg; abstract method *) BEGIN HALT(99) END HandleCopyMsg;  PROCEDURE (e: Elem) HandleFileMsg* (VAR m: T.FileMsg);  (* handle a FileMsg; abstract method *) BEGIN HALT(99) END HandleFileMsg;  PROCEDURE (e:Elem) Display* (col, mode, x, y, displayMode: INTEGER);  (* displays Elem 'e'; abstract method *) BEGIN HALT(99) END Display;  PROCEDURE (e: Elem) Prepare*;  (* handles a DisplayMsg with prepare = TRUE *) BEGIN IF e.visible THEN e.W := elemW ELSE e.W := invisW END END Prepare;  PROCEDURE (e:Elem) CanSwitch* ():BOOLEAN;  (* return whether e can switch or not; abstract method *) BEGIN RETURN TRUE END CanSwitch;  PROCEDURE (e: Elem) Switch*;  (* switches e and its twin; source taken from FoldElems by HM and altered *) VAR a, b: Elem; apos: LONGINT; t: T.Text; buf: T.Buffer; elem: FE.Elem; BEGIN IF e.mode IN leftMode THEN a := e; elem := FE.Twin(a); b := elem(Elem) ELSE b := e; elem := FE.Twin(b); a := elem(Elem) END; IF a.mode IN tempMode THEN a.mode := FE.expLeft END; IF (a # NIL) & (b # NIL) THEN t := T.ElemBase(a); apos := T.ElemPos(a); a.mode := 3 - a.mode; b.mode := 3 - b.mode; T.Delete(t, apos+1, T.ElemPos(b)); T.Recall(buf); T.Insert(t, apos+1, a.hidden); a.hidden := buf; apos := T.ElemPos(a); t.notify(t, T.replace, apos, apos+1) END END Switch;  PROCEDURE FoldHandler (e: T.Elem; VAR m: T.ElemMsg);  (* message handler of the text element 'e' *) VAR keys: SET; x, y: INTEGER; mode: SHORTINT; a: FE.Elem; neutralize: Oberon.ControlMsg; prep: FE.PrepSwitchMsg; BEGIN WITH e: Elem DO WITH m: FE.PrepSwitchMsg DO e.HandlePrepSwitchMsg(m); | m: T.FileMsg DO FE.FoldHandler(e, m); e.HandleFileMsg(m) | m: T.CopyMsg DO e.HandleCopyMsg(m) | m: T.IdentifyMsg DO e.HandleIdentifyMsg(m) | m: TF.DisplayMsg DO IF m.prepare THEN e.Prepare ELSIF e.visible THEN e.Display(Display.white, e.mode, m.X0+x0, m.Y0+y0, Display.replace) END | m: TF.TrackMsg DO IF middleKey IN m.keys THEN neutralize.id := Oberon.neutralize; m.frame.handle(m.frame, neutralize); IF e.mode IN tempMode THEN mode := FE.expLeft ELSE mode := e.mode END; REPEAT Input.Mouse(keys, x, y); m.keys := m.keys + keys; Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, x, y) UNTIL keys = {}; IF e.CanSwitch() THEN IF (m.keys = {middleKey}) THEN IF e.mode IN leftMode THEN e.handle(e, prep) ELSE a := FE.Twin(e); a.handle(a, prep) END ELSE e.Display(Display.white, 3-mode, m.X0+x0, m.Y0+y0, Display.invert); e.Display(Display.white, mode, m.X0+x0, m.Y0+y0, Display.invert) END ELSE NN.Msg("connection is busy") END END ELSE END ELSE END END FoldHandler;  PROCEDURE (e: Elem) Init;  (* initializes e *) BEGIN e.handle := FoldHandler END Init;  PROCEDURE InsertLeftElem (e: Elem; name: ARRAY OF CHAR; numTabs: INTEGER; mode: SHORTINT);  VAR i: INTEGER; BEGIN FOR i := 1 TO numTabs DO Write(TAB) END; e.Init; e.mode := mode; e.W := FE.elemW; e.H := FE.elemH; e.visible := TRUE; NEW(e.hidden); T.OpenBuf(e.hidden); Copy(name, e.name); e.numTabs := numTabs; WriteElem(e) END InsertLeftElem;  PROCEDURE InsertRightElem (e: Elem; mode: SHORTINT);  BEGIN e.Init; e.mode := mode; e.W := FE.elemW; e.H := FE.elemH; e.visible := TRUE; WriteElem(e) END InsertRightElem;    PROCEDURE (e: ArticleElem) ChangeType* (type: SHORTINT);  (* change type and associated color of text between 'e' and its twin *) VAR fnt: Fonts.Font; elem: FE.Elem; color: SHORTINT; BEGIN IF (e.mode IN leftMode) THEN elem := e ELSE elem := FE.Twin(e); END; color := black; fnt := NIL; CASE type OF old: color := red | new: color := black | persistent: color := green END; T.ChangeLooks(T.ElemBase(elem), T.ElemPos(elem), T.ElemPos(FE.Twin(elem)), {1}, fnt, color, 0); elem(ArticleElem).type := type END ChangeType;  PROCEDURE (e:ArticleElem)CanSwitch* ():BOOLEAN;  (* switching is only possible if the corresponding connection is NOT busy *) BEGIN RETURN SECanSwitch(e) END CanSwitch;  PROCEDURE Beautify* (t: T.Text);  (* prepares text 't' containing an article for display *) VAR r: T.Reader; ch: CHAR; done: BOOLEAN; start: LONGINT; state: INTEGER; kwd: ARRAY refLen OF CHAR; i: INTEGER; PROCEDURE IsInKWDList(kwd: ARRAY OF CHAR): BOOLEAN;  (* returns, whether a header line with key word 'kwd' should be displayed or not *) VAR i: INTEGER; BEGIN IF pref.article.headerLen = shortHD THEN RETURN kwd = "Subject" ELSE FOR i := 0 TO LEN(kwdList)-1 DO IF kwdList[i] = kwd THEN RETURN TRUE END END; RETURN FALSE END END IsInKWDList;  BEGIN T.ChangeLooks(t, 0, t.len, {0}, pref.article.font, 0, 0); T.OpenReader(r, t, 0); T.Read(r, ch); done := FALSE; WHILE ~r.eot & ~done DO start := T.Pos(r)-1; WHILE ~r.eot & (ch # ":") DO T.Read(r, ch) END; IF ~r.eot & (ch = ":") THEN (* key word found; change look accordingly *) T.ChangeLooks(t, start, T.Pos(r), {0}, pref.article.keyWordFnt, 0, 0); T.OpenReader(r, t, T.Pos(r)) END; SkipLine(r, ch); IF ~r.eot THEN T.Read(r, ch); done := r.eot OR (ch = CR) ELSE done := TRUE END END; IF ~r.eot THEN state := 1; T.Read(r, ch); LOOP IF r.eot THEN EXIT END; CASE state OF 1: CASE ch OF ">", ":": (* quotation found; change look accordingly *) start := T.Pos(r); SkipLine(r, ch); IF ~r.eot THEN T.ChangeLooks(t, start, T.Pos(r), {0}, pref.article.quotedFnt, 0, 0); T.OpenReader(r, t, T.Pos(r)) ELSE EXIT END | " ": | ".": state := 2 | CR: state := 1 ELSE state := 0 END | 2: IF ch = CR THEN (* end of the article reached; remove the tailing '.' *) T.Delete(t, t.len-2, t.len); EXIT END ELSE IF ch = CR THEN state := 1 ELSE state := 0 END END; T.Read(r, ch) END END; IF pref.article.headerLen IN {shortHD, normalHD} THEN (* parts of the header have to be removed *) T.OpenReader(r, t, 0); done := FALSE; T.Read(r, ch); WHILE ~r.eot & ~done DO i := 0; start := T.Pos(r)-1; WHILE ~r.eot & (ch # ":") DO (* get key word from the beginning of the line *) kwd[i] := ch; T.Read(r, ch); INC(i) END; kwd[i] := 0X; WHILE ~r.eot & (ch # CR) DO T.Read(r, ch); INC(i) END; IF ~r.eot THEN T.Read(r, ch); done := r.eot OR (ch = CR); IF ~IsInKWDList(kwd) THEN (* if line should NOT be displayed *) T.Delete(t, start, start+i+1); IF ~done THEN T.OpenReader(r, t, start); T.Read(r, ch) END END ELSE done := TRUE END END END END Beautify;  PROCEDURE ShowArticle (req: NN.Request);  (* callback procedure; called after an article was entirely fetched *) VAR e: ArticleElem; t: T.Text; v: MV.Viewer; BEGIN e := req.data(Data).elem(ArticleElem); IF e.type # persistent THEN t := TF.Text(""); T.Save(req.tmpText, 0, req.tmpText.len, w.buf); T.Append(t, w.buf); Beautify(t); ShowText(e.subject^, "^Edit.Menu.Text", t, v); IF e.type = new THEN e.ChangeType(old) END END; T.OpenBuf(e.hidden); T.Save(req.tmpText, 0, req.tmpText.len, e.hidden) END ShowArticle;  PROCEDURE (e: ArticleElem) HandlePrepSwitchMsg* (VAR m: FE.PrepSwitchMsg);  (* causes the article associated with 'e' to be displayed *) VAR txt: T.Text; se: ServerElem; reqStr: ARRAY NN.reqLen OF CHAR; data: Data; v: MV.Viewer; BEGIN IF (e.mode IN leftMode) THEN IF ((e.type = new) OR (e.type = persistent)) & (e.hidden.len = 0) THEN se := GetServerElem(e); ASSERT(se#NIL); IF pref.useConnection & ~se.conn.IsConnected() THEN se.conn.Connect(NIL, NN.SCallBackProc) END; IF pref.useConnection THEN NEW(data); data.elem := e; COPY("article ", reqStr); S.Append(e.name^, reqStr); se.conn.Req(reqStr, NIL, data, ShowArticle, DoNothing) END ELSIF e.type = old THEN e.ChangeType(new) ELSE IF e.type = new THEN e.ChangeType(old) END; txt := TF.Text(""); T.Append(txt, e.hidden); T.Save(txt, 0, txt.len, e.hidden); Beautify(txt); ShowText(e.subject^, "^Edit.Menu.Text", txt, v) END END END HandlePrepSwitchMsg;  PROCEDURE (e: ArticleElem) HandleFileMsg* (VAR m: T.FileMsg);  (* stores / loads 'e' to / from file, according to message 'm' *) VAR s: ARRAY refLen OF CHAR; type: INTEGER; BEGIN IF e.mode IN leftMode THEN IF m.id = T.load THEN Files.ReadString(m.r, s); Copy(s, e.name); Files.ReadString(m.r, s); Copy(s, e.subject); Files.ReadString(m.r, s); Copy(s, e.references); Files.ReadString(m.r, s); Copy(s, e.from); Files.ReadLInt(m.r, e.number); Files.ReadInt(m.r, type); e.type := SHORT(type) ELSE Files.WriteString(m.r, e.name^); Files.WriteString(m.r, e.subject^); Files.WriteString(m.r, e.references^); Files.WriteString(m.r, e.from^); Files.WriteLInt(m.r, e.number); Files.WriteInt(m.r, e.type); END END END HandleFileMsg;  PROCEDURE (e: ArticleElem) HandleIdentifyMsg* (VAR m: T.IdentifyMsg);  (* handle a IdentifyMsg *) BEGIN m.mod := "NetNewsElems"; m.proc := "NewArticleElem" END HandleIdentifyMsg;  PROCEDURE (e: ArticleElem) HandleCopyMsg* (VAR m: T.CopyMsg);  (* handle a CopyMsg *) VAR a: ArticleElem; BEGIN NEW(a); T.CopyElem(e, a); a.mode := e.mode; a.visible := e.visible; IF e.mode IN leftMode THEN NEW(a.hidden); T.OpenBuf(a.hidden); T.Copy(e.hidden, a.hidden); a.name := e.name; a.subject := e.subject; a.from := e.from; a.references := e.references; a.numTabs := e.numTabs; a.valid := e.valid; a.number := e.number; a.copied := e.copied; a.markElemKey := e.markElemKey; a.copy := e.copy END; a.type := e.type; m.e := a END HandleCopyMsg;  PROCEDURE (e: ArticleElem) Display* (col, mode, x, y, displayMode: INTEGER);  (* displays 'e' *) BEGIN Display.CopyPattern(col, articlePat[mode], x, y, displayMode) END Display;  PROCEDURE (e: ArticleElem) Prepare*;  (* handles sa display message with prepare set to TRUE *) BEGIN IF e.visible THEN e.W := FE.elemW ELSE e.W := invisW END END Prepare;  PROCEDURE (e: ArticleElem) Init*;  (* initializes 'e'; has to be called prior to a usage of 'e' *) BEGIN e.Init^; e.type := new; e.valid := TRUE; e.copied := FALSE; e.markElemKey := noMarkElem END Init;  PROCEDURE InsertArticleElem1* (t: T.Text; pos: LONGINT; name, subject, references, from: ARRAY OF CHAR;num: LONGINT; numTabs:INTEGER; type: SHORTINT; valid: BOOLEAN);  (* inserts an ArticleElem pair at position 'pos' into text 't' *) VAR e: ArticleElem; BEGIN NEW(e); InsertLeftElem(e, name, numTabs, FE.expLeft); Copy(subject, e.subject); Copy(references, e.references); Copy(from, e.from); e.valid := valid; e.number := num; WriteString(subject); IF pref.showFromEntry THEN WriteString(" - "); WriteString(from) END; NEW(e); InsertRightElem(e, FE.expRight); T.Insert(t, pos, w.buf); e.ChangeType(type) END InsertArticleElem1;  PROCEDURE InsertArticleElem2* (t: T.Text; pos: LONGINT; numTabs: INTEGER; e: ArticleElem; valid: BOOLEAN);  (* inserts an ArticleElem pair with 'e' as its left component at position 'pos' into text 't' *) VAR re: ArticleElem; i: INTEGER; BEGIN FOR i := 1 TO numTabs DO Write(TAB) END; e.valid := valid; e.numTabs := numTabs; WriteElem(e); WriteString(e.subject^); IF pref.showFromEntry THEN WriteString(" - "); WriteString(e.from^) END; NEW(re); InsertRightElem(re, FE.expRight); T.Insert(t, pos, w.buf); e.ChangeType(e.type) END InsertArticleElem2;  PROCEDURE NewArticleElem*;  VAR e: ArticleElem; BEGIN NEW(e); e.Init; T.new := e END NewArticleElem;    PROCEDURE (e: GroupElem) IsOldArticle (num: LONGINT): BOOLEAN;  (* returns whether the article with number 'num' was already read; algorithm: binary search *) VAR x, l, r: LONGINT; BEGIN IF e.oldArticles = NIL THEN RETURN FALSE END; r := LEN(e.oldArticles^)-1; l := 0; REPEAT x := (l+r) DIV 2; IF num < e.oldArticles^[x].beg THEN r := x-1 ELSE l := x+1 END UNTIL ((num >= e.oldArticles^[x].beg) & (num <= e.oldArticles^[x].end)) OR (l > r); RETURN (num >= e.oldArticles^[x].beg) & (num <= e.oldArticles^[x].end) END IsOldArticle;  PROCEDURE InsertArticles (req: NN.Request);  (* inserts the next pref.maxArticles articles into the text *) VAR r: T.Reader; subject, from: ARRAY subjectLen OF CHAR; messageID: ARRAY nameLen OF CHAR; references: ARRAY refLen OF CHAR; i, j, k, l : INTEGER; cMsg: T.CopyMsg; leftGE, rightGE: GroupElem; eText: T.Text; elem: FE.Elem; valid, referenced: BOOLEAN; pos, firstPos, lastPos, num: LONGINT; cur, prev, fe, le: ArticleElem; notify: T.Notifier; ch: CHAR; me: ME.Elem; PROCEDURE GetHeaderEntries(VAR r: T.Reader; VAR mID, sub, ref, from: ARRAY OF CHAR; VAR curArticle: LONGINT): BOOLEAN;  (* reads sone info entries from a line (for a closer description see 'NetNews.Text' *) VAR dummy: ARRAY refLen OF CHAR; references: BOOLEAN; ch: CHAR; PROCEDURE ReadItem(VAR s: ARRAY OF CHAR): BOOLEAN;  (* reads an info entry from a line *) VAR i: INTEGER; BEGIN i := 0; WHILE ~r.eot & (ORD(ch) >= 32) & (i= "0") & (ch <= "9") THEN curArticle := 0; WHILE ~r.eot & (ch >= "0") & (ch <= "9") DO curArticle := curArticle*10+ORD(ch)-48; T.Read(r, ch) END; T.Read(r, ch) ELSE RETURN FALSE END; IF ~ReadItem(sub) THEN RETURN FALSE END; IF ~ReadItem(from) THEN RETURN FALSE END; IF ~ReadItem(dummy) THEN RETURN FALSE END; references := ch # TAB; IF ~references THEN T.Read(r, ch) END; IF ~ReadItem(mID) THEN RETURN FALSE END; IF references THEN IF ~ReadItem(ref) THEN RETURN FALSE END ELSE ref[0] := 0X END; WHILE ~r.eot & (ch # CR) DO T.Read(r, ch) END; RETURN TRUE END GetHeaderEntries;  BEGIN WriteString("trying to show the next "); WriteInt(pref.maxArticles, 0); WriteString(" articles"); EOL; leftGE := req.data(Data).elem(GroupElem); elem := FE.Twin(leftGE); rightGE := elem(GroupElem); eText := T.ElemBase(leftGE); IF pref.useConnection THEN IF leftGE.first THEN T.OpenReader(leftGE.aReader, leftGE.aText, 0); leftGE.first := FALSE END; END; notify := eText.notify; eText.notify := NoNotify; (* remove the name of the group prior to an article insertion *) IF leftGE.mode # FE.colLeft THEN pos := T.ElemPos(leftGE)+1; T.OpenReader(r, eText, pos); T.Read(r, ch); WHILE ~r.eot & (ORD(ch) > 32) DO T.Read(r, ch); END; T.Delete(eText, pos, T.Pos(r)-1) END; ComputeTabNum(leftGE); num := 0; IF pref.useConnection THEN (* insert new articles: referencing articles at the end, not referencing articles at the beginning *) WHILE (num lastPos THEN lastPos := pos; le := cur END; cur.next := leftGE.list; leftGE.list := cur END END END; T.ReadElem(r) END; IF firstPos < MAX(LONGINT) THEN firstPos := firstPos-fe.numTabs-1; IF lastPos > MIN(LONGINT) THEN lastPos := T.ElemPos(FE.Twin(le)) ELSE lastPos := T.ElemPos(FE.Twin(fe)) END; T.Delete(eText, firstPos, lastPos+1) END; (* try to reference articles *) T.OpenReader(r, eText, T.ElemPos(leftGE)+1); T.ReadElem(r); WHILE (r.elem # NIL) & ~(r.elem IS GroupElem) DO IF (r.elem IS ArticleElem) & (r.elem(ArticleElem).mode IN leftMode) THEN cur := leftGE.list; prev := NIL; fe := r.elem(ArticleElem); elem := FE.Twin(fe); le := elem(ArticleElem); WHILE cur # NIL DO referenced := FALSE; k := 0; WHILE (cur.references^[k] # 0X) & ~referenced DO j := 0; l := k; WHILE (fe.name^[j] = cur.references^[k]) & (fe.name^[j] # ">") & (fe.name^[j] # 0X) & (cur.references^[k] # 0X) DO INC(j); INC(k) END; referenced := (fe.name^[j] = cur.references^[k]) & (fe.name^[j] = ">"); IF referenced THEN IF ~cur.copied THEN (* copy it and insert the copy *) WriteLn; cur.copied := TRUE; cur.handle(cur, cMsg); InsertArticleElem2(eText, T.ElemPos(le)+1, fe.numTabs+1, cMsg.e(ArticleElem), TRUE); cur.copy := cMsg.e(ArticleElem) ELSE (* a copy was already inserted *) IF cur.copy.markElemKey = noMarkElem THEN (* if necessary, insert a MarkElem *) INC(markElemKey); cur.copy.markElemKey := markElemKey; me := ME.New(); me.key := cur.copy.markElemKey; T.WriteElem(w, me); T.Insert(eText, T.ElemPos(cur.copy)+1, w.buf) END; WriteLn; FOR i := 1 TO fe.numTabs+1 DO Write(TAB) END; T.WriteElem(w, LE.New("*", cur.copy.markElemKey)); WriteString(cur.copy.subject^); T.Insert(eText, T.ElemPos(le)+1, w.buf) END; S.Delete(cur.references^, l, k-l+1); (* remove reference from list *) IF cur.references^[0] = 0X THEN IF prev = NIL THEN leftGE.list := cur.next ELSE prev.next := cur.next END ELSE prev := cur END ELSE (* skip to the start of the next reference *) WHILE (cur.references^[k] # 0X) & (cur.references^[k] # "<") DO INC(k) END; prev := cur END END; cur := cur.next END END; T.ReadElem(r) END; IF leftGE.curArticle < leftGE.lastArticle THEN (* add articles with not empty reference list to the end *) WHILE leftGE.list # NIL DO WriteLn; InsertArticleElem2(eText, T.ElemPos(rightGE), leftGE.numTabs+1, leftGE.list, FALSE); leftGE.list := leftGE.list.next END END ELSE leftGE.list := NIL END; IF leftGE.mode=FE.expLeft THEN WriteString(leftGE.name^); T.Insert(eText, T.ElemPos(leftGE)+1, w.buf) ELSE NN.Msg("no articles to show") END; eText.notify := notify; eText.notify(eText, T.replace, 0, eText.len) END InsertArticles;  PROCEDURE StartInfoFetching (req: NN.Request);  (* initializes the properties of a groupElem and starts the fetching of article information *) VAR e: GroupElem; i: INTEGER; reqStr: ARRAY NN.reqLen OF CHAR; num: ARRAY numLen OF CHAR; PROCEDURE GetNumber(VAR num: LONGINT);  BEGIN num := 0; WHILE (req.sResponse[i] >= "0") & (req.sResponse[i] <= "9") DO num := num*10+ORD(req.sResponse[i])-48; INC(i) END; INC(i) END GetNumber;  BEGIN NN.SCallBackProc(req); IF ORD(req.sResponse[0]) - 48 IN {1,2} THEN e := req.data(Data).elem(GroupElem); i := 4; GetNumber(e.noArticles); GetNumber(e.firstArticle); GetNumber(e.lastArticle); IF e.lastArticle <= e.firstArticle THEN NN.Msg("no articles to fetch") ELSE e.first := TRUE; e.curArticle := e.firstArticle; reqStr := "xover "; LIntToStr(e.firstArticle, num); S.Append(num, reqStr); S.Append("-", reqStr); LIntToStr(e.lastArticle, num); S.Append(num, reqStr); e.aText := TF.Text(""); req.conn.Req(reqStr, e.aText, req.data, InsertArticles, NN.SCallBackProc) END ELSE NN.SCallBackProc(req) END END StartInfoFetching;  PROCEDURE (e: GroupElem) HandlePrepSwitchMsg* (VAR m: FE.PrepSwitchMsg);  (* handles the PrepSwitchMsg for element 'e' *) VAR t, perT: T.Text; reqStr: ARRAY NN.reqLen OF CHAR; se: ServerElem; name, fileName: ARRAY nameLen OF CHAR; data: Data; pos: LONGINT; nameChanged: BOOLEAN; v: Viewers.Viewer; s: T.Scanner; prev, cur, oldArticles: ArticleElem; notify: T.Notifier; pArticles: HashTable; req: NN.Request; PROCEDURE SetScannerPos(VAR s: T.Scanner; VAR t: T.Text; fName, server, group: ARRAY OF CHAR; VAR beg: LONGINT): BOOLEAN;  VAR ch: CHAR; error: BOOLEAN; srv, grp: ARRAY refLen OF CHAR; PROCEDURE ReadItem(VAR str: ARRAY OF CHAR);  VAR i: INTEGER; BEGIN i := 0; WHILE ~s.eot & (i= LEN(rl^))) & (al = NIL) THEN RETURN FALSE END; IF (al = NIL) OR ((rl # NIL) & (i < LEN(rl^)) & (rl[i].beg < al.number)) THEN beg := rl[i].beg; end := rl[i].end; INC(i) ELSE beg := al.number; end := al.number; al := al.next; WHILE (al # NIL) & (al.number = end+1) DO INC(end); al := al.next END END; RETURN TRUE END GetRange;  BEGIN  IF SetScannerPos(s, t, fName, server, group, pos) THEN SkipLine(s, ch); IF s.eot THEN T.Delete(t, pos, t.len) ELSE T.Delete(t, pos, T.Pos(s)) END END; IF rl # NIL THEN IF e.lastArticle < rl[0].beg THEN rl := NIL ELSE i := 0; IF (i rl[i].end) THEN REPEAT INC(i) UNTIL (i>=LEN(rl^)) OR (e.firstArticle <= rl[i].end); IF (i end1 THEN IF beg2 <= end1+1 THEN end1 := end2 ELSE WriteRange(beg1, end1, FALSE); beg1 := beg2; end1 := end2 END END END; WriteRange(beg1, end1, TRUE); T.Append(t, w.buf) END; T.Close(t, fName)  END SetOldList;  PROCEDURE RebuildReferences (e: ArticleElem);  (* rebuilds the reference list for a persistent article (needed for article threading of persistent articles) *) VAR t: T.Text; r: T.Reader; ch: CHAR; i: INTEGER; ref: ARRAY refLen OF CHAR; done: BOOLEAN; BEGIN ref[0] := 0X; t := TF.Text(""); T.Append(t, e.hidden); T.Save(t, 0, t.len, e.hidden); T.OpenReader(r, t, 0); T.Read(r, ch); i := 0; done := FALSE; WHILE ~r.eot & ~done DO i := 0; WHILE ~r.eot & (ch # CR) & (ch # ":") & (i