<> <> DIRECTORY EditGroup, EditSpan, EditSpanSupport, EditNotify, Rope, UndoEvent, TreeSlice, TiogaLooks, TiogaNode, TiogaNodeOps, TiogaTreeOps; EditGroupImpl: CEDAR PROGRAM IMPORTS EditSpan, EditSpanSupport, TiogaNodeOps, TiogaTreeOps, UndoEvent, EditNotify EXPORTS EditGroup SHARES TiogaNode = BEGIN OPEN EditGroup; Change: TYPE = EditNotify.Change; CannotDoEdit: PUBLIC ERROR = CODE; DeleteGroup: PUBLIC PROC [ root: RefBranchNode, start, end: Ref, saveForPaste: BOOLEAN _ TRUE, event: Event _ NIL] = { MoveGroup[NIL, root, NIL, FALSE, start, end, event]; -- move to limbo IF saveForPaste THEN EditSpan.RecordGroupForPaste[start, end, event] }; <<>> MakeCopyOfGroup: PROC [start, end: Ref] RETURNS [copyRoot: RefBranchNode, copyStart, copyEnd: Ref] = { span: TreeSpan; count: NAT _ 0; IF start=NIL OR end=NIL THEN RETURN; FOR n: Ref _ start, TiogaTreeOps.Next[n] UNTIL n=end DO count _ count+1; ENDLOOP; span.start _ [start, TiogaNode.NodeItself]; span.end _ [TiogaTreeOps.LastWithin[end], TiogaNode.NodeItself]; span _ EditSpanSupport.CopySpan[span]; copyStart _ copyEnd _ span.start.node; THROUGH [0..count) DO copyEnd _ TiogaTreeOps.Next[copyEnd]; ENDLOOP; copyRoot _ TiogaTreeOps.Root[copyStart]; copyRoot.deleted _ TRUE }; ReplaceGroup: PUBLIC PROC [ destRoot, sourceRoot: RefBranchNode, destStart, destEnd: Ref, sourceStart, sourceEnd: Ref, saveForPaste: BOOLEAN _ TRUE, event: Event _ NIL] = { <> <> IF ~IsGroup[destStart, destEnd] THEN ERROR CannotDoEdit; IF ~IsGroup[sourceStart, sourceEnd] THEN ERROR CannotDoEdit; [sourceRoot, sourceStart, sourceEnd] _ MakeCopyOfGroup[sourceStart, sourceEnd]; [] _ TransposeGroups[sourceRoot, destRoot, sourceStart, sourceEnd, destStart, destEnd, event]; IF saveForPaste THEN EditSpan.RecordGroupForPaste[destStart, destEnd, event] }; <<>> CopyGroup: PUBLIC PROC [ destRoot, sourceRoot: RefBranchNode, dest: Ref, contents: BOOL, sourceStart, sourceEnd: Ref, event: Event _ NIL] = { <> <> IF ~IsGroup[sourceStart, sourceEnd] THEN ERROR CannotDoEdit; [sourceRoot, sourceStart, sourceEnd] _ MakeCopyOfGroup[sourceStart, sourceEnd]; MoveGroup[destRoot, sourceRoot, dest, contents, sourceStart, sourceEnd, event] }; MoveGroup: PUBLIC PROC [ destRoot, sourceRoot: RefBranchNode, dest: Ref, contents: BOOL, sourceStart, sourceEnd: Ref, event: Event _ NIL] = { <> <> sourceParent, beforeSource, afterSource: Ref; notify: REF MovingGroup Change; IF ~IsGroup[sourceStart, sourceEnd] THEN ERROR CannotDoEdit; sourceParent _ TiogaTreeOps.Parent[sourceEnd]; IF (beforeSource _ TiogaTreeOps.Previous[sourceStart, sourceParent])=NIL THEN beforeSource _ sourceParent; afterSource _ TiogaTreeOps.Next[sourceEnd]; IF contents THEN { IF TiogaTreeOps.Contents[dest]=sourceStart THEN RETURN } -- already in position ELSE IF dest=beforeSource THEN RETURN; -- already in position IF dest # NIL THEN { -- check for dest inside source IF IsEmbeddedInside[dest, sourceStart, sourceEnd] THEN ERROR CannotDoEdit; IF IsInSiblings[dest, sourceStart, sourceEnd] THEN ERROR CannotDoEdit }; IF ~contents AND (TiogaNodeOps.IsBranch[dest]#TiogaNodeOps.IsBranch[sourceStart]) THEN ERROR CannotDoEdit; -- cannot mix branch and nonbranch IF contents AND TiogaNodeOps.IsBranch[dest] AND TiogaNodeOps.IsBasic[sourceStart] THEN ERROR CannotDoEdit; IF contents AND (TiogaNodeOps.IsBasic[dest] OR TiogaNodeOps.IsText[dest]) THEN ERROR CannotDoEdit; IF dest=NIL THEN { -- moving to limbo dest _ TiogaNodeOps.NewListNode[TiogaNode.invalidItemClassID]; dest.deleted _ contents _ TRUE }; notify _ NEW[MovingGroup Change _ [MovingGroup[ destRoot,sourceRoot,dest,sourceStart,sourceEnd,beforeSource,(beforeSource=sourceParent)]]]; IF beforeSource=NIL THEN { notify.from _ sourceParent; notify.contents _ TRUE }; EditNotify.Notify[notify, before]; <> IF beforeSource=sourceParent THEN { -- moving the initial part of the contents/children list WITH sourceParent SELECT FROM br: RefBranchNode => { IF sourceStart=br.child THEN -- moving children rather than contents IF sourceEnd.last THEN br.child _ NIL -- moving all the children ELSE { -- leaving some of the children nxt: RefBranchNode = TiogaNodeOps.NarrowToBranchNode[sourceEnd.next]; IF nxt=NIL THEN ERROR; br.child _ nxt } ELSE IF sourceStart=br.contents THEN -- moving contents rather than children IF sourceEnd.last THEN br.contents _ NIL -- moving all the contents ELSE { -- leaving some of the contents nxt: TiogaNode.RefItemNode = TiogaNodeOps.NarrowToItemNode[sourceEnd.next]; IF nxt=NIL THEN ERROR; br.contents _ nxt } ELSE ERROR }; bx: TiogaNode.RefBoxNode => bx.contents _ afterSource; ls: TiogaNode.RefListNode => ls.contents _ afterSource; ENDCASE => ERROR } ELSE { -- not moving the initial part of the contents list beforeSource.next _ sourceEnd.next; beforeSource.last _ sourceEnd.last }; <> IF contents THEN { -- it moves to front of contents/children of dest WITH dest SELECT FROM br: RefBranchNode => { WITH sourceStart SELECT FROM br1: RefBranchNode => { -- insert as children rather than contents IF br.child=NIL THEN { sourceEnd.next _ br; sourceEnd.last _ TRUE } ELSE { sourceEnd.next _ br.child; sourceEnd.last _ FALSE }; br.child _ br1 }; itm: TiogaNode.RefItemNode => { -- insert as contents rather than as children IF br.contents=NIL THEN { sourceEnd.next _ br; sourceEnd.last _ TRUE } ELSE { sourceEnd.next _ br.contents; sourceEnd.last _ FALSE }; br.contents _ itm }; ENDCASE => ERROR }; bx: TiogaNode.RefBoxNode => { IF bx.contents=NIL THEN { sourceEnd.next _ bx; sourceEnd.last _ TRUE } ELSE { sourceEnd.next _ bx.contents; sourceEnd.last _ FALSE }; bx.contents _ sourceStart }; ls: TiogaNode.RefListNode => { IF ls.contents=NIL THEN { sourceEnd.next _ ls; sourceEnd.last _ TRUE } ELSE { sourceEnd.next _ ls.contents; sourceEnd.last _ FALSE }; ls.contents _ sourceStart }; ENDCASE => ERROR } ELSE { -- moving to after dest as sibling sourceEnd.next _ dest.next; sourceEnd.last _ dest.last; dest.next _ sourceStart; dest.last _ FALSE }; EditNotify.Notify[notify, after]; UndoEvent.Note[event, UndoMoveGroup, notify]; }; <<>> UndoMoveGroup: PROC [undoRef: REF, currentEvent: Event] = TRUSTED { saved: REF Change _ NARROW[undoRef]; WITH x:saved SELECT FROM MovingGroup => { [] _ MoveGroup[x.sourceRoot, x.destRoot, x.from, x.contents, x.sourceStart, x.sourceEnd, currentEvent] }; ENDCASE => ERROR }; MoveGroupOnto: PUBLIC PROC [ destRoot, sourceRoot: RefBranchNode, destStart, destEnd: Ref, sourceStart, sourceEnd: Ref, saveForPaste: BOOLEAN _ TRUE, event: Event _ NIL] = { <> <> overlap: BOOLEAN; headStart, headEnd, tailStart, tailEnd: Ref; startOrder, endOrder: TreeSlice.Order; IF ~IsGroup[sourceStart, sourceEnd] THEN ERROR CannotDoEdit; IF ~IsGroup[destStart, destEnd] THEN ERROR CannotDoEdit; IF IsEmbeddedInside[destEnd, sourceStart, sourceEnd] THEN ERROR CannotDoEdit; [overlap, headStart, headEnd, tailStart, tailEnd, startOrder, endOrder] _ GroupOrder[destStart, destEnd, sourceStart, sourceEnd]; IF overlap THEN { -- groups overlap. modify dest so it doesn't overlap source IF headStart = NIL AND tailStart = NIL THEN ERROR CannotDoEdit; -- source same as dest IF headStart = NIL THEN { -- source start = dest start IF endOrder=before THEN ERROR CannotDoEdit -- dest end before source end ELSE destStart _ tailStart } ELSE IF tailStart = NIL THEN { -- source end = dest end IF startOrder=before THEN destEnd _ headEnd -- dest start before source start ELSE ERROR CannotDoEdit } -- source start before d start ELSE { -- have both head and tail IF startOrder=before AND endOrder=after THEN { DeleteGroup[destRoot, tailStart, tailEnd, FALSE, event]; destEnd _ headEnd } ELSE IF startOrder=before THEN destEnd _ headEnd ELSE IF endOrder=after THEN destStart _ tailStart ELSE ERROR CannotDoEdit }}; MoveGroup[NIL, sourceRoot, NIL, FALSE, sourceStart, sourceEnd, event]; -- move source to limbo sourceRoot _ TiogaTreeOps.Root[sourceStart]; [] _ TransposeGroups[sourceRoot, destRoot, sourceStart, sourceEnd, destStart, destEnd, event]; IF saveForPaste THEN EditSpan.RecordGroupForPaste[destStart, destEnd, event] }; <<>> TransposeGroups: PUBLIC PROC [ alphaRoot, betaRoot: RefBranchNode, alphaStart, alphaEnd: Ref, betaStart, betaEnd: Ref, event: Event _ NIL] RETURNS [newAlphaStart, newAlphaEnd, newBetaStart, newBetaEnd: Ref] = { <> <> afterAlpha, afterBeta, beforeAlpha, beforeBeta, headStart, headEnd, tailStart, tailEnd: Ref; overlap: BOOL; startOrder, endOrder: TreeSlice.Order; IF ~IsGroup[betaStart, betaEnd] THEN ERROR CannotDoEdit; IF ~IsGroup[alphaStart, alphaEnd] THEN ERROR CannotDoEdit; afterAlpha _ TiogaTreeOps.Next[alphaEnd]; afterBeta _ TiogaTreeOps.Next[betaEnd]; beforeAlpha _ TiogaTreeOps.Previous[alphaStart]; beforeBeta _ TiogaTreeOps.Previous[betaStart]; IF afterAlpha=betaStart THEN { -- alpha just before beta; Move alpha after beta MoveGroup[alphaRoot, betaRoot, betaEnd, FALSE, alphaStart, alphaEnd, event] } ELSE IF afterBeta = alphaStart THEN { -- beta just before alpha; Move beta after alpha MoveGroup[alphaRoot, betaRoot, alphaEnd, FALSE, betaStart, betaEnd, event] } ELSE IF IsEmbeddedInside[alphaEnd, betaStart, betaEnd] OR IsEmbeddedInside[betaEnd, alphaStart, alphaEnd] THEN ERROR CannotDoEdit; [overlap, headStart, headEnd, tailStart, tailEnd, startOrder, endOrder] _ GroupOrder[alphaStart, alphaEnd, betaStart, betaEnd]; IF overlap THEN { -- groups overlap IF headStart = NIL AND tailStart = NIL THEN NULL -- alpha same as beta ELSE IF headStart = NIL THEN { -- move tail to before alphastart IF beforeAlpha # NIL THEN MoveGroup[alphaRoot, betaRoot, beforeAlpha, FALSE, tailStart, tailEnd, event] ELSE MoveGroup[alphaRoot, betaRoot, TiogaTreeOps.Parent[alphaEnd], TRUE, tailStart, tailEnd, event]; IF endOrder=before THEN { -- a end before b end betaStart _ tailStart; betaEnd _ alphaEnd } ELSE { -- b end before a end alphaStart _ tailStart; alphaEnd _ betaEnd }} ELSE IF tailStart = NIL THEN { --move head to after alphaend MoveGroup[alphaRoot, betaRoot, alphaEnd, FALSE, headStart, headEnd, event]; IF startOrder=before THEN { -- a start before b start alphaStart _ betaStart; alphaEnd _ headEnd } ELSE { -- b start before a start betaStart _ alphaStart; betaEnd _ headEnd }} ELSE IF startOrder # endOrder THEN ERROR CannotDoEdit -- one contained in the other ELSE { -- transpose head and tail [] _ TransposeGroups[alphaRoot, betaRoot, headStart, headEnd, tailStart, tailEnd, event]; IF startOrder=before THEN { -- a start before b start alphaStart _ betaStart; alphaEnd _ headEnd; betaStart _ tailStart; betaEnd _ alphaEnd } ELSE { -- b start before a start betaStart _ alphaStart; betaEnd _ headEnd; alphaStart _ tailStart; alphaEnd _ betaEnd }}} ELSE { -- do transpose as two moves child: BOOL _ FALSE; alphaDest: Ref; IF beforeAlpha # NIL THEN alphaDest _ beforeAlpha ELSE { child _ TRUE; alphaDest _ TiogaTreeOps.Parent[alphaEnd] }; MoveGroup[betaRoot, alphaRoot, betaEnd, FALSE, alphaStart, alphaEnd, event]; <> MoveGroup[alphaRoot, betaRoot, alphaDest, child, betaStart, betaEnd, event]; <> }; RETURN [alphaStart, alphaEnd, betaStart, betaEnd] }; <<>> IsInSiblings: PROC [node, start, end: Ref] RETURNS [BOOL] = { FOR n: Ref _ start, TiogaTreeOps.Next[n] DO -- look for prevParent inside group siblings IF n=node THEN RETURN [TRUE]; -- node is inside group IF n=end THEN EXIT; ENDLOOP; RETURN [FALSE] }; IsGroup: PROC [start, end: Ref] RETURNS [BOOL] = INLINE { RETURN [IsInSiblings[end, start, NIL]] }; IsEmbeddedInside: PROC [node, start, end: Ref] RETURNS [BOOL] = { <> <> groupParent: Ref = TiogaTreeOps.Parent[end]; nodeParent: Ref _ TiogaTreeOps.Parent[node]; prevParent: Ref; firstParent: BOOL _ TRUE; UNTIL nodeParent=NIL DO IF nodeParent=groupParent THEN { -- prevParent is a sibling of group IF firstParent THEN EXIT; -- node is sibling of group IF IsInSiblings[prevParent, start, end] THEN RETURN [TRUE]; -- node is inside group EXIT }; prevParent _ nodeParent; nodeParent _ TiogaTreeOps.Parent[nodeParent]; ENDLOOP; RETURN [FALSE] }; GroupOrder: PROC [aStart, aEnd, bStart, bEnd: Ref] RETURNS [overlap: BOOL, headStart, headEnd, tailStart, tailEnd: Ref, startOrder, endOrder: TreeSlice.Order] = { CompareOrder: PROC [x, y: Ref] RETURNS [order: TreeSlice.Order] = { FindNext: PROC [x, y: Ref] RETURNS [BOOL] = { DO -- search siblings following x for y IF x=y THEN RETURN [TRUE]; IF x=NIL THEN RETURN [FALSE]; x _ TiogaTreeOps.Next[x]; ENDLOOP }; IF x=y THEN RETURN [same]; IF FindNext[x, y] THEN RETURN [before]; IF FindNext[y, x] THEN RETURN [after]; RETURN [disjoint] }; IF (startOrder _ CompareOrder[aStart, bStart])=disjoint THEN { overlap _ FALSE; RETURN }; IF (endOrder _ CompareOrder[aEnd, bEnd])=disjoint THEN ERROR; SELECT startOrder FROM before => { headStart _ aStart; headEnd _ TiogaTreeOps.Previous[bStart] }; same => headStart _ headEnd _ NIL; after => { headStart _ bStart; headEnd _ TiogaTreeOps.Previous[aStart] }; ENDCASE => ERROR; SELECT endOrder FROM before => { tailStart _ TiogaTreeOps.Next[aEnd]; tailEnd _ bEnd }; same => tailStart _ tailEnd _ NIL; after => { tailStart _ TiogaTreeOps.Next[bEnd]; tailEnd _ aEnd }; ENDCASE => ERROR; overlap _ TRUE }; END...