EditGroupImpl.mesa; written by Bill Paxton, July 1983
edited by Bill Paxton, August 2, 1983 5:20 pm
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: BOOLEANTRUE, 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: BOOLEANTRUE, event: Event ← NIL] = {
Copies the source group after the dest group and then deletes the dest group.
Takes care of overlap between source and dest.
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 contents is true, copy the group to start of the contents list of dest.
Otherwise, copy the group to immediately after dest.
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] = {
If contents is true, move the group to start of the contents list of dest.
Otherwise, move the group to immediately after dest.
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];
First remove the source from its old location.
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 };
Now insert source in its new location.
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: BOOLEANTRUE, event: Event ← NIL] = {
Moves the source group after the dest group and then deletes the dest group.
Takes care of overlap between source and dest.
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] = {
Transposes the alpha group and the beta group.
Takes care of overlap between alpha and beta.
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: BOOLFALSE;
alphaDest: Ref;
IF beforeAlpha # NIL THEN alphaDest ← beforeAlpha
ELSE { child ← TRUE; alphaDest ← TiogaTreeOps.Parent[alphaEnd] };
MoveGroup[betaRoot, alphaRoot, betaEnd, FALSE, alphaStart, alphaEnd, event];
Move alpha after beta
MoveGroup[alphaRoot, betaRoot, alphaDest, child, betaStart, betaEnd, event];
Move beta to old location of alpha
};
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] = {
Returns true if node is nested inside group defined by start..end
Returns false for case of node being sibling of start or end, since that case of overlap is ok.
groupParent: Ref = TiogaTreeOps.Parent[end];
nodeParent: Ref ← TiogaTreeOps.Parent[node];
prevParent: Ref;
firstParent: BOOLTRUE;
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...