-- TiogaSelectionImpl.mesa Edited by Paxton on June 17, 1983 1:24 pm
Last Edited by: Maxwell, January 6, 1983 11:35 am
Last Edited by: Plass, May 2, 1983 1:19 pm
DIRECTORY
Carets USING [StartCaret, StopCaret],
Graphics USING [black, Context, DrawBox, SetColor, SetPaintMode, SetStipple],
EditSpan USING [CompareNodeOrder, NodeOrder],
InputFocus USING [Focus, GetInputFocus, SetInputFocus],
Process USING [Detach],
TextEdit USING [FetchChar, Size],
TiogaLooks USING [Looks, noLooks],
TiogaNode USING [Location, NodeItself, Offset, Ref, RefBranchNode, RefTextNode, Span],
TiogaNodeOps USING [BranchChild, ForwardClipped, LastWithin, Level, NarrowToTextNode, Parent, Root, StepBackwardNode, StepForwardNode],
TiogaDocument USING [BeforeAfter, LineTable, maxClip, PunctuationPosition, SelectionGrain, SelectionId, SelectionPoint, Selection, TiogaDocumentData],
TiogaFormat USING [LineInfo],
TiogaSelection USING [Alloc, Copy, Create, Free, GetCachedLineInfo, IsDown, ForceDown, LockBothSelections, LockSel, fSel, nilSel, pSel, sSel, UnlockBothSelections, UnlockSel],
TiogaTouchup USING [LockAfterRefresh, UnlockAfterRefresh],
ViewerOps USING [AddProp, FetchProp, PaintViewer],
ViewerClasses USING [ModifyProc, Viewer];
TiogaSelectionImpl: CEDAR PROGRAM
IMPORTS Carets, EditSpan, Graphics, InputFocus, Process, TextEdit, TiogaNodeOps, TiogaSelection, TiogaTouchup, ViewerOps
EXPORTS TiogaSelection = BEGIN
OPEN TiogaDocument, TiogaSelection, TiogaTouchup;
------ Selection Display and Control ------
MakePointSelection:
PUBLIC
PROC [selection: Selection, pos: TiogaNode.Location] =
BEGIN
-- make a point selection at pos out the the current primary selection
tSel: Selection ← Alloc[];
Copy[source: selection, dest: tSel];
tSel.start.pos ← tSel.end.pos ← pos;
tSel.granularity ← point;
tSel.pendingDelete ← FALSE;
tSel.insertion ← before;
MakeSelection[selection: primary, new: tSel];
Free[tSel];
END;
ChangeSelections:
PROC [proc:
PROC [tSel: Selection], sel: Selection] = {
tSel: Selection ← Alloc[];
LockBothSelections["ChangeBothSelections"];
{
ENABLE
UNWIND => UnlockBothSelections[];
Copy[source: sel, dest: tSel]; proc[tSel] };
UnlockBothSelections[]; Free[tSel] };
PushOrExchangeSelections:
PUBLIC
PROC = {
DoPush:
PROC [tSel: Selection] = {
MakeSelection[IF sSel.viewer=NIL THEN NIL ELSE sSel, primary];
MakeSelection[IF tSel.viewer=NIL THEN NIL ELSE tSel, secondary] };
ChangeSelections[DoPush, pSel] };
MakePrimary:
PUBLIC
PROC = {
-- make secondary selection be the primary
DoMakePrimary:
PROC [tSel: Selection] = {
Deselect[secondary]; MakeSelection[tSel, primary] };
ChangeSelections[DoMakePrimary, sSel] };
MakeSecondary:
PUBLIC
PROC = {
-- make secondary selection be the primary
DoMakeSecondary:
PROC [tSel: Selection] = {
Deselect[primary]; MakeSelection[tSel, secondary] };
ChangeSelections[DoMakeSecondary, pSel] };
CancelPrimary:
PUBLIC
PROC = {
MakeSelection[
NIL, primary]
};
CancelSecondary:
PUBLIC
PROC = {
MakeSelection[
NIL, secondary]
};
CancelFeedback:
PUBLIC
PROC = {
MakeSelection[
NIL, feedback]
};
FakeSecondary:
PUBLIC
PROC [sel: Selection] = {
LockSel[secondary, "FakeSecondary"];
{
ENABLE
UNWIND => UnlockSel[secondary];
IF sSel.viewer # NIL THEN Deselect[secondary];
Copy[source: sel, dest: sSel] };
UnlockSel[secondary] };
Deselect:
PUBLIC
PROC [selection: SelectionId ← primary] = {
Take down the selection without changing the input focus.
LockSel[selection, "Deselect"];
{
ENABLE
UNWIND => UnlockSel[selection];
sel: Selection =
SELECT selection
FROM
primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR;
viewer: ViewerClasses.Viewer = sel.viewer;
op:
ATOM =
SELECT selection
FROM
primary => $TakeDownPSel,
secondary => $TakeDownSSel,
feedback => $TakeDownFSel,
ENDCASE => ERROR;
down: BOOL = IsDown[selection];
Copy[source: nilSel, dest: sel];
IF viewer#
NIL
AND ~down
THEN {
-- take the selection down from the screen
ViewerOps.PaintViewer[viewer, client, FALSE, op];
IF ~IsDown[selection]
THEN ForceDown[selection];
If the selection was not in a visible viewer, PaintViewer didn't call our paint proc, so the state didn't change. Force change directly.
}};
UnlockSel[selection] };
MakeSelection:
PUBLIC
PROC
[new: Selection ←
NIL, selection: SelectionId ← primary,
startValid, endValid: BOOLEAN ← FALSE, forkPaint: BOOL ← TRUE] = {
Changes input focus as well as the selection.
sel: Selection;
op: ATOM;
LockSel[selection, "MakeSelection"];
{
ENABLE
UNWIND => UnlockSel[selection];
SELECT selection
FROM
primary =>
BEGIN
if: ViewerClasses.Viewer = InputFocus.GetInputFocus[].owner;
sel ← pSel; op ← $ShowPSel;
IF new=NIL THEN {IF if#NIL THEN InputFocus.SetInputFocus[NIL]}
ELSE IF if#new.viewer THEN InputFocus.SetInputFocus[new.viewer];
END;
feedback => { sel ← fSel; op ← $ShowFSel };
secondary => { sel ← sSel; op ← $ShowSSel };
ENDCASE => ERROR;
IF new=NIL OR new.viewer # sel.viewer THEN Deselect[selection];
IF new#
NIL
THEN
{
IF selection#feedback
AND fSel.viewer=new.viewer
THEN Deselect[feedback];
When called to make a non-feedback selection is the viewer containing the feedback selection, automatically cancel the feedback selection.
Copy[source: new, dest: sel];
sel.start.metricsValid ← startValid;
sel.end.metricsValid ← endValid;
IF sel.granularity=point THEN sel.pendingDelete ← FALSE;
IF forkPaint
THEN {
showSel:
REF ShowSelRec =
SELECT selection
FROM
primary => showPSel, secondary => showSSel, ENDCASE => showFSel;
IF showSel.process =
NIL
THEN
TRUSTED {Process.Detach[showSel.process ←
FORK ShowSel[showSel, op ! ABORTED => CONTINUE]]} }
ELSE ViewerOps.PaintViewer[sel.viewer, client, FALSE, op] }};
UnlockSel[selection] };
ShowSelRec: TYPE = RECORD [ process: PROCESS, selection: SelectionId ];
showPSel: REF ShowSelRec ← NEW[ShowSelRec ← [NIL, primary]];
showSSel: REF ShowSelRec ← NEW[ShowSelRec ← [NIL, secondary]];
showFSel: REF ShowSelRec ← NEW[ShowSelRec ← [NIL, feedback]];
ShowSel:
PROC [my:
REF ShowSelRec, op:
ATOM] = {
selection: SelectionId ← my.selection;
sel: Selection ←
SELECT selection
FROM
primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR;
LockSel[selection, "ShowSel"];
{
ENABLE
UNWIND => UnlockSel[selection];
viewer: ViewerClasses.Viewer ← sel.viewer;
IF viewer#
NIL
THEN {
tdd: TiogaDocumentData ← NARROW[viewer.data];
IF tdd #
NIL
AND LockAfterRefresh[tdd, "ShowSel"]
THEN {
ViewerOps.PaintViewer[viewer, client, FALSE, op];
UnlockAfterRefresh[tdd] }};
my.process ← NIL };
UnlockSel[selection] };
lightGrey: CARDINAL = 0208H;
darkGrey: CARDINAL = 0A5A5H;
veryDarkGrey: CARDINAL ← 0EBEBH;
SelColor: TYPE = {black, lightGrey, darkGrey, veryDarkGrey} ← black;
SelBound: TYPE = {solid, line} ← solid;
feedbackLineWidth: CARDINAL ← 4;
feedbackLineRaise: INTEGER ← 1;
MarkSelection:
PUBLIC
PROC [dc: Graphics.Context, viewer: ViewerClasses.Viewer,
selection: Selection, id: SelectionId] = {
WITH viewer.data
SELECT
FROM
tdd: TiogaDocumentData=> {
OPEN selection;
lines: TiogaDocument.LineTable = tdd.lineTable;
vHeight: INTEGER = viewer.ch;
selBound: SelBound = IF selection.pendingDelete AND id#feedback THEN solid ELSE line;
selColor: SelColor =
IF id=primary
THEN black
ELSE
IF id=feedback
THEN veryDarkGrey
ELSE IF selection.pendingDelete THEN lightGrey ELSE darkGrey;
EffectSelect:
PROC [x, y, w, h:
INTEGER] =
BEGIN OPEN Graphics;
lineHeight: CARDINAL ← 2;
bottom: INTEGER ← vHeight-y-h;
IF id=feedback THEN { lineHeight ← feedbackLineWidth; bottom ← bottom - feedbackLineRaise };
SELECT selColor
FROM
black => SetColor[dc, black];
darkGrey => SetStipple[dc, darkGrey];
lightGrey => SetStipple[dc, lightGrey];
veryDarkGrey=> SetStipple[dc, veryDarkGrey];
ENDCASE => ERROR;
SELECT selBound
FROM
solid => DrawBox[dc, [x, bottom, x+w, vHeight-y]];
ENDCASE => DrawBox[dc, [x, bottom, x+w, bottom+lineHeight]];
END;
IF end.line<0
OR start.line=
LAST[
INTEGER]
OR
(start.line=end.line AND end.clipped) THEN RETURN; -- not visible
[] ← Graphics.SetPaintMode[dc, invert];
IF start.line = end.line
OR (start.line < 0
AND end.line = 0)
THEN {
-- one liner
x, y: INTEGER;
IF start.line < 0
OR start.clipped
THEN {
-- select from beginning of line
line: INTEGER ← MAX[0,start.line];
x ← lines[line].xOffset;
y ← lines[line].yOffset-lines[line].ascent }
ELSE { x ← start.x; y ← start.y };
IF end.clipped THEN ERROR; -- previous tests imply ~end.clipped
EffectSelect[x, y, end.x-x+end.w, end.h]
}
ELSE
IF start.line=lines.lastLine
AND end.line>lines.lastLine
THEN
-- one liner
EffectSelect[start.x, start.y, lines[start.line].width + lines[start.line].xOffset - start.x, start.h]
ELSE
BEGIN
sl: INTEGER = NormalizeLineIndex[lines, start.line];
el: INTEGER = NormalizeLineIndex[lines, end.line];
EffectSelAll:
PROC [n:
INTEGER] =
INLINE {
EffectSelect[lines[n].xOffset, lines[n].yOffset-lines[n].ascent, lines[n].width,
lines[n].ascent+lines[n].descent] };
IF sl=start.line
AND ~start.clipped
THEN
-- select end portion of sl
EffectSelect[start.x, start.y, (lines[sl].width +
lines[sl].xOffset - start.x), start.h]
ELSE EffectSelAll[sl];-- select all of sl
FOR n:
INTEGER
IN (sl..el)
DO
-- select all of the intermediate lines
EffectSelAll[n]; ENDLOOP;
IF end.clipped THEN NULL -- end.line is not actually part of the selection
ELSE
IF el=end.line
THEN
-- end.line is on the screen, so select initial part of el
EffectSelect[lines[el].xOffset, end.y, end.x - lines[el].xOffset + end.w, end.h]
ELSE EffectSelAll[el];-- end.line is off screen, so select all of el
END;
[] ← Graphics.SetPaintMode[dc, opaque];
};
ENDCASE;
};
NormalizeLineIndex:
PROC [lines: TiogaDocument.LineTable, line:
INTEGER]
RETURNS [INTEGER] = INLINE {
RETURN [MAX[0,MIN[lines.lastLine,line]]] };
ExtendSelection:
PUBLIC
PROC [dc: Graphics.Context, viewer: ViewerClasses.Viewer,
old, new: Selection, id: SelectionId, updateEnd: BOOLEAN] = BEGIN
-- when done, old is valid (it's = pSel, e.g.) and new^ = old^
TooHard:
PROC =
BEGIN
MarkSelection[dc, viewer, old, id]; -- take it down
FixupSelection[new, viewer]; -- get position info about the new selection
MarkSelection[dc, viewer, new, id]; -- put it up
Copy[source: new, dest: old];
END;
IF updateEnd
THEN
BEGIN
-- update right end
BumpLoc:
PROC [loc: TiogaNode.Location]
RETURNS [TiogaNode.Location] = {
n: TiogaNode.RefTextNode = TiogaNodeOps.NarrowToTextNode[loc.node];
where: TiogaNode.Offset ← loc.where+1;
IF where >= TextEdit.Size[n] THEN RETURN [[TiogaNodeOps.StepForwardNode[n],0]];
RETURN [[n,where]];
};
tooHard: BOOLEAN = old.end.line NOT IN [0..LAST[INTEGER]);
sameNode: BOOLEAN = (new.end.pos.node = old.end.pos.node);
IF tooHard THEN {TooHard; RETURN};
IF new.end.pos=old.end.pos THEN NULL
ELSE
IF (sameNode
AND new.end.pos.where>old.end.pos.where)
OR
(~sameNode
AND
EditSpan.CompareNodeOrder[new.end.pos.node, old.end.pos.node]=after) THEN
BEGIN -- new end after old end
new.start ← old.end;
new.start.pos ← BumpLoc[new.start.pos];
IF sameNode
AND new.start.pos.node # new.end.pos.node
THEN
new.start.pos ← new.end.pos;
This weird case can happen if old ended with final CR of node and new is in the null line displayed after the CR.
FixupSelection[new, viewer];
MarkSelection[dc, viewer, new, id];
old.end ← new.end;
END
ELSE
BEGIN
-- new end before old end
save: TiogaNode.Location = new.end.pos;
new.start ← new.end;
new.start.pos ← BumpLoc[save];
IF sameNode
AND new.start.pos.node # new.end.pos.node
THEN new.start.pos ← old.end.pos;
old ended in null line after final CR, new ends with the final CR
new.end ← old.end;
FixupSelection[new, viewer, TRUE, FALSE]; -- fix start
MarkSelection[dc, viewer, new, id];
old.end ← new.start;
old.end.pos ← save;
FixupSelection[old, viewer, FALSE, TRUE];
END;
END
ELSE
BEGIN
-- update start
DecLoc:
PROC [loc: TiogaNode.Location]
RETURNS [TiogaNode.Location] = {
n: TiogaNode.RefTextNode;
node: TiogaNode.Ref;
IF loc.where > 0 THEN RETURN [[loc.node, loc.where-1]];
n ← TiogaNodeOps.NarrowToTextNode[node ← TiogaNodeOps.StepBackwardNode[loc.node]];
IF n=NIL THEN RETURN [[node,TiogaNode.NodeItself]];
RETURN [[n,MAX[TextEdit.Size[n],1]-1]] };
tooHard: BOOLEAN = old.start.line NOT IN [0..LAST[INTEGER]);
sameNode: BOOLEAN = (new.start.pos.node = old.start.pos.node);
IF tooHard THEN {TooHard; RETURN};
IF new.start.pos=old.start.pos THEN NULL
ELSE
IF (sameNode
AND new.start.pos.where<old.start.pos.where)
OR
(~sameNode
AND
EditSpan.CompareNodeOrder[new.start.pos.node, old.start.pos.node]=before) THEN
BEGIN -- new start comes before old start
new.end.pos ← DecLoc[old.start.pos];
FixupSelection[new, viewer];
MarkSelection[dc, viewer, new, id];
old.start ← new.start;
END
ELSE
BEGIN
-- new start comes after old start
save: TiogaNode.Location = new.start.pos;
new.end.pos ← DecLoc[save];
new.start ← old.start;
FixupSelection[new, viewer, FALSE, TRUE];
MarkSelection[dc, viewer, new, id];
old.start.pos ← save;
FixupSelection[old, viewer, TRUE, FALSE];
END;
END;
Copy[source: old, dest: new];
END;
CannotFindIt: PUBLIC ERROR = CODE;
ComputeSpanLines:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, span: TiogaNode.Span]
RETURNS [start, end: INTEGER, startClipped, endClipped: BOOLEAN] = BEGIN
[start,startClipped] ← ComputePosLine[viewer, span.start];
IF span.start=span.end THEN { end ← start; endClipped ← startClipped }
ELSE IF start=LAST[INTEGER] THEN end ← LAST[INTEGER] -- scrolled off bottom
ELSE {
firstLine:
INTEGER =
IF start<=0
THEN 0
ELSE
IF startClipped THEN start-1 ELSE start;
[end,endClipped] ← ComputePosLine[viewer, span.end, firstLine] };
END;
ComputePosLine:
PUBLIC
PROC [
viewer: ViewerClasses.Viewer, pos: TiogaNode.Location, firstLine: INTEGER ← 0]
RETURNS [line: INTEGER, clipped: BOOLEAN] = BEGIN
sp: SelectionPoint ← ComputePosPoint[viewer, pos, firstLine, TRUE];
RETURN [sp.line, sp.clipped];
END;
ComputePosPoint:
PUBLIC
PROC [
viewer: ViewerClasses.Viewer, pos: TiogaNode.Location,
firstLine: INTEGER ← 0, lineOnly: BOOLEAN ← FALSE]
RETURNS [sp: SelectionPoint] = BEGIN
-- this must work correctly even if the node was filtered out for some reason
-- if pos was filtered, return line where it would have been and set clipped=TRUE
foundPos: BOOLEAN ← FALSE;
tdd: TiogaDocumentData = NARROW[viewer.data];
lines: TiogaDocument.LineTable;
lineInfo: TiogaFormat.LineInfo;
nChars: INTEGER;
IsPosPastLine:
PROC [pos: TiogaNode.Location, line:
INTEGER]
RETURNS [
BOOLEAN] = {
next: TiogaNode.Offset;
next ← lines[line].pos.where+lines[line].nChars;
IF pos.where >= next AND lines[line].end # eon THEN RETURN [TRUE]; -- it is past this line
RETURN [FALSE] };
FindPos:
PROC [pos: TiogaNode.Location]
RETURNS [found: BOOLEAN, line: INTEGER] = {
found ← FALSE;
FOR n:
INTEGER
IN [firstLine..lines.lastLine]
DO
IF lines[n].pos.node=pos.node
THEN {
IF pos.where < lines[n].pos.where THEN EXIT; -- have gone past it
found ← TRUE; line ← n }
ELSE IF found THEN EXIT; -- have gone past pos.node
ENDLOOP};
IF tdd = NIL THEN RETURN;
lines ← tdd.lineTable;
sp.pos ← pos;
IF (lines[0].pos.node=pos.node
AND
lines[0].pos.where>pos.where) THEN BEGIN
sp.y ← sp.line ← -LAST[INTEGER]; -- before beginning of text
RETURN;
END;
IF lines[lines.lastLine].pos.node=pos.node
AND
IsPosPastLine[pos,lines.lastLine] THEN BEGIN
sp.y ← sp.line ← LAST[INTEGER]; -- after end of text
RETURN;
END;
firstLine ← MIN[lines.lastLine,MAX[firstLine,0]];
[foundPos, sp.line] ← FindPos[pos]; sp.clipped ← FALSE;
IF ~foundPos
THEN
SELECT EditSpan.CompareNodeOrder[pos.node, lines[0].pos.node]
FROM
same => ERROR;
disjoint => ERROR CannotFindIt;
before => { sp.line ← sp.y ← -LAST[INTEGER]; RETURN }; -- before beginning
ENDCASE =>
-- pos.node comes after first line node
IF tdd.clipLevel = maxClip
AND tdd.commentFilter=includeComments
THEN {
-- nothing special going on, so would have found it if not after end
sp.line ← sp.y ← LAST[INTEGER]; RETURN } -- after end
ELSE {
-- must think about this harder
SELECT EditSpan.CompareNodeOrder[pos.node, lines[lines.lastLine].pos.node]
FROM
same => ERROR;
disjoint => ERROR CannotFindIt; -- may have been deleted or moved
after => {
-- it really is after the end
sp.line ← sp.y ← LAST[INTEGER]; RETURN }
ENDCASE => {
-- it got clipped
n: TiogaNode.Ref ← pos.node;
delta:
INTEGER ← TiogaNodeOps.Level[n]-tdd.clipLevel;
firstLine ← 0; -- need to let FindPos look at all the previous lines
FOR i:INTEGER IN [0..delta) DO n ← TiogaNodeOps.Parent[n]; ENDLOOP;
n ← TiogaNodeOps.ForwardClipped[n,tdd.clipLevel].nx;
[foundPos, sp.line] ← FindPos[[n,0]];
IF ~foundPos THEN ERROR CannotFindIt; -- may have been restored by Undo
sp.clipped ← TRUE;
};
};
IF lineOnly OR sp.clipped THEN RETURN; -- otherwise, compute metrics
sp.y ← lines[sp.line].yOffset-lines[sp.line].ascent;
sp.h ← lines[sp.line].ascent+lines[sp.line].descent;
[lineInfo, nChars] ← GetCachedLineInfo[viewer, tdd, sp.line];
IF lineInfo[0]>=
LAST[
INTEGER]
THEN
BEGIN
-- empty line
sp.x ← lines[sp.line].xOffset;
sp.w ← 0;
END
ELSE
IF pos.where>lines[sp.line].pos.where+nChars
THEN
BEGIN
-- past this line
IF sp.line=lines.lastLine THEN sp.line ← sp.y ← LAST[INTEGER]
ELSE {sp.x ← lines[sp.line].width; sp.w ← 0};
END
ELSE
BEGIN
cx: INTEGER ← lines[sp.line].xOffset;
cw: INTEGER ← lineInfo[0];
FOR n:
INTEGER
IN [1..pos.where-lines[sp.line].pos.where]
DO
cx ← cx + cw;
IF (cw←lineInfo[n]) =
LAST[
INTEGER]
THEN
BEGIN
-- off end; selection past end of screen
IF pos.where > lines[sp.line].pos.where+n
AND
pos.where # TextEdit.Size[TiogaNodeOps.NarrowToTextNode[pos.node]]
THEN sp.line ← LAST[INTEGER];
cw ← 0;
EXIT;
END;
ENDLOOP;
sp.x ← cx;
sp.w ← cw;
END;
sp.metricsValid ← TRUE;
END;
FixupSelection:
PUBLIC
PROC [selection: Selection, viewer: ViewerClasses.Viewer,
start, end: BOOLEAN ← TRUE] = BEGIN
-- recompute the xywh fields in the selection
-- fixes selection screen info assuming start.pos and end.pos are correct
IF start THEN selection.start ← ComputePosPoint[viewer, selection.start.pos];
IF end
THEN
BEGIN
IF selection.start.pos=selection.end.pos
THEN
selection.end ← selection.start -- no need to re-compute
ELSE
IF selection.start.line=
LAST[
INTEGER]
THEN
-- scrolled off bottom
{selection.end.line ← LAST[INTEGER]; selection.end.metricsValid ← TRUE}
ELSE {
firstLine:
INTEGER =
IF selection.start.line<=0
THEN 0
ELSE IF selection.start.clipped THEN selection.start.line-1 ELSE selection.start.line;
selection.end ← ComputePosPoint[viewer, selection.end.pos, firstLine] };
END;
END;
FixupCaret:
PUBLIC
PROC [selection: Selection] =
BEGIN
OPEN selection;
-- recompute the xy fields in the caret
lines: TiogaDocument.LineTable = data.lineTable;
node: TiogaNode.RefTextNode ← TiogaNodeOps.NarrowToTextNode[end.pos.node];
size: TiogaNode.Offset ← TextEdit.Size[node];
PutCaretOnNextLine:
PROC =
BEGIN
SELECT end.line
FROM
IN [0..lines.lastLine) =>
BEGIN
nextLine: INTEGER ← end.line+1;
caretX ← lines[nextLine].xOffset;
caretY ← viewer.ch - lines[nextLine].yOffset - lines[nextLine].descent;
END;
= lines.lastLine =>
BEGIN
caretX ← lines[end.line].xOffset;
caretY ← viewer.ch - end.y - end.h - end.h;
END;
ENDCASE => ERROR;
END;
IF insertion=before
THEN
BEGIN
IF start.clipped THEN caretY ← LAST[INTEGER] -- caret not visible
--ELSE
IF start.line=lines.lastLine
AND granularity=point
AND
--start.pos.where=size
AND size>0
AND TextEdit.FetchChar[node, size-1]=15C
--THEN PutCaretOnNextLine
ELSE
BEGIN
caretX ← start.x;
caretY ←
IF start.line
NOT
IN [0..lines.lastLine]
THEN
LAST[
INTEGER]
ELSE viewer.ch - start.y - start.h;
END;
END
ELSE
BEGIN
IF end.clipped THEN caretY ← LAST[INTEGER] -- caret not visible
ELSE
IF end.pos.where
IN [0..size)
AND end.line
IN [0..lines.lastLine]
AND
TextEdit.FetchChar[node,end.pos.where]=15C THEN PutCaretOnNextLine
ELSE
BEGIN
caretX ← end.x+end.w;
caretY ←
IF end.line
NOT
IN [0..lines.lastLine]
THEN
LAST[
INTEGER]
ELSE viewer.ch - end.y - end.h;
END
END;
END;
KillSelection:
PUBLIC
PROCEDURE =
BEGIN
-- selection actually get taken down in our InputModify Proc
IF InputFocus.GetInputFocus[]#NIL THEN InputFocus.SetInputFocus[];
END;
ModifyPSel:
PROC [proc:
PROC [root: TiogaNode.RefBranchNode, tSel: Selection]] = {
tSel: Selection;
{ ENABLE
UNWIND => {
UnlockSel[primary];
IF tSel #
NIL
THEN Free[tSel] };
root: TiogaNode.RefBranchNode;
LockSel[primary, "CallWithSelLock"];
IF (root ← SelectionRoot[pSel])=NIL THEN { UnlockSel[primary]; RETURN };
tSel ← Alloc[];
TiogaSelection.Copy[source: pSel, dest: tSel];
proc[root, tSel];
MakeSelection[new: tSel] };
Free[tSel]; tSel ← NIL; UnlockSel[primary] };
SelectEverything:
PUBLIC
PROC = {
-- expand to include everything
DoSelEverything:
PROC [root: TiogaNode.RefBranchNode, tSel: Selection] = {
root ← TiogaNodeOps.Root[tSel.start.pos.node];
tSel.start.pos ← [TiogaNodeOps.BranchChild[root], 0];
tSel.end.pos.node ← TiogaNodeOps.LastWithin[root];
tSel.end.pos.where ← TextEdit.Size[TiogaNodeOps.NarrowToTextNode[tSel.end.pos.node]]-1;
tSel.granularity ← branch };
ModifyPSel[DoSelEverything] };
PendingDeleteSelection:
PUBLIC
PROC = {
DoPDel:
PROC [root: TiogaNode.RefBranchNode, tSel: Selection] = {
tSel.pendingDelete ← TRUE };
ModifyPSel[DoPDel] };
NotPendingDeleteSelection:
PUBLIC
PROC = {
DoNotPDel:
PROC [root: TiogaNode.RefBranchNode, tSel: Selection] = {
tSel.pendingDelete ← FALSE };
ModifyPSel[DoNotPDel] };
CaretBeforeSelection:
PUBLIC
PROC = {
DoCaretBeforeSelection:
PROC [root: TiogaNode.RefBranchNode, tSel: Selection] = {
tSel.insertion ← before };
ModifyPSel[DoCaretBeforeSelection] };
CaretAfterSelection:
PUBLIC
PROC = {
DoCaretAfterSelection:
PROC [root: TiogaNode.RefBranchNode, tSel: Selection] = {
IF tSel.granularity # point THEN tSel.insertion ← after };
ModifyPSel[DoCaretAfterSelection] };
------ Misc functions ------
SelectionRoot:
PUBLIC
PROC [s: Selection ← pSel]
RETURNS [root: TiogaNode.RefBranchNode] = {
tdd: TiogaDocument.TiogaDocumentData;
IF s.viewer=NIL THEN RETURN [TiogaNodeOps.Root[s.start.pos.node]];
tdd ← NARROW[s.viewer.data];
IF tdd = NIL THEN RETURN [NIL];
RETURN [tdd.text] };
InputModify:
PUBLIC ViewerClasses.ModifyProc =
BEGIN
tdd: TiogaDocumentData = NARROW[self.data];
IF tdd = NIL THEN RETURN;
SELECT change FROM
set, pop => Carets.StartCaret[self, pSel.caretX, pSel.caretY, primary];
kill =>
BEGIN
prop: TiogaDocument.Selection ←
NARROW[ViewerOps.FetchProp[self, $SelectionHistory]];
Carets.StopCaret[primary];
IF prop=NIL THEN ViewerOps.AddProp[self, $SelectionHistory, prop ← Create[]];
LockSel[primary, "InputModify"];
{
ENABLE
UNWIND => UnlockSel[primary];
Copy[source: pSel, dest: prop]; Deselect[primary] };
UnlockSel[primary];
END;
push => Carets.StopCaret[primary];
ENDCASE => ERROR;
END;
END.