-- file: TOCSelection.Mesa
-- last edited by Brotz, December 29, 1981 5:16 PM
-- last edited by Taft, May 21, 1983 3:06 PM

DIRECTORY
dsD: FROM "DisplayDefs" USING [ChangeCursor, GetCursor, ScreenXCoord, ScreenYCoord],
Editor USING [ShiftKey],
exD: FROM "ExceptionDefs" USING [SysBug],
inD: FROM "InteractorDefs" USING [AcceptKeyboardInput, Consider, cursorX, cursorY,
Deconsider, IdleLoop, leftMargin, lineBarLeftX, MapYToTOCIndex, mouseBitsLoc,
SavedMouseButton, ScreenXCoord, ScreenYCoord, TOCTextNbrPtr, UpdateTOCThumbLine],
Inline USING [COPY],
intCommon USING [source],
Storage USING [Free, Node],
tsD: FROM "TOCSelectionDefs" USING [],
vmD: FROM "VirtualMgrDefs" USING [GetTOCFixedPart, TOCHandle, TOCIndex, TOCRunArray,
TOCRunRange, TOCFixedPart, TOCFixedPartPtr, UnlockTOC, WaitForLock];

TOCSelection: PROGRAM
IMPORTS dsD, Editor, exD, inD, Inline, intC: intCommon, Storage, vmD
EXPORTS tsD =

BEGIN
OPEN vmD;

runIncrement: CARDINAL = 10;


AddRange: PUBLIC PROCEDURE
[toc: TOCHandle, key: CARDINAL, low, high: TOCIndex] =
-- Includes the new range [low .. high) in the toc selection.
BEGIN
index: CARDINAL;
IF key = 0 OR key # toc.key THEN exD.SysBug[];
IF toc.runs = 0 THEN {toc.runArray[0] ← [low, high]; toc.runs ← 1}
ELSE BEGIN
index ← FindRunArrayIndex[toc, low, high];
SELECT TRUE FROM
(toc.runArray[index].high < low) =>
{MakeRoomAt[toc, index + 1]; toc.runArray[index + 1] ← [low, high]};
(toc.runArray[index].high = low) =>
BEGIN
toc.runArray[index].high ← high;
IF index + 1 < toc.runs AND toc.runArray[index + 1].low = high THEN
BEGIN
toc.runArray[index].high ← toc.runArray[index + 1].high;
ShiftDownTo[toc, index + 1];
END;
END;
(toc.runArray[index].low = high) =>
BEGIN
toc.runArray[index].low ← low;
IF index > 0 AND toc.runArray[index - 1].high = low THEN
BEGIN
toc.runArray[index - 1].high ← toc.runArray[index].high;
ShiftDownTo[toc, index];
END;
END;
(toc.runArray[index].low > high) =>
{MakeRoomAt[toc, index]; toc.runArray[index] ← [low, high]};
ENDCASE => ERROR;
END;
END; -- of AddRange --


RemoveRange: PUBLIC
PROCEDURE [toc: TOCHandle, key: CARDINAL, low, high: TOCIndex] =
-- Removes the range [low .. high) from the toc selection.
BEGIN
index: CARDINAL;
runHigh, runLow: vmD.TOCIndex;
IF key = 0 OR key # toc.key OR toc.runs = 0 THEN exD.SysBug[];
[runLow, runHigh] ← toc.runArray[index ← FindRunArrayIndex[toc, low, high]];
SELECT TRUE FROM
(runLow = low AND runHigh = high) => ShiftDownTo[toc, index];
(runLow = low) => toc.runArray[index].low ← high;
(runHigh = high) => toc.runArray[index].high ← low;
(runLow < low AND runHigh > high) =>
BEGIN
MakeRoomAt[toc, index];
toc.runArray[index].high ← low;
toc.runArray[index + 1].low ← high;
END;
ENDCASE => ERROR;
END; -- of RemoveRange --


FindRunArrayIndex: PROCEDURE [toc: TOCHandle, low, high: vmD.TOCIndex]
RETURNS [index: CARDINAL] =
-- If [low .. high) is contained in or adjacent to any run range in runArray, retruns the
-- index of one such range in runArray. Otherwise, returns the index of a run range
-- either immediately preceeding or immediately following [low .. high). If no runs exist,
-- returns 0.
BEGIN
lowIndex, highIndex, halfwayIndex: CARDINAL;
IF toc.runs <= 1 THEN RETURN[0];
lowIndex ← 0;
highIndex ← toc.runs - 1;
DO
IF toc.runArray[highIndex].low <= high THEN RETURN[highIndex];
IF toc.runArray[lowIndex].high >= low THEN RETURN[lowIndex];
IF lowIndex + 1 = highIndex THEN RETURN[lowIndex];
halfwayIndex ← (lowIndex + highIndex) / 2;
IF toc.runArray[halfwayIndex].low > high
THEN highIndex ← halfwayIndex ELSE lowIndex ← halfwayIndex;
ENDLOOP;
END; -- of FindRunArrayIndex --


MakeRoomAt: PROCEDURE [toc: TOCHandle, index: CARDINAL] =
-- Moves entries in runArray[index .. runs) up by one index.
BEGIN
IF toc.runs = toc.maxRuns THEN EnlargeRunArray[toc];
IF index < toc.runs THEN
FOR i: CARDINAL DECREASING IN [index .. toc.runs) DO
toc.runArray[i + 1] ← toc.runArray[i];
ENDLOOP;
toc.runs ← toc.runs + 1;
END; -- of MakeRoomAt --


ShiftDownTo: PROCEDURE [toc: TOCHandle, index: CARDINAL] =
-- Moves entries in runArray[index + 1 .. runs) down by one index.
BEGIN
toc.runs ← toc.runs - 1;
IF index < toc.runs THEN Inline.COPY[from: @toc.runArray[index + 1],
nwords: (toc.runs - index) * SIZE[TOCRunRange], to: @toc.runArray[index]];
END; -- of ShiftDownTo --


EnlargeRunArray: PROCEDURE [toc: TOCHandle] =
-- Allocates a larger runArray and copies the contents of the old runArray into it.
BEGIN
newRunArray: TOCRunArray;
toc.maxRuns ← toc.maxRuns + runIncrement;
newRunArray ← Storage.Node[SIZE[TOCRunRange] * toc.maxRuns];
Inline.COPY[from: toc.runArray, nwords: toc.runs * SIZE[TOCRunRange], to: newRunArray];
Storage.Free[toc.runArray];
toc.runArray ← newRunArray;
END; -- of EnlargeRunArray --


ResetTOCSelection: PUBLIC PROCEDURE [toc: TOCHandle, key: CARDINAL] =
-- Resets the entire TOCSelection to be empty.
BEGIN
IF key = 0 OR key # toc.key THEN exD.SysBug[];
IF toc.maxRuns # runIncrement THEN
BEGIN
IF toc.runArray # NIL THEN Storage.Free[toc.runArray];
toc.maxRuns ← runIncrement;
toc.runArray ← Storage.Node[SIZE[TOCRunRange] * runIncrement];
END;
toc.runs ← 0;
END; -- of ResetTOCSelection --


SetTOCSelection: PUBLIC
PROCEDURE [toc: TOCHandle, key: CARDINAL, index: TOCIndex] =
-- sets the TOC selection to the range [index..index+1).
BEGIN
ResetTOCSelection[toc, key];
toc.runArray[0] ← [index, index + 1];
toc.runs ← 1;
END; -- of SetTOCSelection --


IsSelected: PUBLIC PROCEDURE [toc: TOCHandle, key: CARDINAL, index: vmD.TOCIndex]
RETURNS [BOOLEAN] =
-- Returns TRUE iff index is in the current TOCSelection.
BEGIN
low, high: TOCIndex;
IF key = 0 OR key # toc.key THEN exD.SysBug[];
IF toc.runs = 0 THEN RETURN[FALSE];
[low, high] ← toc.runArray[FindRunArrayIndex[toc, index, index]];
RETURN[index IN [low .. high)];
END; -- of IsSelected --


TOCSelectionEmpty: PUBLIC
PROCEDURE [toc: TOCHandle, key: CARDINAL] RETURNS [BOOLEAN] =
-- Returns TRUE iff the current TOCSelection is empty.
BEGIN
IF key = 0 OR key # toc.key THEN exD.SysBug[];
RETURN[toc.runs = 0];
END; -- of TOCSelectionEmpty --


FirstSelectedEntry: PUBLIC
PROCEDURE [toc: TOCHandle, key: CARDINAL] RETURNS [vmD.TOCIndex] =
-- Returns the lowest entry in the current TOCSelection. Returns 0 if the current
-- TOCSelection is empty.
BEGIN
IF key = 0 OR key # toc.key THEN exD.SysBug[];
RETURN[IF toc.runs = 0 THEN 0 ELSE toc.runArray[0].low];
END; -- of FirstSelectedEntry --


LastSelectedEntry: PUBLIC
PROCEDURE [toc: TOCHandle, key: CARDINAL] RETURNS [TOCIndex] =
-- Returns the highest entry in the current TOCSelection. Returns 0 if the current
-- TOCSelection is empty.
BEGIN
IF key = 0 OR key # toc.key THEN exD.SysBug[];
RETURN[IF toc.runs = 0 THEN 0 ELSE toc.runArray[toc.runs - 1].high - 1];
END; -- of LastSelectedEntry --


NextSelectedEntry: PUBLIC
PROCEDURE [toc: TOCHandle, key: CARDINAL, index: TOCIndex] RETURNS [TOCIndex] =
-- Returns the next higher entry after index in the current TOCSelection. Returns 0 if no
-- higher selected entry exists.
BEGIN
runIndex: CARDINAL;
low, high: TOCIndex;
IF key = 0 OR key # toc.key THEN exD.SysBug[];
IF toc.runs = 0 THEN RETURN[0];
runIndex ← FindRunArrayIndex[toc, index, index];
[low, high] ← toc.runArray[runIndex];
SELECT TRUE FROM
(index < low) => RETURN[low];
(index IN [low .. high - 1)) => RETURN[index + 1];
(index >= high - 1 AND runIndex + 1 < toc.runs) =>
RETURN[toc.runArray[runIndex + 1].low];
ENDCASE => RETURN[0];
END; -- of NextSelectedEntry --


PrevSelectedEntry: PUBLIC
PROCEDURE [toc: TOCHandle, key: CARDINAL, index: TOCIndex] RETURNS [TOCIndex] =
-- Returns the next lower entry before index in the current TOCSelection. Returns 0 if no
-- lower selected entry exists.
BEGIN
runIndex: CARDINAL;
low, high: TOCIndex;
IF key = 0 OR key # toc.key THEN exD.SysBug[];
IF toc.runs = 0 THEN RETURN[0];
runIndex ← FindRunArrayIndex[toc, index, index];
[low, high] ← toc.runArray[runIndex];
SELECT TRUE FROM
(index >= high) => RETURN[high - 1];
(index IN (low .. high)) => RETURN[index - 1];
(index <= low AND runIndex > 0) => RETURN[toc.runArray[runIndex - 1].high - 1];
ENDCASE => RETURN[0];
END; -- of PrevSelectedEntry --


FirstSelectedAndUndeletedEntry: PUBLIC PROCEDURE [toc: TOCHandle, key: CARDINAL]
RETURNS [index: TOCIndex] =
-- Returns the lowest entry in the current TOCSelection which is not deleted.
-- Returns 0 if no such entry exists.
BEGIN
tOCEntry: vmD.TOCFixedPart;
tb: vmD.TOCFixedPartPtr = @tOCEntry;
index ← FirstSelectedEntry[toc, key];
IF index#0 THEN
BEGIN

vmD.GetTOCFixedPart[toc, key, index, tb];
IF tb.deleted THEN index ← NextSelectedAndUndeletedEntry[toc, key, index];
END;
END; -- FirstSelectedAndUndeletedEntry --


NextSelectedAndUndeletedEntry: PUBLIC PROCEDURE [toc: TOCHandle, key: CARDINAL,
index: TOCIndex] RETURNS [TOCIndex] =
-- Returns the next higher entry after index in the current TOCSelection which is
-- not deleted. Returns 0 if no such entry exists.
BEGIN
tOCEntry: vmD.TOCFixedPart;
tb: vmD.TOCFixedPartPtr = @tOCEntry;
WHILE (index ← NextSelectedEntry[toc, key, index]) # 0 DO
vmD.GetTOCFixedPart[toc, key, index, tb];
IF ~tb.deleted THEN EXIT;
ENDLOOP;
RETURN [index];

END; -- NextSelectedAndUndeletedEntry --


ConsiderAll: PUBLIC PROCEDURE [tnp: inD.TOCTextNbrPtr, key: CARDINAL] =
BEGIN
toc: TOCHandle = tnp.toc;
IF key = 0 OR key # toc.key THEN exD.SysBug[];
IF toc.runs = 0 THEN RETURN;
FOR i: CARDINAL IN [0 .. toc.runs) DO
inD.Consider[toc.runArray[i].low, toc.runArray[i].high -1, tnp, key];
ENDLOOP;
END; -- of ConsiderAll --


DeconsiderAll: PUBLIC PROCEDURE [tnp: inD.TOCTextNbrPtr, key: CARDINAL] =
BEGIN
toc: TOCHandle = tnp.toc;
IF key = 0 OR key # toc.key THEN exD.SysBug[];
IF toc.runs = 0 THEN RETURN;
FOR i: CARDINAL IN [0 .. toc.runs) DO
inD.Deconsider[toc.runArray[i].low, toc.runArray[i].high -1, tnp, key];
ENDLOOP;
END; -- of DeconsiderAll --


TOCTextTracker: PUBLIC PROCEDURE [tnp: inD.TOCTextNbrPtr] =
-- Sets cursor shape for TOC Text subneighborhood. Watches for button up and down,
-- selects and restores selections in TOC according to the TOC selection protocol.
BEGIN
OPEN inD;
trackerState: {neutral, startIsCocked, extending, adding, removing} ← neutral;
extendState: {addLow, addHigh, takeLow, takeHigh};
x: ScreenXCoord;
y: ScreenYCoord;
mouseBits: UNSPECIFIED;
xOffset, yOffset: INTEGER;
low, high, start, lowerRunHigh, upperRunLow, thisEntry, floatC: TOCIndex;
runIndex: CARDINAL;
toc: TOCHandle = tnp.toc;
key: CARDINAL ← 0;
holdLock: BOOLEAN ← FALSE;
-- The toc is held locked for the entire time that the trackerState # neutral. We cannot
-- allow the toc the change while holding onto interim start, low, high, etc. indices. The
-- BailOut procedure implicitly believes this, considering that if trackerState # neutral,
-- then the toc is locked and key is valid.

BailOut: PROCEDURE =
BEGIN
SELECT trackerState FROM
startIsCocked => {Deconsider[start, start, tnp, key]; ConsiderAll[tnp, key]};
adding => Deconsider[start, start, tnp, key];
removing => Consider[start, start, tnp, key];
extending =>
SELECT extendState FROM
addLow => IF floatC < low THEN Deconsider[floatC, low - 1, tnp, key];
addHigh => IF floatC > high THEN Deconsider[high, floatC - 1, tnp, key];
takeLow => IF floatC > low THEN Consider[low, floatC - 1, tnp, key];
takeHigh => IF floatC < high THEN Consider[floatC, high - 1, tnp, key];
ENDCASE => ERROR;
ENDCASE;
IF holdLock THEN vmD.UnlockTOC[toc, key];
END; -- of BailOut --

dsD.ChangeCursor[lineArrow];
[ , xOffset, yOffset] ← dsD.GetCursor[];
DO
x ← cursorX↑ + xOffset;
y ← cursorY↑ + yOffset;
mouseBits ← inD.mouseBitsLoc↑;
IF ~(y IN [tnp.topY .. tnp.bottomY) AND x IN [lineBarLeftX .. leftMargin)) THEN
-- cursor is out of line bar subneighborhood -- {BailOut[]; RETURN};
IF trackerState # neutral OR SavedMouseButton[mouseBits, any, down] THEN
BEGIN
IF ~holdLock AND (key ← intC.source.key) = 0 THEN
{key ← vmD.WaitForLock[toc]; holdLock ← TRUE; LOOP};
IF (thisEntry ← MapYToTOCIndex[y, tnp]) > 0 THEN
SELECT trackerState FROM
neutral =>
BEGIN
start ← thisEntry;
SELECT TRUE FROM
(start = 0) => NULL;
SavedMouseButton[mouseBits, left, down] =>
BEGIN
trackerState ← startIsCocked;
DeconsiderAll[tnp, key];
Consider[start, start, tnp, key];
END;
SavedMouseButton[mouseBits, middle, down] =>
IF Editor.ShiftKey[up] AND ~IsSelected[toc, key, thisEntry] THEN
{trackerState ← adding; Consider[start, start, tnp, key]}
ELSE IF Editor.ShiftKey[down] AND IsSelected[toc, key, thisEntry] THEN
{trackerState ← removing; Deconsider[start, start, tnp, key]};
(SavedMouseButton[mouseBits, right, down] AND toc.runs > 0) =>
BEGIN
trackerState ← extending;
runIndex ← FindRunArrayIndex[toc, thisEntry, thisEntry];
[low, high] ← toc.runArray[runIndex];
lowerRunHigh ← IF runIndex = 0 THEN 0 ELSE toc.runArray[runIndex - 1].high;
upperRunLow ← IF runIndex + 1 = toc.runs
THEN LAST[CARDINAL] ELSE toc.runArray[runIndex + 1].low;
SELECT thisEntry FROM
<= low =>
{extendState ← addLow; floatC ← thisEntry;
Consider[floatC, low - 1, tnp, key]};
< high =>
{extendState ← takeHigh; floatC ← thisEntry + 1;
Deconsider[floatC, high - 1, tnp, key]};
ENDCASE =>
{extendState ← addHigh; floatC ← thisEntry+ 1;
Consider[high, thisEntry, tnp, key]};
END;
ENDCASE;
END;
startIsCocked =>
SELECT TRUE FROM
(SavedMouseButton[mouseBits, left, down] AND thisEntry # start) =>
{Deconsider[start, start, tnp, key]; start ← thisEntry;
Consider[start, start, tnp, key]};
SavedMouseButton[mouseBits, left, up] =>
{SetTOCSelection[toc, key, start]; trackerState ← neutral;
inD.UpdateTOCThumbLine[tnp, key]};
ENDCASE;
adding =>
SELECT TRUE FROM
(SavedMouseButton[mouseBits, middle, down] AND thisEntry # start) =>
{Deconsider[start, start, tnp, key];
IF ~IsSelected[toc, key, thisEntry] THEN
{start ← thisEntry; Consider[start, start, tnp, key]}
ELSE trackerState ← neutral};
SavedMouseButton[mouseBits, middle, up] =>
{AddRange[toc, key, start, start + 1]; trackerState ← neutral;
inD.UpdateTOCThumbLine[tnp, key]};
ENDCASE;
removing =>
SELECT TRUE FROM
(SavedMouseButton[mouseBits, middle, down] AND thisEntry # start) =>
{Consider[start, start, tnp, key];
IF IsSelected[toc, key, thisEntry] THEN
{start ← thisEntry; Deconsider[start, start, tnp, key]}
ELSE trackerState ← neutral};
SavedMouseButton[mouseBits, middle, up] =>
{RemoveRange[toc, key, start, start + 1]; trackerState ← neutral;
inD.UpdateTOCThumbLine[tnp, key]};
ENDCASE;
extending =>
IF SavedMouseButton[mouseBits, right, down] THEN -- track --
SELECT extendState FROM
addLow => BEGIN -- [floatC .. low) will be added. --
SELECT thisEntry FROM
>= high, < lowerRunHigh =>
{Deconsider[floatC, low - 1, tnp, key]; trackerState ← neutral};
> low => {extendState ← takeLow; Deconsider[floatC, thisEntry - 1, tnp, key]};
> floatC => Deconsider[floatC, thisEntry - 1, tnp, key];
= floatC => NULL;
ENDCASE => Consider[thisEntry, floatC, tnp, key];
floatC ← thisEntry;
END;
addHigh => BEGIN -- [high .. floatC) will be added. --
SELECT thisEntry FROM
>= upperRunLow, < high =>
{Deconsider[high, floatC - 1, tnp, key]; trackerState ← neutral};
>= floatC => Consider[floatC, thisEntry, tnp, key];
= floatC - 1 => NULL;
ENDCASE => Deconsider[thisEntry + 1, floatC -1, tnp, key];
floatC ← thisEntry + 1;
END;
takeLow => BEGIN -- [low .. floatC) will be removed. --
SELECT thisEntry FROM
>= high, < lowerRunHigh =>
{Consider[low, floatC - 1, tnp, key]; trackerState ← neutral};
> floatC => Deconsider[floatC, thisEntry - 1, tnp, key];
= floatC => NULL;
>= low => Consider[thisEntry, floatC - 1, tnp, key];
ENDCASE =>
{extendState ← addLow; Consider[thisEntry, floatC - 1, tnp, key]};
floatC ← thisEntry;
END;
takeHigh => BEGIN -- [floatC .. high) will be removed. --
SELECT thisEntry FROM
>= high, < lowerRunHigh =>
{Consider[floatC, high - 1, tnp, key]; trackerState ← neutral};
>= floatC => Consider[floatC, thisEntry, tnp, key];
= floatC - 1 => NULL;
>= low => Deconsider[thisEntry +1, floatC - 1, tnp, key];
ENDCASE =>
BEGIN
extendState ← addLow;
Consider[floatC, high - 1, tnp, key];
Consider[thisEntry, low - 1, tnp, key];
thisEntry ← thisEntry - 1;
END;
floatC ← thisEntry + 1;
END;
ENDCASE => ERROR
ELSE -- right mouse button up --
BEGIN
trackerState ← neutral;
SELECT extendState FROM
addLow => IF floatC < low THEN AddRange[toc, key, floatC, low];
addHigh => IF floatC > high THEN AddRange[toc, key, high, floatC];
takeLow => IF floatC > low THEN RemoveRange[toc, key, low, floatC];
takeHigh => IF floatC < high THEN RemoveRange[toc, key, floatC, high];
ENDCASE => ERROR;
inD.UpdateTOCThumbLine[tnp, key];
END;
ENDCASE;
END;
IF holdLock AND trackerState = neutral THEN
{vmD.UnlockTOC[toc, key]; holdLock ← FALSE};
IdleLoop[];
AcceptKeyboardInput[ ! UNWIND => BailOut[]];
ENDLOOP;
END; -- of TOCTextTracker --


END. -- of TOCSelection --