-- ALELinesA.mesa
--   Edited by Sweet,  9-Apr-82 10:22:13
DIRECTORY
  ALEOps,
  Storage,
  String,
  Table,
  Window;

ALELinesA: PROGRAM
  IMPORTS ALEOps, Storage, String, Table, Window
  EXPORTS ALEOps =
  BEGIN OPEN ALEOps;
  header: PUBLIC ALEHeader ← [];

  ptb, ltb, lbb, hrb, vrb: Table.Base;
  tableSpace: POINTER;
  hArray: PUBLIC DESCRIPTOR FOR ARRAY OF HorizRec;
  vArray: PUBLIC DESCRIPTOR FOR ARRAY OF VertRec;
  lastH, lastV: PUBLIC INTEGER;
  DrawnWidth: ARRAY [-3..4] OF CARDINAL = [1, 1, 1, 1, 1, 2, 4, 4];

  LineNotify: Table.Notifier =
    BEGIN
    ptb ← base[ptType];
    ltb ← base[ltType];
    lbb ← base[lbType];
    hrb ← base[hrType];
    hArray ← DESCRIPTOR[hrb, LENGTH[hArray]];
    vrb ← base[vrType];
    vArray ← DESCRIPTOR[vrb, LENGTH[vArray]];
    ALELinesBNotify[base];
    END;

  tablePages: CARDINAL ← 100;
  InitLines: PUBLIC PROC =
    BEGIN
    weights: ARRAY [0..nTables) OF CARDINAL ← [10, 10, 10, 10, 10, 10, 10];
    p: POINTER;
    header ← [];
    tableSpace ← p ← Storage.Pages[tablePages];
    hArray ← DESCRIPTOR[NIL, 0];
    vArray ← DESCRIPTOR[NIL, 0];
    lastH ← lastV ← -1;
    Table.Create[[LOOPHOLE[p], tablePages*256], DESCRIPTOR[weights]];
    Table.AddNotify[LineNotify];
    InitHash[];
    END;

  ResetLines: PUBLIC PROC =
    BEGIN
    FOR i: CARDINAL IN [0..nTables) DO Table.Trim[i, 0] ENDLOOP;
    hArray ← DESCRIPTOR[NIL, 0];
    vArray ← DESCRIPTOR[NIL, 0];
    lastH ← lastV ← -1;
    ResetHash[];
    header ← [];
    END;

  MulDiv: PUBLIC PROC [a, b, c: LONG INTEGER] RETURNS [LONG INTEGER] =
    BEGIN
    threshold: LONG INTEGER;
    IF a < b THEN {t: LONG INTEGER = a; a ← b; b ← t};
    threshold ← LAST[LONG INTEGER] / ABS[b];
    WHILE ABS[a] > threshold DO a ← a/8; c ← c/8; ENDLOOP;
    RETURN[(a*b)/c]
    END;

  lastHY: ADistance;
  FindHList: PUBLIC PROC [y: ADistance, addNew: BOOLEAN ← FALSE] RETURNS [hi: INTEGER] =
    BEGIN
    l, u: INTEGER;
    l ← 0; u ← LENGTH[hArray] - 1;
    IF u = -1 AND ~addNew THEN RETURN[-1];
    IF lastH # -1 THEN SELECT y FROM
      > lastHY => l ← MIN[lastH + 1, LENGTH[hArray]-1];
      < lastHY => u ← MAX[lastH - 1, 0];
      ENDCASE => RETURN[lastH];
    WHILE l <= u DO
      hi ← (l+u)/2;
      SELECT hArray[hi].y FROM
	< y => l ← hi + 1;
	> y => u ← hi - 1;
	ENDCASE => {lastHY ← y; lastH ← hi; RETURN};
      ENDLOOP;
    hi ← l;
    IF ~addNew THEN
      {IF hi = LENGTH[hArray] THEN RETURN[-1];
      lastHY ← hArray[hi].y; lastH ← hi; RETURN}; -- first item > y
    [] ← Table.Allocate[hrType, SIZE[HorizRec]];
    hArray ← DESCRIPTOR[hrb, LENGTH[hArray]+1];
    FOR i: INTEGER DECREASING IN (hi..LENGTH[hArray]) DO
      hArray[i] ← hArray[i-1];
      ENDLOOP;
    hArray[hi] ← [y: y];
    lastHY ← y; lastH ← hi; 
    RETURN
    END;

  UnChainHoriz: PROC [l: LTIndex] =
    BEGIN
    hi: INTEGER = FindHList[ptb[ltb[l].p1].pos.y];
    prev: LTIndex ← LTNull;
    IF hi = -1 THEN ERROR;
    FOR nl: LTIndex ← hArray[hi].lines, ltb[nl].thread WHILE
      nl # l DO
      IF nl = LTNull THEN ERROR;
      prev ← nl;
      ENDLOOP;
    IF hArray[hi].l.min = ptb[ltb[l].p1].pos.x OR
      hArray[hi].l.max = ptb[ltb[l].p2].pos.x THEN
	BEGIN
	max:  ADistance ← 0;
	min: ADistance ← LAST[ADistance];
	FOR nl: LTIndex ← hArray[hi].lines, ltb[nl].thread WHILE
	  nl # LTNull DO
	  min ← MIN[ptb[ltb[nl].p1].pos.x, min];
	  max ← MAX[ptb[ltb[nl].p2].pos.x, max];
	  ENDLOOP;
	hArray[hi].l ← [min: min, max: max];
	END;
    IF prev = LTNull THEN hArray[hi].lines ← ltb[l].thread
    ELSE ltb[prev].thread ← ltb[l].thread;
    END;

  UnChainVert: PROC [l: LTIndex] =
    BEGIN
    vi: INTEGER = FindVList[ptb[ltb[l].p1].pos.x];
    prev: LTIndex ← LTNull;
    IF vi = -1 THEN ERROR;
    FOR nl: LTIndex ← vArray[vi].lines, ltb[nl].thread WHILE
      nl # l DO
      IF nl = LTNull THEN ERROR;
      prev ← nl;
      ENDLOOP;
    IF vArray[vi].l.min = ptb[ltb[l].p1].pos.y OR
      vArray[vi].l.max = ptb[ltb[l].p2].pos.y THEN
	BEGIN
	max:  ADistance ← 0;
	min: ADistance ← LAST[ADistance];
	FOR nl: LTIndex ← vArray[vi].lines, ltb[nl].thread WHILE
	  nl # LTNull DO
	  min ← MIN[ptb[ltb[nl].p1].pos.y, min];
	  max ← MAX[ptb[ltb[nl].p2].pos.y, max];
	  ENDLOOP;
	vArray[vi].l ← [min: min, max: max];
	END;
    IF prev = LTNull THEN vArray[vi].lines ← ltb[l].thread
    ELSE ltb[prev].thread ← ltb[l].thread;
    END;

  UnChainDiag: PROC [l: LTIndex] =
    BEGIN
    prev: LTIndex ← LTNull;
    FOR nl: LTIndex ← header.diagLines, ltb[nl].thread WHILE
      nl # l DO
      IF nl = LTNull THEN ERROR;
      prev ← nl;
      ENDLOOP;
    IF prev = LTNull THEN header.diagLines ← ltb[l].thread
    ELSE ltb[prev].thread ← ltb[l].thread;
    END;

  lastVX: ADistance;
  FindVList: PUBLIC PROC [x: ADistance, addNew: BOOLEAN ← FALSE] RETURNS [vi: INTEGER] =
    BEGIN
    l, u: INTEGER;
    l ← 0; u ← LENGTH[vArray] - 1;
    IF u = -1 AND ~addNew THEN RETURN[-1];
    IF lastV # -1 THEN SELECT x FROM
      > lastVX => l ← MIN[lastV + 1, LENGTH[vArray]-1];
      < lastVX => u ← MAX[lastV - 1, 0];
      ENDCASE => RETURN[lastV];
    WHILE l <= u DO
      vi ← (l+u)/2;
      SELECT vArray[vi].x FROM
	< x => l ← vi + 1;
	> x => u ← vi - 1;
	ENDCASE => {lastVX ← x; lastV ← vi; RETURN};
      ENDLOOP;
    vi ← l;
    IF ~addNew THEN 
      {IF vi = LENGTH[vArray] THEN RETURN[-1];
      lastVX ← vArray[vi].x; lastV ← vi; RETURN}; -- first item > x
    [] ← Table.Allocate[vrType, SIZE[VertRec]];
    vArray ← DESCRIPTOR[vrb, LENGTH[vArray]+1];
    FOR i: INTEGER DECREASING IN (vi..LENGTH[vArray]) DO
      vArray[i] ← vArray[i-1];
      ENDLOOP;
    vArray[vi] ← [x: x];
    lastVX ← x; lastV ← vi; 
    RETURN
    END;

  AllPoints: PUBLIC PROC [action: PointScan] RETURNS [p: PTIndex] =
    BEGIN
    pointTableSize: CARDINAL = Table.Bounds[ptType].size;
    FOR p ← FIRST[PTIndex], p + SIZE[Point] WHILE 
	LOOPHOLE[p, CARDINAL] < pointTableSize DO
      IF ~ptb[p].free AND action[p, @ptb[p]] THEN RETURN;
      ENDLOOP;
    RETURN[PTNull]
    END;

  SelectedPoints: PUBLIC PROC [action: PointScan] RETURNS [p: PTIndex] =
    BEGIN
    FOR p ← header.selectedPoints, header.nextSelPoint WHILE p # PTNull DO 
      header.nextSelPoint ← ptb[p].selNext;
      IF action[p, @ptb[p]] THEN RETURN;
      ENDLOOP;
    END;

  AllLines: PUBLIC PROC [action: LineScan] RETURNS [l: LTIndex] =
    BEGIN
    lineTableSize: CARDINAL = Table.Bounds[ltType].size;
    FOR l ← FIRST[LTIndex], l + SIZE[Line] WHILE
	 LOOPHOLE[l, CARDINAL] < lineTableSize DO
      IF ~ltb[l].free AND action[l, @ltb[l]] THEN RETURN;
      ENDLOOP;
    RETURN[LTNull]
    END;

  SelectedLines: PUBLIC PROC [action: LineScan] RETURNS [l: LTIndex] =
    BEGIN
    next: LTIndex;
    FOR l ← header.selectedLines, next WHILE l # LTNull DO 
      next ← ltb[l].selNext;
      IF action[l, @ltb[l]] THEN RETURN;
      ENDLOOP;
    END;

  LinesThru: PUBLIC PROC [p: PTIndex, action: LineScan] RETURNS [l: LTIndex] =
    BEGIN
    next: LTIndex;
    l ← ptb[p].lines;
    WHILE l # LTNull DO
      next ← IF ltb[l].p1 = p THEN ltb[l].p1Chain ELSE ltb[l].p2Chain;
      IF action[l, @ltb[l]] THEN RETURN;
      l ← next;
      ENDLOOP;
    RETURN
    END;

  HLinesInABox: PROC [box: POINTER TO ABox, action: LineScan, completely: BOOLEAN ← FALSE] RETURNS [l: LTIndex] =
    BEGIN
    y1: ADistance ← box.y1;
    hi: INTEGER;
    IF ~completely THEN y1 ← y1 - AdjForWidth[4]; -- min possible
    hi ← FindHList[y1];
    IF hi = -1 THEN RETURN[LTNull];
    DO
      hr: HorizRec ← hArray[hi];
      IF hr.y > box.y2 THEN EXIT;
      IF hr.l.min < box.x2 AND hr.l.max > box.x1 THEN
	BEGIN
	FOR l ← hr.lines, ltb[l].thread WHILE l # LTNull DO 
	  x1: ADistance = ptb[ltb[l].p1].pos.x;
	  x2: ADistance = ptb[ltb[l].p2].pos.x;
	  IF x1 > box.x2 THEN EXIT;
	  IF (
	      (completely AND x1>= box.x1 AND x2 <= box.x2 AND
		hr.y + AdjForWidth[ltb[l].width] <= box.y2) OR 
	      (x2 >= box.x1 AND hr.y + AdjForWidth[ltb[l].width] >= box.y1)) 
	    AND action[l, @ltb[l]] THEN RETURN;
	  ENDLOOP;
	END;
      hi ← hi + 1;
      IF hi >= LENGTH[hArray] THEN EXIT;
      ENDLOOP;
    RETURN [LTNull];
    END;

  VLinesInABox: PROC [box: POINTER TO ABox, action: LineScan, completely: BOOLEAN ← FALSE] RETURNS [l: LTIndex] =
    BEGIN
    x1: ADistance ← box.x1;
    vi: INTEGER;
    IF ~completely THEN x1 ← x1 - AdjForWidth[4]; -- min possible
    vi ← FindVList[x1];
    IF vi = -1 THEN RETURN[LTNull];
    DO
      vr: VertRec ← vArray[vi];
      IF vr.x > box.x2 THEN EXIT;
      IF vr.l.min < box.y2 AND vr.l.max > box.y1 THEN
	BEGIN
	FOR l ← vr.lines, ltb[l].thread WHILE l # LTNull DO 
	  y1: ADistance = ptb[ltb[l].p1].pos.y;
	  y2: ADistance = ptb[ltb[l].p2].pos.y;
	  IF y1 > box.y2 THEN EXIT;
	  IF (
	      (completely AND y1>= box.y1 AND y2 <= box.y2 AND
		vr.x + AdjForWidth[ltb[l].width] <= box.x2) OR 
	      (y2 >= box.y1 AND vr.x + AdjForWidth[ltb[l].width] >= box.x1))
	    AND action[l, @ltb[l]] THEN RETURN;
	  ENDLOOP;
	END;
      vi ← vi + 1;
      IF vi >= LENGTH[vArray] THEN EXIT;
      ENDLOOP;
    RETURN [LTNull];
    END;

  AdjForWidth: PROC [w: INTEGER] RETURNS [ADistance] =
    INLINE {RETURN [DrawnWidth[state.magnify]*w-1]};

  DLinesInABox: PROC [box: POINTER TO ABox, action: LineScan, completely: BOOLEAN ← FALSE] RETURNS [l: LTIndex] =
    BEGIN -- rework for completely
    FOR l ← header.diagLines, ltb[l].thread WHILE l # LTNull DO
      lBox: ABox ← ABoxForBox[BoxForLine[l]];
      IF ~(lBox.x1 > box.x2 OR box.x1 > lBox.x2 OR
	   lBox.y1 > box.y2 OR box.y1 > lBox.y2)
	 AND action[l, @ltb[l]] THEN RETURN;
      ENDLOOP;
    RETURN [LTNull];
    END;

  LinesInABox: PUBLIC PROC [box: POINTER TO ABox, action: LineScan, completely: BOOLEAN ← FALSE] RETURNS [l: LTIndex] =
    BEGIN
    l ← HLinesInABox[box, action, completely];
    IF l # LTNull THEN RETURN;
    l ← VLinesInABox[box, action, completely];
    IF l # LTNull THEN RETURN;
    RETURN [DLinesInABox[box, action, completely]];
    END;

  ABoxForBox: PUBLIC PROCEDURE [box: Window.Box] RETURNS [aBox: ABox] =
    BEGIN -- is conservative for magnify < 0
    fudge: ARRAY [-3..4] OF ADistance = [7*16, 3*16, 16, 0, 0, 0, 0, 0];
    aPlace: APosition = APosForPlace[box.place];
    aBox ← [
      x1: aPlace.x, 
      x2: aPlace.x + ADistanceForDots[box.dims.w] + fudge[state.magnify],  
      y1: aPlace.y, 
      y2: aPlace.y + ADistanceForDots[box.dims.h] + fudge[state.magnify]];
    END;

  SelectedLabels: PUBLIC PROC [action: LabelScan] RETURNS [lb: LBIndex] =
    BEGIN
    next: LBIndex;
    FOR lb ← header.selectedLabels, next WHILE lb # LBNull DO
      next ← lbb[lb].selNext;
      IF action[lb, @lbb[lb]] THEN RETURN;
      ENDLOOP;
    END;

  currentWidth: PUBLIC LineWidth ← 1;
  currentTexture: PUBLIC LineTexture ← solid;

  ClearSelections: PUBLIC PROC =
    BEGIN
    DeselectLine: LineScan = 
      BEGIN
      lth.selected ← FALSE;
      lth.selNext ← LTNull;
      MaybeDisplayLine[l];
      RETURN[FALSE]
      END;
    DeselectLabel: LabelScan = 
      BEGIN
      lbh.selected ← FALSE;
      lbh.selNext ← LBNull;
      PaintLabel[lb];
      RETURN[FALSE]
      END;
    DeselectPoint: PointScan = 
      BEGIN
      Window.InvalidateBox[pictureWindow, BoxForPoint[p]];
      pth.selNext ← PTNull;
      pth.selected ← FALSE;
      RETURN[FALSE]
      END;
    [] ← SelectedPoints[DeselectPoint];
    [] ← SelectedLines[DeselectLine];
    [] ← SelectedLabels[DeselectLabel];
    header.selectedPoints ← PTNull;
    header.selectedLines ← LTNull;
    header.selectedLabels ← LBNull;
    END;

  BoxForPoint: PUBLIC PROC [p: PTIndex] RETURNS [Window.Box] =
    BEGIN
    pPlace: Window.Place = PicturePlace[ptb[p].pos];
    RETURN[[[pPlace.x-4, pPlace.y-4], [9,9]]]
    END;

  ClosePoint: PROC [pos: APosition, selected: BOOLEAN ← FALSE] RETURNS [p: PTIndex] =
    BEGIN
    epsilon: ADistance ← ADistanceForDots[2 * DrawnWidth[state.magnify]];
    Check: PointScan =
      BEGIN
      RETURN [ (~selected OR pth.selected) AND
	ABS[pth.pos.x-pos.x] <= epsilon AND ABS[pth.pos.y-pos.y] <= epsilon];
      END;
    p ← AllPoints[Check];
    END;

  CloseLine: PROC [pos: APosition, selected: BOOLEAN ← FALSE] RETURNS [l: LTIndex] =
    BEGIN
    epsilon: ADistance ← (2*ADistanceForDots[1]);
    box: ABox ← [x1: pos.x - epsilon, x2: pos.x + epsilon, 
      y1: pos.y - epsilon, y2: pos.y + epsilon];
    Check: LineScan =
      BEGIN
      y, x: ADistance;
      pos1: APosition = ptb[lth.p1].pos;
      pos2: APosition = ptb[lth.p2].pos;
      IF selected AND ~lth.selected THEN RETURN[FALSE];
      SELECT lth.class FROM
	horiz => RETURN [TRUE];
	vert => RETURN [TRUE];
	shallow => 
	  BEGIN
	  IF pos.x ~IN [pos1.x..pos2.x] THEN RETURN[FALSE];
          y ← pos1.y + MulDiv[
	    (pos.x - pos1.x), (pos2.y - pos1.y), (pos2.x - pos1.x)];
          RETURN [pos.y IN 
	    [y-epsilon..
	     y + ADistanceForDots[DrawnWidth[lth.width]] + epsilon]];
	  END;
	ENDCASE => IF pos1.y > pos2.y THEN
	  BEGIN
	  IF pos.y ~IN [pos2.y..pos1.y] THEN RETURN[FALSE];
	  END
	ELSE IF pos.y ~IN [pos1.y..pos2.y] THEN RETURN[FALSE];
      x ← pos1.x + MulDiv[
	(pos1.y - pos.y), (pos2.x - pos1.x), (pos1.y - pos2.y)];
      RETURN [pos.x IN 
	    [x-epsilon..
	     x + ADistanceForDots[DrawnWidth[lth.width]] + epsilon]];
      END;
    l ← LinesInABox[@box, Check];
    END;

  CloseLabel: PROC [pos: APosition, selected: BOOLEAN ← FALSE] RETURNS [lb: LBIndex] =
    BEGIN
    place: Window.Place ← PicturePlace[pos];
    box: Window.Box ← [[place.x-2, place.y-2], [4,4]];
    Check: LabelScan =
      BEGIN
      labelBox: Window.Box;
      IF selected AND ~lbh.selected THEN RETURN[FALSE];
      labelBox ← BoxForLabel[lb];
      RETURN [~Disjoint[@labelBox, @box]];
      END;
    lb ← AllLabels[Check];
    END;

  AddSelection: PUBLIC PROC [pos: APosition] =
    BEGIN
    l: LTIndex;
    lb: LBIndex;
    p: PTIndex;
    p ← ClosePoint[pos];
    IF p # PTNull THEN
      BEGIN
      pPlace: Window.Place = PicturePlace[ptb[p].pos];
      IF ~ptb[p].selected THEN
	BEGIN
        ptb[p].selected ← TRUE;
        Window.DisplayData[
          window: pictureWindow,
          box: [[x: pPlace.x-8, y: pPlace.y-8], [16,16]],
          data: @Cursors[selPt],
          wpl: 1];
        ptb[p].selNext ← header.selectedPoints;
        header.selectedPoints ← p;
	END;
      ASetSourcePos[ptb[p].pos];
      RETURN
      END;
    l ← CloseLine[pos];
    IF l # LTNull THEN
      BEGIN
      IF ~ltb[l].selected THEN
	BEGIN
	ltb[l].selected ← TRUE; MaybeDisplayLine[l]; 
        ltb[l].selNext ← header.selectedLines;
        header.selectedLines ← l;
	END;
      ASetSourcePos[ptb[ltb[l].p1].pos]; 
      RETURN;
      END;
    lb ← CloseLabel[pos];
    IF lb # LBNull THEN
      BEGIN
      IF ~lbb[lb].selected THEN
	BEGIN
	lbb[lb].selected ← TRUE; PaintLabel[lb];
	lbb[lb].selNext ← header.selectedLabels;
	header.selectedLabels ← lb; 
	END;
      ASetSourcePos[lbb[lb].pos]; 
      RETURN;
      END;
    END;

  UnSelChainPoint: PROC [p: PTIndex] =
    BEGIN
    prev: PTIndex ← PTNull;
    FOR np: PTIndex ← header.selectedPoints, ptb[np].selNext WHILE
      np # p DO
      IF np = PTNull THEN ERROR;
      prev ← np;
      ENDLOOP;
    IF prev = PTNull THEN header.selectedPoints ← ptb[p].selNext
    ELSE ptb[prev].selNext ← ptb[p].selNext;
    IF header.nextSelPoint = p THEN header.nextSelPoint ← ptb[p].selNext;
    ptb[p].selNext ← PTNull;
    END;

  UnSelChainLine: PROC [l: LTIndex] =
    BEGIN
    prev: LTIndex ← LTNull;
    FOR nl: LTIndex ← header.selectedLines, ltb[nl].selNext WHILE
      nl # l DO
      IF nl = LTNull THEN ERROR;
      prev ← nl;
      ENDLOOP;
    IF prev = LTNull THEN header.selectedLines ← ltb[l].selNext
    ELSE ltb[prev].selNext ← ltb[l].selNext;
    ltb[l].selNext ← LTNull;
    END;

  UnSelChainLabel: PUBLIC PROC [lb: LBIndex] =
    BEGIN
    prev: LBIndex ← LBNull;
    FOR nlb: LBIndex ← header.selectedLabels, lbb[nlb].selNext WHILE
      nlb # lb DO
      IF nlb = LBNull THEN ERROR;
      prev ← nlb;
      ENDLOOP;
    IF prev = LBNull THEN header.selectedLabels ← lbb[lb].selNext
    ELSE lbb[prev].selNext ← lbb[lb].selNext;
    lbb[lb].selNext ← LBNull;
    END;

  SubSelection: PUBLIC PROC [pos: APosition] =
    BEGIN
    l: LTIndex;
    lb: LBIndex;
    p: PTIndex;
    p ← ClosePoint[pos, TRUE];
    IF p # PTNull THEN
      BEGIN
      Window.InvalidateBox[pictureWindow, BoxForPoint[p]];
      ptb[p].selected ← FALSE;
      UnSelChainPoint[p];
      RETURN
      END;
    l ← CloseLine[pos, TRUE];
    IF l # LTNull THEN
      {ltb[l].selected ← FALSE; 
      UnSelChainLine[l];
      MaybeDisplayLine[l]; 
      RETURN};
    lb ← CloseLabel[pos, TRUE];
    IF lb # LBNull THEN
      {lbb[lb].selected ← FALSE; 
      UnSelChainLabel[lb];
      PaintLabel[lb]; 
      RETURN};
    END;

  SelectInBox: PUBLIC PROC [pos1, pos2: APosition] =
    BEGIN
    ul: APosition = [x: MIN[pos1.x, pos2.x], y: MIN[pos1.y, pos2.y]];
    lr: APosition = [x: MAX[pos1.x, pos2.x], y: MAX[pos1.y, pos2.y]];
    newSource: APosition ← [LAST[ADistance], LAST[ADistance]];
    CheckLine: LineScan =
      BEGIN
      lp1: APosition = ptb[lth.p1].pos;
      lp2: APosition = ptb[lth.p2].pos;
      IF lp1.x IN [ul.x..lr.x] AND lp2.x IN [ul.x..lr.x] AND
        lp1.y IN [ul.y..lr.y] AND lp2.y IN [ul.y..lr.y] THEN
          {lth.selected ← TRUE; 
	  lth.selNext ← header.selectedLines;
	  header.selectedLines ← l;
	  MaybeDisplayLine[l];
	  newSource.x ← MIN[newSource.x, lp1.x];
	  newSource.y ← MIN[newSource.y, lp1.y, lp2.y]};
      RETURN[FALSE];
      END;
    CheckLabel: LabelScan =
      BEGIN
      IF lbh.pos.x IN [ul.x..lr.x] AND lbh.pos.y IN [ul.y..lr.y] THEN
        {lbh.selected ← TRUE; 
	  lbh.selNext ← header.selectedLabels;
	  header.selectedLabels ← lb;
	  PaintLabel[lb];
	  newSource.x ← MIN[newSource.x, lbh.pos.x];
	  newSource.y ← MIN[newSource.y, lbh.pos.y]};
      RETURN[FALSE];
      END;
    ClearSelections[];
    [] ← AllLines[CheckLine];
    [] ← AllLabels[CheckLabel]; 
    IF newSource # [LAST[ADistance], LAST[ADistance]] THEN 
      ASetSourcePos[newSource];
    END;

  DeleteSelections: PUBLIC PROC =
    BEGIN
    deleted: Redraw ← NIL;
    KillLine: LineScan =
      BEGIN
      new: Redraw = Storage.Node[SIZE[line RedrawObject]];
      pos1: APosition = ptb[lth.p1].pos;
      pos2: APosition = ptb[lth.p2].pos;
      new↑ ← [next: deleted, var: line[
        pos1: pos1,
        pos2: pos2,
        width: lth.width,
        texture: lth.texture]];
      deleted ← new;
      DeleteLine[l];
      RETURN[FALSE];
      END;
    KillLabel: LabelScan =
      BEGIN
      new: Redraw = Storage.Node[SIZE[label RedrawObject]];
      new↑ ← [next: deleted, var: label[
        font: lbh.font, mode: lbh.mode,
        pos: lbh.pos,
        hti: lbh.hti]];
      deleted ← new;
      DeleteLabel[lb];
      RETURN[FALSE];
      END;
    [] ← SelectedLines[KillLine];
    [] ← SelectedLabels[KillLabel];
    header.selectedLines ← LTNull;
    header.selectedPoints ← PTNull;
    ToWasteBasket[deleted];
    END;

  UndeleteItems: PUBLIC PROC =
    BEGIN
    ClearSelections[];
    RedrawItems[FromWasteBasket[]];
    END;

  wbDepth: CARDINAL = 4;
  wasteBasket: ARRAY [0..wbDepth) OF Redraw ← ALL[NIL];

  ToWasteBasket: PROC [new: Redraw] =
    BEGIN
    FreeItems[wasteBasket[wbDepth-1]];
    FOR i: CARDINAL DECREASING IN (0..wbDepth) DO
      wasteBasket[i] ← wasteBasket[i-1]
      ENDLOOP;
    wasteBasket[0] ← new;
    END;

  FromWasteBasket: PROC RETURNS [rd: Redraw] =
    BEGIN
    rd ← wasteBasket[0];
    FOR i: CARDINAL IN (0..wbDepth) DO
      wasteBasket[i-1] ← wasteBasket[i]
      ENDLOOP;
    wasteBasket[wbDepth-1] ← NIL;
    END;

  FreeItems: PROC [rd: Redraw] =
    BEGIN
    next: Redraw;
    WHILE rd # NIL DO
      next ← rd.next;
      Storage.Free[rd];
      rd ← next;
      ENDLOOP;
    END;

  SourceToClosePoint: PUBLIC PROC [pos: APosition] =
    BEGIN
    p: PTIndex ← ClosePoint[pos];
    IF p # PTNull THEN ASetSourcePos[ptb[p].pos];
    END;

  DestToClosePoint: PUBLIC PROC [pos: APosition] =
    BEGIN
    p: PTIndex ← ClosePoint[pos];
    IF p # PTNull THEN ASetDestPos[ptb[p].pos];
    END;

  PosOf: PUBLIC PROC [p: PTIndex] RETURNS [APosition] = 
    {RETURN [ptb[p].pos]};
  PointsOf: PUBLIC PROC [l: LTIndex] RETURNS [p1, p2: PTIndex] =
    {RETURN [ltb[l].p1, ltb[l].p2]};
  WidthOf: PUBLIC PROC [l: LTIndex] RETURNS [[1..4]] =
    {RETURN [ltb[l].width]};

  DeleteLine: PUBLIC PROC [l: LTIndex] =
    BEGIN
    prev: LTIndex;
    pt, pb: PTIndex;
    FindPT: LineScan =
      BEGIN
      p: PTIndex = IF ltb[l].p1 = pb THEN ltb[l].p2 ELSE ltb[l].p1;
      IF p = pt THEN RETURN[TRUE];
      prev ← l;
      RETURN[FALSE]
      END;
    Window.InvalidateBox[pictureWindow, BoxForLine[l]];
    pb ← ltb[l].p1; pt ← ltb[l].p2; prev ← LTNull;
    IF LinesThru[pb, FindPT] = LTNull THEN ERROR;
    IF prev = LTNull THEN ptb[pb].lines ← ltb[l].p1Chain
    ELSE
      IF ltb[prev].p1 = pb THEN ltb[prev].p1Chain ← ltb[l].p1Chain
      ELSE ltb[prev].p2Chain ← ltb[l].p1Chain;
    pb ← ltb[l].p2; pt ← ltb[l].p1; prev ← LTNull;
    IF LinesThru[pb, FindPT] = LTNull THEN ERROR;
    IF prev = LTNull THEN ptb[pb].lines ← ltb[l].p2Chain
    ELSE
      IF ltb[prev].p1 = pb THEN ltb[prev].p1Chain ← ltb[l].p2Chain
      ELSE ltb[prev].p2Chain ← ltb[l].p2Chain;
    SELECT ltb[l].class FROM
      horiz => UnChainHoriz[l];
      vert => UnChainVert[l];
      ENDCASE => UnChainDiag[l];
    FreeLine[l];
    IF ptb[pb].lines = LTNull THEN FreePoint[pb];
    IF ptb[pt].lines = LTNull THEN FreePoint[pt];
    END;

  FreeLine: PROC [l: LTIndex] =
    BEGIN
    ltb[LOOPHOLE[l, FNIndex]] ← [next: header.freeLine];
    header.freeLine ← l;
    END;

  FreePoint: PROC [p: PTIndex] =
    BEGIN
    hi: INTEGER = FindHList[ptb[p].pos.y, FALSE];
    follows: PTIndex ← PTNull;
    np: PTIndex;
    FOR np ← hArray[hi].points, ptb[np].thread WHILE np # p DO
      follows ← np;
      ENDLOOP;
    IF follows = PTNull THEN hArray[hi].points ← ptb[p].thread
    ELSE ptb[follows].thread ← ptb[p].thread;
    IF ptb[p].selected THEN UnSelChainPoint[p];
    ptb[LOOPHOLE[p, FNIndex]] ← [next: header.freePoint];
    header.freePoint ← p;
    END;

  DimSelection: PUBLIC PROC [pos: APosition, feet: BOOLEAN] =
    BEGIN
    Count: LineScan = {n ← n + 1; RETURN[FALSE]};
    This: LineScan = {RETURN[TRUE]};
    n: CARDINAL ← 0;
    l: LTIndex;
    s: STRING ← [20];
    pos1, pos2: APosition;
    dy, dx, len, inches, nfeet, num: ADistance;
    denom: CARDINAL;

    [] ← SelectedLines[Count];
    IF n # 1 THEN {OutString["Select a single line first"L]; RETURN};
    l ← SelectedLines[This];
    pos1 ← ptb[ltb[l].p1].pos; pos2 ← ptb[ltb[l].p2].pos;
    dx ← ABS[pos1.x - pos2.x]; dy ← ABS[pos1.y - pos2.y];
    len ← SELECT TRUE FROM
	dx = 0 => dy,
	dy = 0 => dx,
	ENDCASE => Hypot[dx, dy];
    inches ← len / 16; num ← len MOD 16;
    IF feet THEN {nfeet ← inches / 12; inches ← inches MOD 12}
    ELSE nfeet ← 0;
    IF nfeet # 0 THEN {
      String.AppendLongDecimal[s, nfeet];
      String.AppendChar[s, '']};
    IF inches # 0 THEN String.AppendLongDecimal[s, inches];
    IF num # 0 THEN {
      String.AppendChar[s, '-];
      denom ← 16;
      WHILE num MOD 2 = 0 DO num ← num / 2; denom ← denom / 2; ENDLOOP;
      String.AppendLongDecimal[s, num];
      String.AppendChar[s, '/];
      String.AppendDecimal[s, denom]};
    IF inches # 0 OR num # 0 THEN String.AppendChar[s, '"];
    DrawLabel[s, pos];
    END;
    

  END.