<<>> <> <> <> <> <> <> <> <> <> <> <<>> DIRECTORY FS USING [Error, StreamOpen], FileNames USING [ConvertToSlashFormat], Convert USING [RopeFromInt], IO, IOClasses USING [CreateCommentFilterStream], IconRegistry USING [Failure], Icons USING [NewIconFromFile, IconFlavor], Process USING [Detach], Rope USING [Concat, Cat, Equal, IsEmpty, ROPE], SimpleFeedback USING [Append], UserProfile USING [CallWhenProfileChanges, ProfileChangedProc, Token], ViewerClasses USING [Viewer], ViewerEvents USING [EventProc, RegisterEventProc], ViewerOps USING [FindViewer, RestoreViewer], VM USING [AddressFault]; IconRegistryImpl: CEDAR MONITOR IMPORTS FileNames, Convert, FS, IO, Icons, IOClasses, Process, Rope, SimpleFeedback, UserProfile, ViewerEvents, ViewerOps, VM EXPORTS IconRegistry = BEGIN <> IconList: TYPE = LIST OF IconEntry; IconEntry: TYPE = REF IconRecord; IconRecord: TYPE = RECORD[ iconName: Rope.ROPE ¬ NIL, fileName: Rope.ROPE ¬ NIL, index: CARDINAL ¬ LAST[CARDINAL], flavor: Icons.IconFlavor ¬ unInit, position: INT ¬ -1 -- in catalogue ]; Failed: PUBLIC ERROR [why: IconRegistry.Failure, reason: Rope.ROPE] = CODE; <> RegisterIcon: PUBLIC ENTRY PROC [iconName: Rope.ROPE, fileName: Rope.ROPE, index: CARDINAL, saveInCatalogue: BOOL ¬ FALSE] = { <> ENABLE UNWIND => NULL; entry: IconEntry; <> FOR l: IconList ¬ iconList, l.rest UNTIL l = NIL DO IF Rope.Equal[l.first.iconName, iconName, FALSE] THEN { IF Rope.Equal[l.first.fileName, fileName, FALSE] THEN { IF l.first.index = index OR NOT saveInCatalogue THEN RETURN[]; -- need way to specify whether or not this definition is to redefine. currently overloading on saveInCatalogue. Idea is that program can register and if user has already registered, then user's takes precedence entry ¬ l.first; EXIT; }; LOOP; }; REPEAT FINISHED => TRUSTED { entry ¬ NEW[IconRecord ¬ []]; iconList ¬ CONS[entry, iconList]; }; ENDLOOP; entry­ ¬ [iconName: iconName, fileName: fileName, index: index, flavor: unInit, position: -1]; IF saveInCatalogue THEN WriteEntry[entry]; }; GetIcon: PUBLIC ENTRY PROC [iconName: Rope.ROPE, default: Icons.IconFlavor ¬ unInit] RETURNS [Icons.IconFlavor] = { <> <> ENABLE UNWIND => NULL; < IF default # unInit THEN GOTO Default]; -- first time>> FOR l: IconList ¬ iconList, l.rest UNTIL l = NIL DO IF Rope.Equal[l.first.iconName, iconName, FALSE] THEN { IF l.first.flavor = unInit THEN l.first.flavor ¬ Icons.NewIconFromFile[l.first.fileName, l.first.index ! FS.Error => <> IF default # unInit THEN GOTO Default ELSE ERROR Failed[fileNotFound, l.first.fileName]; VM.AddressFault => IF default # unInit THEN GOTO Default ELSE ERROR Failed[invalidIndex, Convert.RopeFromInt[l.first.index]]; ]; -- NewIconFromFile raises this if given badindex. RETURN[l.first.flavor]; }; ENDLOOP; IF default # unInit THEN GOTO Default; ERROR Failed[noSuchIcon, iconName]; EXITS Default => RETURN[default]; }; IsRegistered: PUBLIC ENTRY PROC[iconName: Rope.ROPE ¬ NIL, fileName: Rope.ROPE ¬ NIL, index: CARDINAL ¬ LAST[CARDINAL]] RETURNS[name: Rope.ROPE, file: Rope.ROPE, i: CARDINAL] = { <> < GOTO Out]; -- first time>> ENABLE UNWIND => NULL; FOR l: IconList ¬ iconList, l.rest UNTIL l = NIL DO IF (IF iconName # NIL THEN Rope.Equal[l.first.iconName, iconName, FALSE] ELSE Rope.Equal[l.first.fileName, fileName, FALSE] AND l.first.index = index) THEN RETURN[l.first.iconName, l.first.fileName, l.first.index]; ENDLOOP; RETURN[NIL, NIL, 0]; }; Lookup: PROC[iconName: Rope.ROPE] RETURNS[IconEntry] = { <> FOR l: IconList ¬ iconList, l.rest UNTIL l = NIL DO IF Rope.Equal[l.first.iconName, iconName, FALSE] THEN RETURN[l.first]; ENDLOOP; RETURN[NIL]; }; InvalidateCache: PUBLIC PROC [iconName: Rope.ROPE ¬ NIL] = { <> FOR l: IconList ¬ iconList, l.rest UNTIL l = NIL DO IF iconName = NIL OR Rope.Equal[l.first.iconName, iconName, FALSE] THEN l.first.flavor ¬ unInit; ENDLOOP; }; <> iconList: IconList ¬ NIL; catalogueName: Rope.ROPE ¬ "RegisteredIcons.Catalogue"; ParseCatalogue: ENTRY PROCEDURE = { ENABLE UNWIND => NULL; stream: IO.STREAM; stream ¬ IOClasses.CreateCommentFilterStream[FS.StreamOpen[fileName: catalogueName, accessOptions: read ! FS.Error => CONTINUE]]; -- change interface not to explain that it doesn't raise an error if no catalogue. IF stream # NIL THEN { ReadIcons[stream]; stream.Close[]; }; }; ReadIcons: PROCEDURE [stream: IO.STREAM] = { OPEN IO; ris: IO.STREAM ¬ NIL; { ENABLE { Failed => REJECT; IO.EndOfStream => GOTO Out; }; DO tokenProc: IO.BreakProc = { RETURN[SELECT char FROM IO.SP, IO.TAB, ', => sepr, IO.CR => break, ENDCASE => other ]; }; <> <> <<};>> <> <> <<};>> entry: IconEntry; iconName, fileName: Rope.ROPE; index: CARDINAL; position: INT; [] ¬ IO.SkipWhitespace[stream]; position ¬ stream.GetIndex[]; iconName ¬ IO.GetTokenRope[stream].token; <<-- see if key is followed by a :>> <> IF stream.PeekChar[] # ': THEN { Report1[msg: Rope.Cat["missing : at [", Convert.RopeFromInt[position], "]"]]; [] ¬ IO.GetLineRope[stream]; LOOP; }; [] ¬ stream.GetChar[]; -- the : ris ¬ IO.RIS[IO.GetLineRope[stream], ris]; -- read to CR, make this a stream. Reason is to bound the scope of the GetTokenRope, GetCards below. fileName ¬ FileNames.ConvertToSlashFormat[IO.GetTokenRope[ris, tokenProc].token]; IF Rope.IsEmpty[fileName] THEN { Report1[msg: Rope.Cat["missing fileName at [", Convert.RopeFromInt[position], "]"]]; LOOP; }; <> <> index ¬ IO.GetCard[ris]; entry ¬ Lookup[iconName]; IF entry # NIL THEN { entry.position ¬ position; -- comments may have changed. <> IF NOT (Rope.Equal[entry.fileName, fileName, FALSE] AND index = entry.index) THEN { entry.fileName ¬ fileName; entry.index ¬ index; entry.flavor ¬ unInit; }; } ELSE iconList ¬ CONS[NEW[IconRecord ¬ [iconName: iconName, fileName: fileName, index: index, position: position]], iconList]; ENDLOOP; EXITS Out => NULL; }; }; WriteEntry: INTERNAL PROC [entry: IconEntry] = { OPEN IO; viewer: ViewerClasses.Viewer; stream: STREAM = FS.StreamOpen[fileName: catalogueName, accessOptions: append]; WriteOne[entry, stream]; stream.Close[]; IF (viewer ¬ ViewerOps.FindViewer[catalogueName]) # NIL THEN ViewerOps.RestoreViewer[viewer]; }; WriteOne: INTERNAL PROC [entry: IconEntry, stream: IO.STREAM] = { OPEN IO; entry.position ¬ stream.GetIndex[]; stream.PutF["\n%g: %g %d", rope[entry.iconName], rope[entry.fileName], int[entry.index]]; }; WriteCatalogue: PUBLIC ENTRY PROC [] = { OPEN IO; ENABLE UNWIND => NULL; viewer: ViewerClasses.Viewer; stream: STREAM; lst: IconList ¬ NIL; FOR l: IconList ¬ iconList, l.rest UNTIL l = NIL DO IF l.first.position = -1 THEN lst ¬ CONS[l.first, lst]; -- need to write them out in reverse order seen on lst, i.e. first one seen gets written out last ENDLOOP; IF lst = NIL THEN RETURN; stream ¬ FS.StreamOpen[fileName: catalogueName, accessOptions: append]; FOR l: IconList ¬ lst, l.rest UNTIL l = NIL DO WriteOne[l.first, stream]; ENDLOOP; stream.Close[]; IF (viewer ¬ ViewerOps.FindViewer[catalogueName]) # NIL THEN ViewerOps.RestoreViewer[viewer]; }; <> Report1: PROCEDURE [entry: IconEntry ¬ NIL, msg: Rope.ROPE] = { IF entry # NIL THEN msg ¬ msg.Concat[IO.PutFR[", at %g [%d]", [rope[entry.iconName]], [integer[entry.position]]]]; SimpleFeedback.Append[$IconRegistry, begin, $Error, IO.PutFR1["IconRegistry error catalogue/profile at %t", IO.time[]]]; SimpleFeedback.Append[$IconRegistry, end, $Error, msg]; }; <> WasCatalogueEdited: ViewerEvents.EventProc = { <> IF Rope.Equal[viewer.name, catalogueName, FALSE] THEN TRUSTED {Process.Detach[FORK ParseCatalogue[]]}; }; IconsFromProfile: UserProfile.ProfileChangedProc = { r: Rope.ROPE ¬ UserProfile.Token["RegisteredIcons"]; IF r # NIL THEN ReadIcons[IO.RIS[r]]; }; <> [] ¬ ViewerEvents.RegisterEventProc[proc: WasCatalogueEdited, event: save, before: FALSE]; ParseCatalogue[]; UserProfile.CallWhenProfileChanges[IconsFromProfile]; END. <> <> <> <> <<>> <> <> <> <> <> <<>> <> <>