-- TEditDocumentsImpl.mesa; Written by S. McGregor
-- Edited by McGregor on March 28, 1983 9:21 am
-- Edited by Paxton on June 2, 1983 10:39 am
-- Edited by Maxwell, January 6, 1983 11:03 am
-- Edited by Russ Atkinson, April 15, 1983 2:31 pm
Last Edited by: October 12, 1983 9:33 am
DIRECTORY
Atom USING [Gensym, GetPName],
Buttons,
CedarSnapshot USING [After, Register],
CIFS USING [Close, GetFC, Open, OpenFile, read, Error],
Convert USING [ValueToRope],
Directory USING [Error, GetProperty, LookupUnlimited, RemoveFile, Rename],
File USING [Capability, Delete, nullCapability],
FileIO USING [OpenFailed],
InputFocus USING [GetInputFocus, SetInputFocus],
MessageWindow USING [Append, Blink, Clear],
NodeProps USING [GetProp, MapProps, PutProp],
Process USING [Detach, GetCurrent],
PropertyTypes USING [tCreateDate],
PutGet USING [FromFile, ToFile, FromRope, ToRope, WriteRopePlain],
EditSpanSupport USING [Apply],
Rope USING [Cat, Concat, Equal, Find, Flatten, FromRefText, Length, ROPE, Substr, Text],
RTFiles USING [IsFileInUse],
System USING [GreenwichMeanTime, SecondsSinceEpoch],
TextEdit USING [DocFromNode, FromRope, Ref],
TextLooks USING [noLooks],
TextNode USING [FirstChild, LastLocWithin, LocNumber, Location, LocOffset, LocRelative, LocWithin, Offset, NarrowToTextNode, NodeProps, pZone, Ref, RefTextNode, Root, TypeName],
TEditCompile USING [minAvgLineLeading],
TEditDocument USING [ForgetViewer, LineTableRec, RecordViewerForRoot, Selection, TEditDocumentData, TEditDocumentDataRec],
TEditDocumentPrivate,
TEditImpl USING [PaintTEditDocument, TEditNotifyProc],
TEditInput USING [FreeTree],
TEditInputOps USING [CallWithLocks],
TEditLocks USING [Lock, LockDocAndTdd, Unlock, UnlockDocAndTdd],
TEditProfile USING [DoList],
TEditRefresh USING [ScrollToEndOfSel],
TEditScrolling USING [ScrollTEditDocument],
TEditSelection USING [Alloc, Free, InputModify, MakeSelection, pSel, sSel, fSel],
TEditTouchup USING [fullUpdate],
TIPUser USING [InstantiateNewTIPTable, InvalidTable, TIPTable],
ViewerClasses USING [DestroyProc, GetProc, InitProc, SaveProc, SetProc, Viewer, ViewerClass, ViewerClassRec],
ViewerOps,
ViewerTools USING [EnableUserEdits, InhibitUserEdits, SelPos, SelPosRec, TiogaContents, TiogaContentsRec];
TEditDocumentsImpl: CEDAR MONITOR
IMPORTS Atom, Buttons, CIFS, CedarSnapshot, Convert, Directory, EditSpanSupport, File, FileIO, InputFocus, MessageWindow, NodeProps, Process, PutGet, Rope, RTFiles, System, TEditDocument, TEditDocumentPrivate, TextEdit, TextNode, TEditImpl, TEditInput, TEditInputOps, TEditLocks, TEditProfile, TEditRefresh, TEditTouchup, TEditScrolling, TEditSelection, TIPUser, ViewerOps, ViewerTools
EXPORTS
TEditImpl,
SaveTEditDocument, InitTEditDocument, tiogaTIP, ReloadTipTable, ReloadReadonlyTipTable, ReloadTable
TEditDocument,
SpinAndLock, Unlock, fatalTiogaError
TEditDocumentPrivate
InitViewerDoc, FindMenu, LevelMenu, findMenu, levelMenu
= BEGIN OPEN TEditDocument, TEditDocumentPrivate;
Viewer: TYPE = ViewerClasses.Viewer;
ROPE: TYPE = Rope.ROPE;
fatalTiogaError: PUBLIC ERROR = CODE ; -- for RETURN WITH ERROR monitor clearing punts
InitTEditDocument: PUBLIC ViewerClasses.InitProc = {
data: REF ANY ← self.data;
self.data ← NIL;
InitViewerDoc[self, data] };
InitViewerDoc: PUBLIC PROC [self: Viewer, data: REF ANY] = BEGIN
tdd: TEditDocumentData ← NARROW[self.data];
InitTddText: PROC = {
IF tdd.text # NIL THEN TEditInput.FreeTree[tdd.text];
tdd.text ← NIL;
IF self.name.Length#0 THEN tdd.text ←
TextNode.NarrowToTextNode[PutGet.FromFile[self.name
! CIFS.Error => {self.newFile ← TRUE; CONTINUE}]];
IF tdd.text=NIL THEN tdd.text ←
TextNode.NarrowToTextNode[TextEdit.DocFromNode[TextEdit.FromRope[""]]] };
InitLineTable: PROC = {
IF NodeProps.GetProp[tdd.text, $OpenFirstLevelOnly]#NIL THEN tdd.clipLevel ← 1;
tdd.lineTable.lastLine ← 0;
tdd.lineTable[0].pos ← [TextNode.NarrowToTextNode[TextNode.FirstChild[tdd.text]], 0] };
IF self.link#NIL THEN BEGIN
-- make sure another link didn't already init
-- and if so, copy that data
otherInit: Viewer ← NIL;
IF tdd=NIL THEN self.data ← tdd ← NARROW[data]; -- someday I should really find out how to fix this in a less horrible manner
[] ← SpinAndLock[tdd, "InitViewerDoc"];
FOR v: Viewer ← self.link, v.link UNTIL v=self DO
IF ~v.newVersion THEN {otherInit ← v; EXIT};
ENDLOOP;
IF otherInit=NIL THEN InitTddText[]
ELSE BEGIN-- somebody else already did the init
otherTdd: TEditDocumentData ← NARROW[otherInit.data];
tdd.text ← otherTdd.text;
END;
InitLineTable[];
END
ELSE IF data=NIL THEN BEGIN
IF tdd=NIL THEN tdd ← AllocateDataForNewViewer[self];
[] ← SpinAndLock[tdd, "InitViewerDoc"];
InitTddText[];
InitLineTable[];
END
ELSE WITH data SELECT FROM
d: TEditDocumentData => BEGIN
self.data ← tdd ← d;
[] ← SpinAndLock[tdd, "InitViewerDoc"];
InitTddText[];
InitLineTable[];
END;
root: TextNode.RefTextNode => BEGIN
IF tdd=NIL THEN tdd ← AllocateDataForNewViewer[self];
[] ← SpinAndLock[tdd, "InitViewerDoc"];
tdd.text ← root;
InitLineTable[];
END;
r: ROPE => BEGIN
IF tdd=NIL THEN tdd ← AllocateDataForNewViewer[self];
[] ← SpinAndLock[tdd, "InitViewerDoc"];
tdd.text ← TextNode.NarrowToTextNode[
TextEdit.DocFromNode[TextEdit.FromRope[r]]];
InitLineTable[];
END;
t: REF TEXT => BEGIN
IF tdd=NIL THEN tdd ← AllocateDataForNewViewer[self];
[] ← SpinAndLock[tdd, "InitViewerDoc"];
tdd.text ← TextNode.NarrowToTextNode[
TextEdit.DocFromNode[TextEdit.FromRope[Rope.FromRefText[t]]]];
InitLineTable[];
END;
ENDCASE => BEGIN-- bad data
IF tdd=NIL THEN tdd ← AllocateDataForNewViewer[self];
[] ← SpinAndLock[tdd, "InitViewerDoc"];
tdd.text ← TextNode.NarrowToTextNode[PutGet.FromRope["*ERROR*"]];
InitLineTable[];
END;
RecordViewerForRoot[self,tdd.text];
IF HasBrackets[self.name] THEN ViewerTools.InhibitUserEdits[self]
ELSE ViewerTools.EnableUserEdits[self];
Unlock[tdd];
END;
HasBrackets: PROC [file: ROPE] RETURNS [BOOL] = {
Has: PROC [delim: ROPE] RETURNS [BOOL] = {
RETURN [Rope.Find[file, delim] # -1] };
RETURN [Has["/"] OR Has["["] OR Has["<"]] };
AllocateDataForNewViewer: PROC [self: Viewer] RETURNS [tdd: TEditDocumentData] = BEGIN
tdd ← TextNode.pZone.NEW[TEditDocumentDataRec];
-- for now, just hack in a conservative size line table
tdd.lineTable ← TextNode.pZone.NEW[LineTableRec[MAX[2, (self.ch/TEditCompile.minAvgLineLeading)+1]] ←
[lastLine: 0, lastY: 0, lines: NULL]];
IF self.column#static AND self.parent=NIL THEN ViewerOps.SetMenu[self, tiogaMenu, FALSE];
self.data ← tdd;
END;
SaveTEditDocument: PUBLIC ViewerClasses.SaveProc = BEGIN
tdd: TEditDocumentData ← NARROW[self.data];
dollarCap: File.Capability ← File.nullCapability;
root: TextNode.Ref;
file: Rope.Text ← Rope.Flatten[self.name]; -- makes LOOPHOLE to LONG STRING OK
dollarFile: Rope.Text ← Rope.Flatten[Rope.Concat[file, "$"]];
IF file.Length = 0 OR file.Equal["No Name"] THEN BEGIN
IF ~force THEN BEGIN
MessageWindow.Append["Can't SAVE -- no file name!", TRUE];
MessageWindow.Blink[];
RETURN [ok: FALSE];
END;
file ← Rope.Flatten[Rope.Concat[Atom.GetPName[Atom.Gensym['F]], ".SaveAllEdits"]];
dollarFile ← Rope.Flatten[Rope.Concat[file, "$"]];
END;
IF tdd.readOnly THEN BEGIN
MessageWindow.Append["Can't SAVE -- read only document!", TRUE];
MessageWindow.Blink[];
RETURN [ok: FALSE];
END;
IF ~force THEN { -- lock so no edits while saving
MessageWindow.Clear[];
[] ← SpinAndLock[tdd, "SaveTEditDocument"]; -- may have other operation in progress
root ← tdd.text;
Unlock[tdd]; -- don't need to keep this locked during rest of Save
IF FileIsMoreRecent[root, file] THEN { -- the create date of the file on the disk
MessageWindow.Append[Rope.Concat[file," on disk is newer than this one."], TRUE];
MessageWindow.Blink[] };
[] ← TEditLocks.Lock[root, "SaveTEditDocument", read] }
ELSE root ← tdd.text;
TRUSTED {
FOR dollarIndex: NAT IN [0..1000) DO
dollarCap ← Directory.LookupUnlimited[
fileName: LOOPHOLE[dollarFile]
! Directory.Error => CONTINUE];
IF dollarCap#File.nullCapability THEN {
RRA: The file is in the directory, but we must check to see if the file is in use as far as the system is concerned, so we do not delete a file out from under someone.
IF RTFiles.IsFileInUse[dollarCap] THEN {
dollarCap ← File.nullCapability;
dollarFile ←
Rope.Flatten[Rope.Cat[file, "$", Convert.ValueToRope[[signed[dollarIndex]]], "$"]];
IF dollarIndex = 999 THEN ERROR;
LOOP;
};
Directory.RemoveFile[LOOPHOLE[dollarFile], dollarCap];
};
EXIT;
ENDLOOP;
Directory.Rename[LOOPHOLE[file], LOOPHOLE[dollarFile]
! Directory.Error => CONTINUE];
};
[] ← PutGet.ToFile[file, root ! FileIO.OpenFailed => {
IF why=illegalFileName THEN {
MessageWindow.Append[file, TRUE];
MessageWindow.Append[" is an illegal file name. Operation aborted."];
MessageWindow.Blink[];
CONTINUE }}];
IF dollarCap#File.nullCapability THEN
TRUSTED {Process.Detach[FORK ReclaimDollarFile[dollarCap]]};
IF ~force THEN TEditLocks.Unlock[root];
RETURN [ok: TRUE]
END;
FileIsMoreRecent: PROC [root: TextNode.Ref, file: ROPE] RETURNS [BOOL] = TRUSTED {
Returns true if the file on the disk has a more recent create date than the root file create date
prop: REF System.GreenwichMeanTime;
fh: CIFS.OpenFile;
createDate: System.GreenwichMeanTime;
fileCap: File.Capability;
IF (prop ← NARROW[NodeProps.GetProp[root, $FileCreateDate]]) = NIL THEN RETURN [FALSE];
fh ← CIFS.Open[file, CIFS.read ! CIFS.Error => {CONTINUE}];
IF fh = NIL THEN RETURN [FALSE];
fileCap ← CIFS.GetFC[fh];
CIFS.Close[fh];
Directory.GetProperty[file: fileCap, property: PropertyTypes.tCreateDate,
propertyValue: DESCRIPTOR[@createDate, SIZE[System.GreenwichMeanTime]]];
RETURN [System.SecondsSinceEpoch[prop^] < System.SecondsSinceEpoch[createDate]] };
ReclaimDollarFile: PROC [file: File.Capability] = TRUSTED {
File.Delete[file ! ABORTED => CONTINUE];
};
nullNode: TextNode.RefTextNode = TextEdit.FromRope[NIL];
SetTEditDocument: ViewerClasses.SetProc = BEGIN
KillSelections: PROC [self: Viewer] = { OPEN TEditSelection;
This duplicates a procedure in TEditDocuments2Impl. Someday when you can change an interface, export it from there rather than copying it. -- Bill
IF pSel # NIL AND pSel.viewer = self THEN MakeSelection[selection: primary];
IF sSel # NIL AND sSel.viewer = self THEN MakeSelection[selection: secondary];
IF fSel # NIL AND fSel.viewer = self THEN MakeSelection[selection: feedback] };
tdd: TEditDocumentData ← NARROW[self.data];
IF self.destroyed OR tdd = NIL THEN RETURN;
IF op#$SelPos AND InputFocus.GetInputFocus[].owner=self THEN InputFocus.SetInputFocus[];
IF op=NIL OR op=$TiogaContents OR op=$TiogaDocument OR op=$KeepRootStyleProps THEN BEGIN
rope: ROPE;
IF self.link#NIL THEN ERROR; -- not yet implemented
[] ← SpinAndLock[tdd, "SetTEditDocument"];
KillSelections[self];
IF op=NIL OR op=$KeepRootStyleProps THEN {
root: TextNode.RefTextNode = tdd.text;
node: TextNode.RefTextNode = TextNode.NarrowToTextNode[root];
typename: TextNode.TypeName = root.typename;
child: TextNode.RefTextNode =
IF node=NIL THEN NIL ELSE TextNode.NarrowToTextNode[node.child];
rope ← IF data=NIL THEN "" ELSE NARROW[data];
IF child#NIL AND child.last AND child.child=NIL THEN { -- reuse rather than reallocate
RemoveProps: PROC [name: ATOM, value: REF] RETURNS [BOOLEAN] = {
IF name#$DocumentLock AND name#$Viewer
AND NOT (op=$KeepRootStyleProps AND (name=$Prefix OR name=$Postfix))
THEN NodeProps.PutProp[root, name, NIL];
RETURN [FALSE] };
rootProps: TextNode.NodeProps;
Should really have a write lock on the document for this. change for Tioga2.
child^ ← nullNode^;
rootProps ← root.props; root^ ← nullNode^; root.props ← rootProps;
root.child ← child; child.next ← root;
root.last ← child.last ← TRUE; child.rope ← rope;
IF op = $KeepRootStyleProps THEN root.typename ← typename;
[] ← NodeProps.MapProps[root, RemoveProps, FALSE, FALSE] }
ELSE {
prefix, postfix: REF;
IF op=$KeepRootStyleProps THEN {
prefix ← NodeProps.GetProp[root, $Prefix];
postfix ← NodeProps.GetProp[root, $Postfix] };
TEditInput.FreeTree[root];
tdd.text ← TextNode.NarrowToTextNode[
TextEdit.DocFromNode[TextEdit.FromRope[rope]]];
IF op=$KeepRootStyleProps THEN {
IF prefix#NIL THEN NodeProps.PutProp[tdd.text, $Prefix, prefix];
IF postfix#NIL THEN NodeProps.PutProp[tdd.text, $Postfix, postfix];
tdd.text.typename ← typename };
}
}
ELSE IF op=$TiogaContents THEN {
info: ViewerTools.TiogaContents ← NARROW[data];
rope ← Rope.Concat[info.contents, info.formatting];
TEditInput.FreeTree[tdd.text];
tdd.text ← TextNode.NarrowToTextNode[PutGet.FromRope[rope]] }
ELSE IF op=$TiogaDocument THEN {
root: TextNode.RefTextNode ← NARROW[data];
TEditInput.FreeTree[tdd.text];
tdd.text ← root }
ELSE ERROR;
tdd.lineTable.lastLine ← 0;
tdd.lineTable[0].pos ← [TextNode.NarrowToTextNode[TextNode.FirstChild[tdd.text]], 0];
RecordViewerForRoot[self,tdd.text];
self.newVersion ← FALSE; -- clear edited bit
IF finalise THEN ViewerOps.PaintViewer[self, all, TRUE, TEditTouchup.fullUpdate];
Unlock[tdd];
END
ELSE IF op = $SelPos THEN BEGIN OPEN TEditSelection;
IF ~self.iconic THEN BEGIN  -- no iconic selections please
tSel: TEditDocument.Selection ← TEditSelection.Alloc[];
sel: ViewerTools.SelPos ← NARROW[data];
start, length: INT;
[] ← TEditLocks.LockDocAndTdd[tdd, "SetTEditDocument", read];
IF sel=NIL THEN BEGIN
IF tdd.tsInfo#NIL THEN { -- for typescripts, NIL => caret at end
start ← TextNode.LocNumber[ -- includes 1 for root, so must subtract 1
at: TextNode.LastLocWithin[tdd.text], skipCommentNodes: TRUE];
length ← 0 }
ELSE { -- for other text viewers, NIL => entire contents
start ← 0;
length ← TextNode.LocNumber[ -- includes 1 for root, so must subtract 1
at: TextNode.LastLocWithin[tdd.text], skipCommentNodes: TRUE]-1};
END
ELSE {
tiogaFile: BOOL ← (NodeProps.GetProp[tdd.text, $FromTiogaFile] = $Yes);
start ← sel.start;
IF ~tiogaFile THEN start ← start+1; -- hack to compensate for leading CR
length ← sel.length };
tSel.start.pos ← TextNode.LocWithin[n: tdd.text, count: start, skipCommentNodes: TRUE];
tSel.end.pos ← TextNode.LocRelative[
location: tSel.start.pos, count: MAX[length-1, 0], skipCommentNodes: TRUE];
TEditLocks.UnlockDocAndTdd[tdd];
tSel.granularity ← IF length=0 THEN point ELSE char;
tSel.viewer ← self;
tSel.data ← tdd;
tSel.insertion ← IF length=0 THEN before ELSE after;
tSel.pendingDelete ← TRUE;
tSel.looks ← TextLooks.noLooks;
TEditSelection.MakeSelection[new: tSel];
TEditSelection.Free[tSel];
TEditRefresh.ScrollToEndOfSel[self, FALSE];
END;
END
ELSE IF op = $ReadOnly THEN BEGIN
self.tipTable ← readonlyTIP;
tdd.readOnly ← TRUE;
END
ELSE IF op = $ReadWrite THEN BEGIN
self.tipTable ← tiogaTIP;
tdd.readOnly ← FALSE;
END
ELSE ERROR;
END;
GetTEditDocument: ViewerClasses.GetProc = BEGIN
tdd: TEditDocumentData = NARROW[self.data];
IF op=NIL THEN {
node: TextNode.RefTextNode = TextNode.NarrowToTextNode[tdd.text.child];
IF node#NIL AND node.last AND node.child=NIL THEN -- simple case of only one node
RETURN [node.rope];
RETURN [PutGet.WriteRopePlain[tdd.text]] };
IF op = $TiogaContents THEN {
contents, formatting: ROPE;
dataLen, count: INT;
[dataLen, count, contents] ← PutGet.ToRope[tdd.text];
formatting ← Rope.Substr[base: contents, start: dataLen, len: count-dataLen];
contents ← Rope.Substr[base: contents, start: 0, len: dataLen];
RETURN [TextNode.pZone.NEW[ViewerTools.TiogaContentsRec ← [contents, formatting]]] };
IF op = $SelChars THEN BEGIN OPEN TEditSelection;
rope: ROPE; -- hack to fix compiler problem
DoSelChars: PROC [root: TextEdit.Ref, tSel: Selection] = {
SelConcat: PROC [node: TextNode.RefTextNode, start, len: TextNode.Offset]
RETURNS [stop: BOOLEAN] = {
rope ← IF rope = NIL THEN Rope.Substr[node.rope, start, len]
ELSE Rope.Cat[rope, "\n", Rope.Substr[node.rope, start, len]];
RETURN [FALSE] };
IF tSel.viewer # NIL AND tSel.granularity # point THEN
EditSpanSupport.Apply[[tSel.start.pos, tSel.end.pos], SelConcat];
IF rope=NIL THEN rope ← "" };
TEditInputOps.CallWithLocks[DoSelChars, read];
RETURN[rope];
END
ELSE IF op = $SelPos THEN BEGIN OPEN TEditSelection;
sel: ViewerTools.SelPos ← TextNode.pZone.NEW[ViewerTools.SelPosRec];
DoSelPos: PROC [root: TextEdit.Ref, tSel: Selection] = {
sel.start ← TextNode.LocNumber[at: tSel.start.pos, skipCommentNodes: TRUE];
sel.length ← TextNode.LocOffset[
loc1: tSel.start.pos, loc2: tSel.end.pos, skipCommentNodes: TRUE] };
TEditInputOps.CallWithLocks[DoSelPos, read];
RETURN[sel];
END
ELSE ERROR;
END;
DestroyTEditDocument: ViewerClasses.DestroyProc = {
ForgetViewer[self]; -- this also fixes up the Root => Viewer mapping if necessary
IF self.link # NIL THEN RETURN; -- linked, so not finished with document yet
TRUSTED {Process.Detach[FORK CleanupAfterDestroy[self, self.name, NARROW[self.data]]] }};
Need to pass self.name as variable because it will be set to NIL
CleanupAfterDestroy: PROC [self: Viewer, file: ROPE, tdd: TEditDocument.TEditDocumentData] = {
root: TextNode.Ref;
IF tdd = NIL THEN RETURN; -- anybody remember why this is here (SM)?
[] ← SpinAndLock[tdd, "DestroyTEditDocument"];
root ← tdd.text;
IF self.newVersion AND ~self.saveInProgress AND file # NIL THEN
RecordUnsavedDocument[file, root]
ELSE TEditInput.FreeTree[root];
Unlock[tdd] };
unlocked: CONDITION;
SpinAndLock: PUBLIC ENTRY PROC [tdd: TEditDocumentData, who: ROPE,
interrupt, defer: BOOLFALSE]
RETURNS [ok: BOOL] = TRUSTED BEGIN
ENABLE UNWIND => NULL;
myProcess: PROCESS = LOOPHOLE[Process.GetCurrent[]];
IF myProcess#tdd.lockProcess THEN BEGIN
IF interrupt THEN {
IF defer AND tdd.interrupt > 0 THEN RETURN [FALSE];
tdd.interrupt ← tdd.interrupt+1 };
WHILE tdd.lock>0 DO WAIT unlocked; ENDLOOP;
tdd.lockProcess ← myProcess;
tdd.who ← who; -- save the first guy who got the lock
IF interrupt THEN tdd.interrupt ← tdd.interrupt-1;
END;
tdd.lock ← tdd.lock+1;
RETURN [TRUE];
END;
Unlock: PUBLIC ENTRY PROC [tdd: TEditDocumentData] = TRUSTED {
ENABLE UNWIND => NULL;
IF tdd.lockProcess # Process.GetCurrent[] THEN ERROR;
IF (tdd.lock ← tdd.lock-1) = 0 THEN BROADCAST unlocked };
ReloadTipTable: PUBLIC PROC = BEGIN
newTIP: TIPUser.TIPTable ←
ReloadTable[tiogaTIP, "TiogaTIP", "Tioga.tip"];
IF newTIP#NIL THEN ChangeTipTables[tiogaClass.tipTable ← tiogaTIP ← newTIP, FALSE];
END;
ReloadReadonlyTipTable: PUBLIC PROC = BEGIN
newTIP: TIPUser.TIPTable ←
ReloadTable[readonlyTIP, "ReadonlyTiogaTIP", "ReadonlyTioga.tip"];
IF newTIP # NIL THEN ChangeTipTables[readonlyTIP ← newTIP, TRUE];
END;
readonlyTIP: TIPUser.TIPTable;
tiogaTIP: PUBLIC TIPUser.TIPTable;
ChangeTipTables: PROC [newTIP: TIPUser.TIPTable, readonly: BOOL] = {
changeTip: PROC [v: Viewer] RETURNS [BOOLTRUE] = {
IF v.class.flavor = $Text THEN {
tdd: TEditDocumentData ← NARROW[v.data];
IF tdd # NIL AND tdd.readOnly = readonly THEN v.tipTable ← newTIP
}
ELSE IF ViewerOps.IsClass[v, $Container] THEN {
ViewerOps.EnumerateChildren[v, changeTip];
};
};
ViewerOps.EnumerateViewers[changeTip]
};
ReloadTable: PUBLIC PROC [oldTIP: TIPUser.TIPTable, profileKey, default: ROPE]
RETURNS [newTIP: TIPUser.TIPTable] = {
ok: BOOLTRUE;
AddTable: PROC [r: ROPE] = {
bad: BOOLEANFALSE;
t: TIPUser.TIPTable;
msg: ROPE;
IF ~ok THEN RETURN;
IF Rope.Equal[r,"default",FALSE] THEN {
TEditProfile.DoList["", AddTable, default];
RETURN };
t ← TIPUser.InstantiateNewTIPTable[r !
CIFS.Error => {
bad ← TRUE;
msg ← Rope.Concat["Cannot read TIP table file: ", r];
CONTINUE};
TIPUser.InvalidTable => {
bad ← TRUE;
msg ← Rope.Concat["Error(s) saved on TIP.Errors for: ", r];
CONTINUE}];
IF bad THEN { ok ← FALSE; MessageWindow.Append[msg, TRUE]; RETURN };
IF newTIP=NIL THEN { newTIP ← t; RETURN };
FOR x: TIPUser.TIPTable ← newTIP, x.link UNTIL x=NIL DO
FOR y: TIPUser.TIPTable ← t, y.link UNTIL y=NIL DO
IF x # y THEN LOOP;
ok ← FALSE;
MessageWindow.Append[Rope.Concat["Loop in TIP table layers caused by ", r], TRUE];
RETURN;
ENDLOOP;
ENDLOOP;
FOR x: TIPUser.TIPTable ← newTIP, x.link UNTIL x.link=NIL DO
REPEAT FINISHED => BEGIN
newTIP.mouseTicks ← MIN[t.mouseTicks, newTIP.mouseTicks];
x.opaque ← FALSE;
x.link ← t;
END;
ENDLOOP;
};
TEditProfile.DoList[profileKey, AddTable, default];
IF NOT ok AND oldTIP=NIL THEN { -- try the default by itself
ok ← TRUE; TEditProfile.DoList["", AddTable, default] };
};
CheckFilesAfterRollback: PROC [when: CedarSnapshot.After] = {
This is called after Rollback to see if any viewers need to be Reset.
Check: ViewerOps.EnumProc --PROC [v: Viewer] RETURNS [BOOL ← TRUE]-- = {
tdd: TEditDocument.TEditDocumentData;
IF v.class.flavor # $Text THEN RETURN [TRUE]; -- only interested in text viewers
tdd ← NARROW[v.data];
IF tdd = NIL THEN RETURN [TRUE];
IF v.name.Equal["No Name"] THEN RETURN [TRUE];
IF NOT FileIsMoreRecent[tdd.text, v.name] THEN RETURN [TRUE];
MessageWindow.Append[Rope.Concat["Loading newer version of ", v.name], TRUE];
ViewerOps.RestoreViewer[v]; -- reload the file
RETURN [TRUE] --continue enumeration
};
IF when=checkpoint THEN RETURN; -- only do this after return from Rollback
ViewerOps.EnumerateViewers[Check]; -- Check each top level viewer
};
tiogaClass: ViewerClasses.ViewerClass ← NEW[ViewerClasses.ViewerClassRec ← [
paint: TEditImpl.PaintTEditDocument,
bltContents: top,  -- use blt to copy screen contents to top of viewer when change dimensions
notify: TEditImpl.TEditNotifyProc,
modify: TEditSelection.InputModify,
init: InitTEditDocument,
set: SetTEditDocument,
get: GetTEditDocument,
save: SaveTEditDocument,
destroy: DestroyTEditDocument,
scroll: TEditScrolling.ScrollTEditDocument,
menus: LIST[$tiogaBasicMenu, $tiogaPlacesMenu, $tiogaLevelsMenu]
-- tipTable set below
]];
ReloadTipTable[]; ReloadReadonlyTipTable[];
ViewerOps.RegisterViewerClass [$Text, tiogaClass];
TRUSTED {CedarSnapshot.Register[r: CheckFilesAfterRollback]};
[] ← Buttons.Create[info: [name: "New"], proc: NewButton, fork: FALSE];
don't fork since will change the selection; want to capture following typein
[] ← Buttons.Create[info: [name: "Open"], proc: OpenButton];
END.