-- TiogaInputEventsImpl.mesa DIRECTORY InputFocus USING [GetInputFocus, SetInputFocus], List USING [Reverse], MessageWindow USING [Append, Blink], MonitoredQueue USING [Add, Create, MQ, Remove], Process USING [Detach, priorityBackground, SetPriority, Yield], TiogaDocument USING [TiogaDocumentData, Selection, SelectionId, SelectionRec], TiogaHistory, TiogaImpl, TiogaInput, TiogaInputOps, TiogaLocks USING [Lock, LockOrder, Unlock], TiogaSelection USING [Copy, Deselect, LockBothSelections, MakeSelection, pSel, UnlockBothSelections], TiogaBasicClass USING [BasicClass], TiogaItemClass USING [ItemClass], TiogaNode USING [BasicClassID, ItemClassID, Node, Location, Offset, Ref, RefBranchNode, RefItemNode, RefBoxNode, RefListNode, RefBasicNode, RefTextNode], TiogaNodeOps USING [FetchBasicClass, FetchItemClass], UndoEvent USING [Change, Create, Empty, Ref, Reset, SubEvent, Undo], UserProfile USING [Number], ViewerClasses USING [Viewer, ViewerRec]; TiogaInputEventsImpl: CEDAR MONITOR IMPORTS InputFocus, List, MessageWindow, MonitoredQueue, Process, TiogaInput, TiogaInputOps, TiogaLocks, TiogaSelection, TiogaNodeOps, UndoEvent, UserProfile EXPORTS TiogaInput, TiogaHistory = BEGIN OPEN TiogaInput, TiogaSelection; currentEvent: PUBLIC UndoEvent.Ref; editEvent: EditEvent; -- the current edit event eventNumber: INT _ 0; -- event number for current editEvent EditEvent: TYPE = REF EditEventRec; EditEventRec: TYPE = RECORD [ prev, next: EditEvent, -- links to adjacent events in edit history undo: UndoEvent.Ref, -- stuff to undo for this event repeatList: LIST OF REF ANY, -- the command list for this event chars: REF TEXT, -- to hold first set of input chars for this event savePSel: TiogaDocument.Selection -- primary selection when event started ]; repeatList: PUBLIC LIST OF REF ANY; charsClosed, charsUsed: PUBLIC BOOLEAN _ FALSE; chars: PUBLIC REF TEXT; -- buffer for typein CloseEventNow: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; BetweenEvents: PROC RETURNS [BOOLEAN] = INLINE { RETURN [repeatList = NIL AND UndoEvent.Empty[currentEvent]] }; subevents: UndoEvent.SubEvent; freeList: LIST OF TiogaNode.RefBranchNode; IF BetweenEvents[] THEN { TiogaSelection.Copy[source: pSel, dest: editEvent.savePSel]; RETURN }; -- already closed editEvent.repeatList _ repeatList; repeatList _ NIL; editEvent _ editEvent.next; TiogaSelection.Copy[source: pSel, dest: editEvent.savePSel]; editEvent.repeatList _ NIL; charsClosed _ charsUsed _ FALSE; chars _ editEvent.chars; currentEvent _ editEvent.undo; subevents _ currentEvent.subevents; UndoEvent.Reset[currentEvent]; eventNumber _ eventNumber+1; FOR sub: UndoEvent.SubEvent _ subevents, sub.next UNTIL sub=NIL DO Free: PROC [root: TiogaNode.RefBranchNode] = { FOR lst: LIST OF TiogaNode.RefBranchNode _ freeList, lst.rest UNTIL lst=NIL DO IF lst.first = root THEN RETURN; ENDLOOP; freeList _ CONS[root, freeList] }; IF sub.undoRef # NIL THEN WITH sub.undoRef SELECT FROM x: REF UndoEvent.Change.ChangingText => { IF x.root # NIL AND x.root.deleted THEN Free[x.root] }; x: REF UndoEvent.Change.ChangingProp => { IF x.root # NIL AND x.root.deleted THEN Free[x.root] }; x: REF UndoEvent.Change.MovingNodes => { IF x.destRoot # NIL AND x.destRoot.deleted THEN Free[x.destRoot]; IF x.sourceRoot # NIL AND x.sourceRoot.deleted THEN Free[x.sourceRoot] }; x: REF UndoEvent.Change.NodeNesting => { IF x.root # NIL AND x.root.deleted THEN Free[x.root] }; x: REF UndoEvent.Change.InsertingNode => { IF x.root # NIL AND x.root.deleted THEN Free[x.root] }; ENDCASE; ENDLOOP; FOR lst: LIST OF TiogaNode.RefBranchNode _ freeList, lst.rest UNTIL lst=NIL DO FreeTree[lst.first]; ENDLOOP; }; RecordInt: PUBLIC ENTRY PROC [i: LONG INTEGER] = { ENABLE UNWIND => NULL; IF interpreterNesting > 1 THEN RETURN; IF charsUsed THEN charsClosed _ TRUE; repeatList _ CONS[NEW[LONG INTEGER _ i], repeatList] }; RecordChar: PUBLIC ENTRY PROC [c: CHARACTER] = { ENABLE UNWIND => NULL; IF interpreterNesting > 1 THEN RETURN; IF ~charsClosed THEN { IF ~charsUsed THEN { charsUsed _ TRUE; chars.length _ 0; repeatList _ CONS[chars,repeatList] }; chars[chars.length] _ c; IF (chars.length _ chars.length+1) = chars.maxLength THEN charsClosed _ TRUE } ELSE repeatList _ CONS[NEW[CHARACTER _ c], repeatList] }; RecordRef: PUBLIC ENTRY PROC [ref: REF ANY] = { ENABLE UNWIND => NULL; IF interpreterNesting > 1 THEN RETURN; IF charsUsed THEN charsClosed _ TRUE; repeatList _ CONS[ref, repeatList] }; treeQueue: MonitoredQueue.MQ _ MonitoredQueue.Create[]; FreeTree: PUBLIC PROC [root: TiogaNode.RefBranchNode] = { IF root=NIL OR root.deleted THEN RETURN; root.deleted _ TRUE; FOR event: EditEvent _ editEvent.next, event.next UNTIL event = editEvent DO FOR sub: UndoEvent.SubEvent _ editEvent.undo.subevents, sub.next UNTIL sub=NIL DO IF sub.undoRef # NIL THEN WITH sub.undoRef SELECT FROM x: REF UndoEvent.Change.ChangingText => IF x.root = root THEN RETURN; x: REF UndoEvent.Change.ChangingProp => IF x.root = root THEN RETURN; x: REF UndoEvent.Change.MovingNodes => IF x.destRoot = root OR x.sourceRoot = root THEN RETURN; x: REF UndoEvent.Change.NodeNesting => IF x.root = root THEN RETURN; x: REF UndoEvent.Change.InsertingNode => IF x.root = root THEN RETURN; ENDCASE; ENDLOOP; ENDLOOP; MonitoredQueue.Add[root, treeQueue] }; FreeTrees: PROC = { DO -- forever tree: REF ANY _ MonitoredQueue.Remove[treeQueue]; DoFreeTree[NARROW[tree] ! ABORTED => CONTINUE]; ENDLOOP }; nodesFreed: INT _ 0; DoFreeTree: PROC [root: TiogaNode.RefBranchNode] = { OPEN TiogaNode; itemClassID: ItemClassID _ 0; itemClass: TiogaItemClass.ItemClass _ TiogaNodeOps.FetchItemClass[itemClassID]; basicClassID: BasicClassID _ 0; basicClass: TiogaBasicClass.BasicClass _ TiogaNodeOps.FetchBasicClass[basicClassID]; DestroyItem: PROC [item: RefItemNode] = INLINE { IF item.class # itemClassID THEN itemClass _ TiogaNodeOps.FetchItemClass[itemClassID _ item.class]; IF itemClass.destroy # NIL THEN itemClass.destroy[item] }; DestroyBasic: PROC [basic: RefBasicNode] = INLINE { IF basic.class # basicClassID THEN basicClass _ TiogaNodeOps.FetchBasicClass[basicClassID _ basic.class]; IF basicClass.destroy # NIL THEN basicClass.destroy[basic] }; next, node: Ref; IF root.child = NIL THEN RETURN; -- has already been freed TRUSTED {Process.SetPriority[Process.priorityBackground]}; -- no rush to finish this Process.Yield[]; [] _ TiogaLocks.Lock[root, "DoFreeTree", write]; -- must make sure no one still reading it. never unlock it. node _ root; next _ NIL; DO -- go through the tree zapping REF's WITH node SELECT FROM br: RefBranchNode => IF br.contents # NIL THEN { next _ br.contents; br.contents _ NIL } ELSE { next _ br.child; br.child _ NIL }; bx: RefBoxNode => next _ bx.contents; ls: RefListNode => next _ ls.contents; ENDCASE; IF next # NIL THEN { node _ next; next _ NIL; LOOP }; next _ node.next; node.next _ NIL; node.last _ node.deleted _ TRUE; WITH node SELECT FROM tx: RefTextNode => { DestroyItem[tx]; tx.rope _ NIL; tx.runs _ NIL }; bx: RefBoxNode => { DestroyItem[bx]; bx.data _ NIL }; ls: RefListNode => { DestroyItem[ls]; ls.data _ NIL }; bc: RefBasicNode => { DestroyBasic[bc]; bc.data _ NIL }; ENDCASE; node.props _ NIL; nodesFreed _ nodesFreed+1; IF (node _ next) = NIL THEN EXIT; ENDLOOP }; Cancel: PUBLIC CommandProc = { CloseEventNow[]; Undo[eventNumber-1]; RETURN [FALSE] }; Undo: PUBLIC PROC [eventNum: INT] = { e, first, last: EditEvent; viewer: ViewerClasses.Viewer; num, undone: INT; selectionsLocked: BOOL _ FALSE; docList, lockedList: LIST OF TiogaNode.RefBranchNode; Cleanup: PROC = { IF selectionsLocked THEN TiogaSelection.UnlockBothSelections[]; FOR list: LIST OF TiogaNode.RefBranchNode _ lockedList, list.rest UNTIL list=NIL DO TiogaLocks.Unlock[list.first]; ENDLOOP }; CloseEventNow[]; TiogaSelection.LockBothSelections["Undo"]; selectionsLocked _ TRUE; { ENABLE UNWIND => Cleanup[]; list: LIST OF TiogaNode.RefBranchNode; num _ eventNumber; first _ e _ editEvent; UNTIL (num _ num-1) < eventNum DO -- find all the documents to be changed Add: PROC [doc: TiogaNode.RefBranchNode] = { prev: LIST OF TiogaNode.RefBranchNode; IF doc=NIL THEN RETURN; FOR list: LIST OF TiogaNode.RefBranchNode _ docList, list.rest UNTIL list=NIL DO IF list.first = doc THEN RETURN; ENDLOOP; IF docList=NIL OR TiogaLocks.LockOrder[doc, docList.first] THEN { -- doc goes at start docList _ CONS[doc, docList]; RETURN }; prev _ docList; FOR list: LIST OF TiogaNode.RefBranchNode _ docList, list.rest UNTIL list=NIL DO IF TiogaLocks.LockOrder[doc, list.first] THEN { -- doc goes after prev and before this prev.rest _ CONS[doc, list.rest]; RETURN }; prev _ list; ENDLOOP; prev.rest _ CONS[doc, NIL]; -- put it at the end of the list }; sub: UndoEvent.SubEvent; IF (e _ e.prev) = first THEN EXIT; sub _ e.undo.subevents; UNTIL sub=NIL DO IF sub.undoRef#NIL THEN WITH sub.undoRef SELECT FROM x: REF UndoEvent.Change.ChangingText => Add[x.root]; x: REF UndoEvent.Change.ChangingFormat => Add[x.root]; x: REF UndoEvent.Change.ChangingProp => Add[x.root]; x: REF UndoEvent.Change.MovingNodes => { Add[x.destRoot]; Add[x.sourceRoot] }; x: REF UndoEvent.Change.NodeNesting => Add[x.root]; x: REF UndoEvent.Change.InsertingNode => Add[x.root]; x: REF UndoEvent.Change.Misc => Add[x.root]; x: REF UndoEvent.Change.Misc2 => { Add[x.root1]; Add[x.root2] }; ENDCASE; sub _ sub.next; ENDLOOP; ENDLOOP; list _ docList; WHILE list # NIL DO -- get write locks for them rest: LIST OF TiogaNode.RefBranchNode _ list.rest; [] _ TiogaLocks.Lock[list.first, "Undo", write]; list.rest _ lockedList; lockedList _ list; -- move item to list of locked documents list _ rest; ENDLOOP; viewer _ pSel.viewer; Deselect[primary]; -- get rid of the primary selection Deselect[secondary]; -- get rid of secondary selection Deselect[feedback]; -- get rid of feedback selection num _ eventNumber; undone _ 0; first _ e _ editEvent; UNTIL (num _ num-1) < eventNum DO IF (e _ e.prev) = first THEN EXIT; -- have undone all the saved events UndoEvent.Undo[e.undo, currentEvent]; last _ e; undone _ undone+1; ENDLOOP; IF CheckSelection[last.savePSel] THEN MakeSelection[selection: primary, new: last.savePSel] ELSE { -- give up the input focus if: ViewerClasses.Viewer = InputFocus.GetInputFocus[].owner; IF if=viewer THEN InputFocus.SetInputFocus[NIL] }; RecordInt[undone]; RecordRef[$Undone]; CloseEventNow[]; Cleanup[]; }}; CurrentEventNumber: PUBLIC PROC RETURNS [INT] = { RETURN [eventNumber] }; SliceSize: PUBLIC ENTRY PROC RETURNS [number: INT] = { ENABLE UNWIND => NULL; number _ 1; FOR e: EditEvent _ editEvent.next, e.next UNTIL e=editEvent DO number _ number+1; ENDLOOP }; GetEvent: PROC [number: INT] RETURNS [event: EditEvent] = { IF eventNumber < number THEN RETURN [NIL]; IF (number _ eventNumber-number) = 0 THEN RETURN [editEvent]; FOR e: EditEvent _ editEvent.prev, e.prev DO IF e = editEvent THEN EXIT; -- have gone past IF (number _ number-1) = 0 THEN RETURN [e]; ENDLOOP; RETURN [NIL] }; NewSliceSize: PUBLIC ENTRY PROC [number: INT] = { ENABLE UNWIND => NULL; delta: INT; number _ MAX[MIN[number,200],2]; -- limit to [2..200] delta _ number-SliceSize[]; SELECT delta FROM = 0 => NULL; < 0 => FOR e: EditEvent _ editEvent.next, e.next DO -- reduce size IF (delta _ delta+1) > 0 THEN { -- connect to e editEvent.next _ e; e.prev _ editEvent; RETURN }; ENDLOOP; ENDCASE => -- increase size FOR i: INT IN [0..delta) DO e: EditEvent _ CreateEvent[]; e.next _ editEvent.next; e.next.prev _ e; e.prev _ editEvent; editEvent.next _ e; ENDLOOP }; Known: PUBLIC ENTRY PROC [number: INT] RETURNS [BOOLEAN] = { ENABLE UNWIND => NULL; e: EditEvent _ GetEvent[number]; RETURN [ e # NIL AND (e.repeatList # NIL OR ~UndoEvent.Empty[e.undo]) ] }; Repeat: PUBLIC CommandProc = { list: LIST OF REF ANY; IF pSel = NIL OR pSel.granularity = point THEN { MessageWindow.Append["Make selection for repeat",TRUE]; MessageWindow.Blink[] } ELSE IF (list _ GetRepeatSequence[]) = NIL THEN { MessageWindow.Append["Nothing saved for repeat",TRUE]; MessageWindow.Blink[] } ELSE { DoRepeat: PROC [root: TiogaNode.Ref, tSel: TiogaDocument.Selection] = { InterpInput[viewer, list, FALSE] }; TiogaInputOps.CallWithLocks[DoRepeat] }; RETURN [FALSE] }; GetRepeatList: PUBLIC ENTRY PROC [number: INT] RETURNS [LIST OF REF ANY] = { ENABLE UNWIND => NULL; e: EditEvent _ GetEvent[number]; RETURN [IF e = NIL THEN NIL ELSE List.Reverse[e.repeatList]] }; GetRepeatSequence: PUBLIC PROC RETURNS [params: LIST OF REF ANY] = { num: INT; CloseEventNow[]; num _ eventNumber; WHILE Known[num _ num-1] DO -- get list to repeat IF (params _ GetRepeatList[num]) = NIL THEN LOOP; -- skip over empty entries IF params.rest # NIL AND params.rest.rest = NIL AND params.rest.first = $Undone THEN { params _ NIL; LOOP }; -- skip over Undo commands RETURN; ENDLOOP }; closeEvent: PUBLIC BOOL _ FALSE; CreateEvent: PROC RETURNS [e: EditEvent] = { e _ NEW[EditEventRec]; e.undo _ UndoEvent.Create[]; e.chars _ NEW[TEXT[64]]; e.savePSel _ NEW[TiogaDocument.SelectionRec]; }; InitEvents: PROC = { num: INT _ MAX[MIN[UserProfile.Number["EditHistory", 20],200],2]; -- force to [2..200] first: EditEvent _ CreateEvent[]; prev: EditEvent _ first; FOR i: INT IN [1..num) DO next: EditEvent _ CreateEvent[]; next.prev _ prev; prev.next _ next; prev _ next; ENDLOOP; first.prev _ prev; prev.next _ first; -- close the ring editEvent _ first; chars _ editEvent.chars; currentEvent _ editEvent.undo }; RegisterCommandAtoms: PROC = { Register[$Repeat, Repeat]; Register[$Undo,Cancel]; }; RegisterCommandAtoms; InitEvents; TRUSTED {Process.Detach[FORK FreeTrees[]]}; END. ÚLast Edited by: Maxwell, January 6, 1983 11:54 am Last Edited by: Plass, April 21, 1983 10:57 am Last Edited by: Paxton, May 23, 1983 11:21 am -- now move to the next editEvent -- check for pending deletes in undo list for event The node has now been removed from the tree and its contents have been destroyed. Not on list. Insert it in appropriate order for locking. Because of the way docList was created, can lock in order without danger of deadlock. -- this counter is incremented at the end of each event -- the size of the edit history buffer (number of events remembered) -- can change history length dynamically -- returns true if event is still remembered -- returns the atoms and other args stored for the event Êí˜JšÏc˜J™1J™.Jšœœ™-JšÏk ˜ ˜Jšœ žœ ˜0Jšœžœ ˜Jšœžœ˜$Jšœžœ˜/Jšœžœ2˜?Jšœžœ;˜NJ˜ J˜ J˜ Jšœ˜Jšœ žœ˜+JšœžœQ˜eJšœžœ˜#Jšœžœ ˜!Jšœ žœŠ˜™Jšœ žœ#˜5Jšœ žœ5˜DJšœ žœ ˜Jšœžœ˜(—J˜Jšœž ˜#J˜Jšžœ–˜J˜Jšžœ˜"J˜Jšžœžœ˜&J˜Jšœžœ˜#Jšœ˜/Jšœ žœ%˜;Jšœ žœžœ˜#šœžœžœ˜Jšœ+˜BJšœ˜4Jš œ žœžœžœžœ"˜?Jšœžœžœ2˜CJšœ"'˜IJ˜—J˜Jš œ ž œžœžœžœ˜#Jšœžœžœ˜/Jšœž œžœ˜,J˜šÏn œžœ˜$Jšžœžœžœ˜š Ÿ œžœžœžœžœ˜0Jšžœžœžœ"˜>—J˜Jšœ žœžœ˜*šžœžœ˜J˜žœžœž˜Qš žœžœžœžœ žœž˜6Jš œžœ"žœžœžœ˜EJš œžœ"žœžœžœ˜EJš œžœ!žœžœžœžœ˜_Jš œžœ!žœžœžœ˜DJš œžœ#žœžœžœ˜FJšžœ˜—Jšžœ˜—Jšžœ˜—J˜&J˜—šŸ œžœ˜šžœ ˜ Jšœžœžœ$˜1Jšœ žœ žœžœ˜/Jšžœ˜ J˜——Jšœ žœ˜šŸ œžœ%žœ ˜DJšœ˜JšœO˜OJšœ˜JšœT˜TšŸ œžœžœ˜0šžœž˜ JšœC˜C—Jšžœžœžœ˜:—šŸ œžœžœ˜3šžœž˜"JšœF˜F—Jšžœžœžœ˜=—J˜Jš žœžœžœžœ˜:Jšžœ4˜TJ˜Jšœ1;˜lJšœžœ˜šžœ$˜'šžœžœž˜šœ˜Jšžœžœžœ%žœ˜CJšžœžœ˜)—Jšœ%˜%Jšœ&˜&Jšžœ˜—Jš žœžœžœžœžœ˜5Jšœžœžœ˜CJ™Qšžœžœž˜Jšœ0žœ žœ˜EJšœ/žœ˜5Jšœ0žœ˜6Jšœ2žœ˜8Jšžœ˜—Jšœ žœ˜J˜Jšžœžœžœžœ˜!Jšžœ˜ J˜——Jšœžœ7žœžœ˜VJ˜šŸœžœžœ žœ˜%J˜J˜Jšœ žœ˜Jšœžœžœ˜Jšœžœžœ˜5J˜šŸœžœ˜Jšžœžœ'˜?š žœžœžœ1žœžœž˜SJšœžœ˜)—J˜—J˜Jšœ>žœ˜CJšœžœžœ˜Jšœžœžœ˜&J˜)šžœžœ'˜IšŸœžœ#˜,Jšœžœžœ˜&Jšžœžœžœžœ˜š žœžœžœ.žœžœž˜PJšžœžœžœžœ˜)—J™9š žœ žœžœ*žœ˜VJšœ žœ˜Jšžœ˜ —Jšœ˜š žœžœžœ.žœžœž˜Pšžœ'žœ&˜VJšœ žœ˜!Jšžœ˜ —J˜ Jšžœ˜—Jšœ žœžœ ˜Jšœžœ˜J˜——šŸœžœ žœžœ˜;Jšžœžœžœžœ˜*Jšžœ#žœžœ ˜=šžœ'ž˜,Jšžœžœžœ˜-Jšžœžœžœ˜+Jšžœ˜—Jšžœžœ˜J˜—šŸ œžœž œ žœ˜1Jš(™(Jšžœžœžœ˜Jšœžœ˜ Jšœ žœžœ˜6J˜šžœž˜Jšœžœ˜ šœžœ'žœ˜Bšžœžœ˜/Jšœ(žœ˜1—Jšžœ˜—šžœ˜šžœžœžœ žœ˜J˜J˜)J˜'Jšžœ˜ J˜————š Ÿœžœž œ žœžœžœ˜