-- file: TOCSelection.Mesa
-- last edited by Brotz, December 1, 1980 12:08 PM

DIRECTORY
dsD: FROM "DisplayDefs",
Editor,
inD: FROM "InteractorDefs",
Inline,
SystemDefs: FROM "SystemDefs",
tsD: FROM "TOCSelectionDefs",
vmD: FROM "VirtualMgrDefs";

TOCSelection: PROGRAM
IMPORTS dsD, Editor, inD, Inline, SystemDefs
EXPORTS tsD = PUBLIC

BEGIN

RunArrayBlk: TYPE = ARRAY [0 .. 0) OF RunRange;
RunArray: TYPE = POINTER TO RunArrayBlk;

RunRange: TYPE = RECORD
[low,
high: vmD.TOCIndex];

runArray: RunArray ← NIL;

maxRuns: CARDINAL ← 0;

runs: CARDINAL ← 0;

runIncrement: CARDINAL = 10;


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


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


FindRunArrayIndex: PROCEDURE [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 runs <= 1 THEN RETURN[0];
lowIndex ← 0;
highIndex ← runs - 1;
DO
IF runArray[highIndex].low <= high THEN RETURN[highIndex];
IF runArray[lowIndex].high >= low THEN RETURN[lowIndex];
IF lowIndex + 1 = highIndex THEN RETURN[lowIndex];
halfwayIndex ← (lowIndex + highIndex) / 2;
IF runArray[halfwayIndex].low > high
THEN highIndex ← halfwayIndex ELSE lowIndex ← halfwayIndex;
ENDLOOP;
END; -- of FindRunArrayIndex --


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


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


EnlargeRunArray: PROCEDURE =
-- Allocates a larger runArray and copies the contents of the old runArray into it.
BEGIN
newRunArray: RunArray;
maxRuns ← maxRuns + runIncrement;
newRunArray ← SystemDefs.AllocateHeapNode[SIZE[RunRange] * maxRuns];
Inline.COPY[from: runArray, nwords: runs * SIZE[RunRange], to: newRunArray];
SystemDefs.FreeHeapNode[runArray];
runArray ← newRunArray;
END; -- of EnlargeRunArray --


ResetTOCSelection: PROCEDURE =
-- Resets the entire TOCSelection to be empty.
BEGIN
IF maxRuns # runIncrement THEN
BEGIN
IF runArray # NIL THEN SystemDefs.FreeHeapNode[runArray];
maxRuns ← runIncrement;
runArray ← SystemDefs.AllocateHeapNode[SIZE[RunRange] * maxRuns];
END;
runs ← 0;
END; -- of ResetTOCSelection --


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


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


TOCSelectionEmpty: PROCEDURE RETURNS [BOOLEAN] =
-- Returns TRUE iff the current TOCSelection is empty.
BEGIN
RETURN[runs = 0];
END; -- of TOCSelectionEmpty --


FirstSelectedEntry: PROCEDURE RETURNS [vmD.TOCIndex] =
-- Returns the lowest entry in the current TOCSelection. Returns 0 if the current
-- TOCSelection is empty.
BEGIN
RETURN[IF runs = 0 THEN 0 ELSE runArray[0].low];
END; -- of FirstSelectedEntry --


LastSelectedEntry: PROCEDURE RETURNS [vmD.TOCIndex] =
-- Returns the highest entry in the current TOCSelection. Returns 0 if the current
-- TOCSelection is empty.
BEGIN
RETURN[IF runs = 0 THEN 0 ELSE runArray[runs - 1].high - 1];
END; -- of LastSelectedEntry --


NextSelectedEntry: PROCEDURE [index: vmD.TOCIndex] RETURNS [vmD.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: vmD.TOCIndex;
IF runs = 0 THEN RETURN[0];
runIndex ← FindRunArrayIndex[index, index];
[low, high] ← runArray[runIndex];
SELECT TRUE FROM
(index < low) => RETURN[low];
(index IN [low .. high - 1)) => RETURN[index + 1];
(index >= high - 1 AND runIndex + 1 < runs) => RETURN[runArray[runIndex + 1].low];
ENDCASE => RETURN[0];
END; -- of NextSelectedEntry --


PrevSelectedEntry: PROCEDURE [index: vmD.TOCIndex] RETURNS [vmD.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: vmD.TOCIndex;
IF runs = 0 THEN RETURN[0];
runIndex ← FindRunArrayIndex[index, index];
[low, high] ← runArray[runIndex];
SELECT TRUE FROM
(index >= high) => RETURN[high - 1];
(index IN (low .. high)) => RETURN[index - 1];
(index <= low AND runIndex > 0) => RETURN[runArray[runIndex - 1].high - 1];
ENDCASE => RETURN[0];
END; -- of PrevSelectedEntry --


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


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


TOCTextTracker: 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;
xOffset, yOffset: INTEGER;
low, high, start, lowerRunHigh, upperRunLow, thisEntry, floatC: vmD.TOCIndex;
runIndex: CARDINAL;

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

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


END. -- of TOCSelection --