-- 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: Plass, April 20, 1983 5:02 pm
DIRECTORY
Atom USING [Gensym, GetPName],
Buttons USING [ButtonProc, Create],
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],
Menus, -- USING lots
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],
TiogaMenuOps, -- exports tiogaMenu
TIPUser USING [InstantiateNewTIPTable, InvalidTable, TIPTable],
ViewerBLT USING [ChangeNumberOfLines],
ViewerOps USING [EnumerateChildren, PaintHint, PaintViewer, RegisterViewerClass, SetMenu, SetNewVersion],
ViewerClasses USING [BltRule, DestroyProc, GetProc, InitProc, SaveProc, SetProc, Viewer, ViewerClass, ViewerClassRec],
ViewerTools USING [EnableUserEdits, InhibitUserEdits, SelPos, SelPosRec, TiogaContents, TiogaContentsRec],
VirtualDesktops USING [EnumerateViewers];
TEditDocumentsImpl: CEDAR MONITOR
IMPORTS Atom, Buttons, CIFS, Convert, Directory, EditSpanSupport, File, FileIO, InputFocus, Menus, MessageWindow, NodeProps, Process, PutGet, Rope, RTFiles, System, TEditDocument, TEditDocumentPrivate, TextEdit, TextNode, TEditImpl, TEditInput, TEditInputOps, TEditLocks, TEditProfile, TEditRefresh, TEditTouchup, TEditScrolling, TEditSelection, TIPUser, ViewerBLT, ViewerOps, ViewerTools, VirtualDesktops
EXPORTS TEditImpl, TEditDocument, TEditDocumentPrivate, TiogaMenuOps
SHARES Menus, ViewerClasses =
BEGIN OPEN Menus, TEditDocument, TEditDocumentPrivate, ViewerClasses;
fatalTiogaError: PUBLIC ERROR = CODE ; -- for RETURN WITH ERROR monitor clearing punts
InitTEditDocument:
PUBLIC 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.file#
NIL
THEN tdd.text ←
TextNode.NarrowToTextNode[PutGet.FromFile[self.file
! 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.
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.file] THEN ViewerTools.InhibitUserEdits[self]
ELSE ViewerTools.EnableUserEdits[self];
Unlock[tdd];
END;
HasBrackets:
PROC [file: Rope.
ROPE]
RETURNS [
BOOL] = {
Has:
PROC [delim: Rope.
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 SaveProc =
BEGIN
tdd: TEditDocumentData ← NARROW[self.data];
dollarCap: File.Capability ← File.nullCapability;
root: TextNode.Ref;
file: Rope.Text ← Rope.Flatten[self.file]; -- makes LOOPHOLE to LONG STRING OK
dollarFile: Rope.Text ← Rope.Flatten[Rope.Concat[file, "$"]];
IF file.Length = 0
THEN
BEGIN
IF
~force
THEN
BEGIN
MessageWindow.Append["Can't SAVE -- no file name!", TRUE];
MessageWindow.Blink[];
ViewerOps.SetNewVersion[self]; -- turn the bit back on
RETURN;
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;
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];
END;
FileIsMoreRecent:
PUBLIC
PROC [root: TextNode.Ref, file: Rope.
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: SetProc =
BEGIN
KillSelections:
PROC [self: ViewerClasses.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.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 ~(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: 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.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.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: 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.file,
NARROW[self.data]]] }};
Need to pass self.file as variable because it will be set to NIL
CleanupAfterDestroy:
PROC [self: Viewer, file: Rope.
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] };
ChangeMenu:
PROC [viewer: Viewer, subMenu: MenuEntry] = {
menu: Menus.Menu ← viewer.menu;
found: BOOLEAN ← FALSE;
numLines: Menus.MenuLine = Menus.GetNumberOfLines[menu];
newLines: Menus.MenuLine ← numLines;
FOR i: Menus.MenuLine
IN [1..numLines)
DO
-- see if already showing the submenu
IF Rope.Equal[Menus.GetLine[menu,i].name, subMenu.name]
THEN {
-- yes, so remove it
FOR j: Menus.MenuLine
IN (i..numLines)
DO
Menus.SetLine[menu, j-1, Menus.GetLine[menu, j]];
ENDLOOP;
Menus.ChangeNumberOfLines[menu, newLast-1];
newLines ← newLines-1;
found ← TRUE; EXIT };
ENDLOOP;
IF ~found
THEN {
-- add it. do insertion sort to get it in the right place
GoesBefore:
PROC [m1, m2: MenuEntry]
RETURNS [
BOOL] = {
Priority:
PROC [m: MenuEntry]
RETURNS [
INTEGER] = {
higher priority means goes above in series of submenus
looks at rope for first item to identify the subMenu.
RETURN [
SELECT
TRUE
FROM
Rope.Equal[m.name, "Find"] => 1,
Rope.Equal[m.name, "FirstLevelOnly"] => 0,
ENDCASE => -1 -- unknown menu goes at bottom -- ] };
RETURN [Priority[m1] > Priority[m2]] };
newLast: Menus.MenuLine = MIN[numLines, LAST[Menus.MenuLine]];
newLines ← newLines+1;
Menus.ChangeNumberOfLines[menu, newLast+1];
FOR i: Menus.MenuLine
IN [1..numLines)
DO
IF GoesBefore[subMenu, Menus.GetLine[menu, i]]
THEN {
-- put it here
FOR j: Menus.MenuLine
DECREASING
IN (i..newLast]
DO
Menus.SetLine[menu, j, Menus.GetLine[menu, j-1]]; ENDLOOP;
Menus.SetLine[menu, i, subMenu];
found ← TRUE; EXIT };
ENDLOOP;
IF ~found THEN Menus.SetLine[menu, newLast, subMenu];
};
ViewerOps.EstablishViewerPosition[viewer, viewer.wx, viewer.wy, viewer.ww, viewer.wh];
ViewerOps.PaintViewer[viewer, all];
ViewerBLT.ChangeNumberOfLines[viewer, newLines];
};
LevelMenu: PUBLIC Menus.MenuProc = { ChangeMenu[NARROW[parent], levelMenu] };
FindMenu: PUBLIC Menus.MenuProc = { ChangeMenu[NARROW[parent], findMenu] };
unlocked: CONDITION;
SpinAndLock:
PUBLIC
ENTRY
PROC [tdd: TEditDocumentData, who: Rope.
ROPE,
interrupt, defer: BOOL ← FALSE]
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", "/Indigo/Tioga/TEdit/Tioga.tip"];
IF newTIP#NIL THEN ChangeTipTables[tiogaClass.tipTable ← tiogaTIP ← newTIP, FALSE];
END;
ReloadReadonlyTipTable:
PUBLIC
PROC =
BEGIN
newTIP: TIPUser.TIPTable ←
ReloadTable[readonlyTIP, "ReadonlyTiogaTIP", "/Indigo/Tioga/TEdit/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 [
BOOL ←
TRUE] = {
SELECT v.class.flavor
FROM
$Text => {
tdd: TEditDocumentData ← NARROW[v.data];
IF tdd.readOnly=readonly THEN v.tipTable ← newTIP };
$Container => ViewerOps.EnumerateChildren[v, changeTip];
ENDCASE };
VirtualDesktops.EnumerateViewers[changeTip] };
ReloadTable:
PUBLIC
PROC [oldTIP: TIPUser.TIPTable, profileKey, default: Rope.
ROPE]
RETURNS [newTIP: TIPUser.TIPTable] = {
ok: BOOL ← TRUE;
AddTable:
PROC [r: Rope.
ROPE] = {
bad: BOOLEAN ← FALSE;
t: TIPUser.TIPTable;
msg: Rope.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 ~ok
AND oldTIP=
NIL
THEN {
-- try the default by itself
ok ← TRUE; TEditProfile.DoList["", AddTable, default] };
};
tiogaClass: ViewerClass ←
NEW[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
-- tipTable set below
]];
tiogaMenu:
PUBLIC Menus.Menu ← Menus.CreateMenu[];
findMenu, levelMenu, lineMenu: PUBLIC MenuEntry;
--preSave: REF Menus.ClickProc = NEW[Menus.ClickProc ← PreSave];
preReset: REF Menus.ClickProc = NEW[Menus.ClickProc ← PreReset];
--preClear: REF Menus.ClickProc = NEW[Menus.ClickProc ← PreClear];
preStore: REF Menus.ClickProc = NEW[Menus.ClickProc ← PreStore];
--preLoadPrevious: REF Menus.ClickProc = NEW[Menus.ClickProc ← PreLoadPrevious];
--preGet: REF Menus.ClickProc = NEW[Menus.ClickProc ← PreGet];
--preGetImpl: REF Menus.ClickProc = NEW[Menus.ClickProc ← PreGetImpl];
-- first row
Menus.AppendMenuEntry[menu: tiogaMenu, entry: Menus.CreateEntry[name: "Clear", proc: Clear
--, guarded: TRUE, documentation: preClear--]];
Menus.AppendMenuEntry[menu: tiogaMenu,
entry: Menus.CreateEntry[name: "Reset", proc: Reset, guarded: TRUE, documentation: preReset]];
Menus.AppendMenuEntry[menu: tiogaMenu, entry: Menus.CreateEntry[name: "Get", proc: Get
--, guarded: TRUE, documentation: preGet--]];
Menus.AppendMenuEntry[menu: tiogaMenu,
entry: Menus.CreateEntry[name: "GetImpl", proc: GetImpl
--,guarded: TRUE, documentation: preGetImpl--]];
Menus.AppendMenuEntry[menu: tiogaMenu,
entry: Menus.CreateEntry[name: "PrevFile", proc: PreviousFile
--,guarded: TRUE, documentation: preLoadPrevious--]];
Menus.AppendMenuEntry[menu: tiogaMenu,
entry: Menus.CreateEntry[name: "Store", proc: Store, guarded: TRUE, documentation: preStore]];
Menus.AppendMenuEntry[menu: tiogaMenu, entry: Menus.CreateEntry[name: "Save", proc: Save
--, guarded: TRUE, documentation: preSave--]];
Menus.AppendMenuEntry[tiogaMenu, Menus.CreateEntry["Time", Time]];
Menus.AppendMenuEntry[tiogaMenu, Menus.CreateEntry["Split", Split]];
Menus.AppendMenuEntry[tiogaMenu, Menus.CreateEntry["Places", FindMenu]];
Menus.AppendMenuEntry[tiogaMenu, Menus.CreateEntry["Levels", LevelMenu]];
-- build the places menu
Menus.InsertMenuEntry[tiogaMenu, Menus.CreateEntry["Reselect", Reselect], 1];
Menus.InsertMenuEntry[tiogaMenu, Menus.CreateEntry["PrevPlace", JumpToPrevious], 1];
Menus.InsertMenuEntry[tiogaMenu, Menus.CreateEntry["Normalize", Normalize], 1];
Menus.InsertMenuEntry[tiogaMenu, Menus.CreateEntry["Position", Position], 1];
Menus.InsertMenuEntry[tiogaMenu, Menus.CreateEntry["Def", FindDef], 1];
Menus.InsertMenuEntry[tiogaMenu, Menus.CreateEntry["Word", FindWord], 1];
Menus.InsertMenuEntry[tiogaMenu, Menus.CreateEntry["Find", Find], 1];
findMenu ← Menus.GetLine[tiogaMenu, 1];
Menus.SetLine[tiogaMenu, 1, NIL];
-- build the levels menu
Menus.InsertMenuEntry[tiogaMenu, Menus.CreateEntry["AllLevels", AllLevels], 1];
Menus.InsertMenuEntry[tiogaMenu, Menus.CreateEntry["FewerLevels", FewerLevels], 1];
Menus.InsertMenuEntry[tiogaMenu, Menus.CreateEntry["MoreLevels", MoreLevels], 1];
Menus.InsertMenuEntry[tiogaMenu, Menus.CreateEntry["FirstLevelOnly", FirstLevelOnly], 1];
levelMenu ← Menus.GetLine[tiogaMenu, 1];
Menus.SetLine[tiogaMenu, 1, NIL];
----
Menus.ChangeNumberOfLines[tiogaMenu, 1]; -- only show the top level to start
ReloadTipTable[]; ReloadReadonlyTipTable[];
ViewerOps.RegisterViewerClass [$Text, tiogaClass];
[] ← 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.
PreSave:
PUBLIC Menus.MenuProc = {
viewer: ViewerClasses.Viewer = NARROW[parent];
tdd: TEditDocumentData ← NARROW[viewer.data];
root: TextNode.Ref;
file: Rope.ROPE = viewer.name;
IF file=
NIL
THEN
BEGIN
MessageWindow.Append["Can't SAVE -- no file name!", TRUE];
MessageWindow.Blink[];
ViewerOps.SetNewVersion[viewer]; -- turn the bit back on
RETURN;
END;
IF tdd.readOnly
THEN
BEGIN
MessageWindow.Append["Can't SAVE -- read only document!", TRUE];
MessageWindow.Blink[];
RETURN;
END;
[] ← SpinAndLock[tdd, "PreSave"]; -- may have other operation in progress
root ← tdd.text;
Unlock[tdd];
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[] }
ELSE MessageWindow.Append[Rope.Concat["Confirm save of ", file], TRUE];
};