-- ALELinesB.mesa
--   Edited by Sweet, 20-Jan-81 10:36:11
DIRECTORY
  ALEOps,
  Inline,
  Storage,
  Table,
  UserTerminal,
  Window;

ALELinesB: PROGRAM
  IMPORTS ALEOps, Inline, Storage, Table, UserTerminal, Window
  EXPORTS ALEOps =
  BEGIN OPEN ALEOps;

  ptb, ltb, lbb: Table.Base;

  ALELinesBNotify: PUBLIC Table.Notifier =
    BEGIN
    ptb ← base[ptType];
    ltb ← base[ltType];
    lbb ← base[lbType];
    END;

  AddHoriz: PROCEDURE [l: LTIndex] =
    BEGIN
    x1: ADistance = ptb[ltb[l].p1].pos.x;
    x2: ADistance = ptb[ltb[l].p2].pos.x;
    y: ADistance = ptb[ltb[l].p1].pos.y;
    hi: INTEGER = FindHList[y, TRUE];
    prev: LTIndex ← LTNull;
    m: LTIndex ← hArray[hi].lines;
    WHILE m # LTNull AND ptb[ltb[m].p1].pos.x < x1 DO
      prev ← m; m ← ltb[m].thread;
      ENDLOOP;
    IF prev = LTNull THEN {hArray[hi].lines ← l; hArray[hi].l.min ← x1}
    ELSE ltb[prev].thread ← l;
    ltb[l].thread ← m;
    hArray[hi].l.max ← MAX[hArray[hi].l.max, x2];
    hArray[hi].maxW ← MAX[hArray[hi].maxW, ltb[l].width];
    END;

  AddVert: PROCEDURE [l: LTIndex] =
    BEGIN
    y1: ADistance = ptb[ltb[l].p1].pos.y;
    y2: ADistance = ptb[ltb[l].p2].pos.y;
    x: ADistance = ptb[ltb[l].p1].pos.x;
    vi: INTEGER = FindVList[x, TRUE];
    prev: LTIndex ← LTNull;
    m: LTIndex ← vArray[vi].lines;
    WHILE m # LTNull AND ptb[ltb[m].p1].pos.y < y1 DO
      prev ← m; m ← ltb[m].thread;
      ENDLOOP;
    IF prev = LTNull THEN {vArray[vi].lines ← l; vArray[vi].l.min ← y1}
    ELSE ltb[prev].thread ← l;
    ltb[l].thread ← m;
    vArray[vi].l.max ← MAX[vArray[vi].l.max, y2];
    vArray[vi].maxW ← MAX[vArray[vi].maxW, ltb[l].width];
    END;

  ShouldLengthen: PUBLIC PROC [l: LTIndex] RETURNS [extra: [0..4]] =
    BEGIN
    CheckThis: LineScan =
      BEGIN
      IF lth.class = vert AND lth.p2 = endP THEN 
	extra ← MAX[extra, lth.width];
      RETURN[FALSE];
      END;
    endP: PTIndex = ltb[l].p2;
    extra ← 0;
    IF ltb[l].class = horiz THEN [] ← LinesThru[endP, CheckThis];
    END;

  MaybeDisplayLine: PUBLIC PROC [l: LTIndex] =
    BEGIN
    lBox: Window.Box ← BoxForLine[l];
    vBox: Window.Box ← [
      [x: -pictureWindow.box.place.x, y: -pictureWindow.box.place.y],
      FrameBox.dims];
    IF ~Disjoint[@lBox, @vBox] THEN DisplayLine[l, lBox]; -- conservative
    END;

  RedrawSelections: PUBLIC PROC =
    BEGIN
    CheckLine: LineScan  =
      BEGIN
      IF lth.width # state.currentWidth OR lth.texture # state.currentTexture THEN
	BEGIN
        Window.InvalidateBox[pictureWindow, BoxForLine[l]];
        lth.width ← state.currentWidth;
        lth.texture ← state.currentTexture;
	END;
      RETURN[FALSE];
      END;
    CheckLabel: LabelScan =
      BEGIN
      IF lbh.font # state.currentFont THEN
	BEGIN
	Window.InvalidateBox[pictureWindow, BoxForLabel[lb]];
	lbh.font ← state.currentFont;
	END;
      RETURN[FALSE];
      END;
    [] ← SelectedLines[CheckLine];
    [] ← SelectedLabels[CheckLabel];
    END;

  DrawLine: PUBLIC PROC [pos1, pos2: APosition, selected: BOOLEAN ← TRUE] =
    BEGIN
    l: LTIndex;
    IF pos1 = pos2 THEN RETURN;
    IF selected THEN ClearSelections[];
    l ← InsertLine[
      InsertPoint[pos1],
      InsertPoint[pos2],
      state.currentWidth,
      state.currentTexture];
    ltb[l].selected ← selected;
    IF selected THEN header.selectedLines ← l;
    MaybeDisplayLine[l];
    END;

  InsertLine: PROC [p1, p2: PTIndex, width: LineWidth, texture: LineTexture] 
      RETURNS [newL: LTIndex] =
    BEGIN
    pos1: APosition;
    pos2: APosition;
    pTemp: PTIndex;
    FindP2: LineScan =
      BEGIN
      p: PTIndex = IF ltb[l].p1 = p1 THEN ltb[l].p2 ELSE ltb[l].p1;
      RETURN [p = p2];
      END;
    newL ← LinesThru[p1, FindP2];
    IF newL # LTNull THEN
      BEGIN
      Window.InvalidateBox[pictureWindow, BoxForLine[newL]];
      ltb[newL].width ← width;
      ltb[newL].texture ← texture;
      ltb[newL].selected ← TRUE;
      RETURN
      END;
    pos1 ← ptb[p1].pos;
    pos2 ← ptb[p2].pos;
    SELECT pos1.x FROM
      < pos2.x => NULL;
      > pos2.x => {pTemp ← p1; p1 ← p2; p2 ← pTemp};
      ENDCASE => IF pos1.y > pos2.y THEN
	{pTemp ← p1; p1 ← p2; p2 ← pTemp};
    newL ← AllocateLine[];
    ltb[newL] ← [
      p1: p1, p1Chain: ptb[p1].lines,
      p2: p2, p2Chain: ptb[p2].lines,
      width: width, texture: texture, class: NULL];
    ptb[p1].lines ← newL;
    ptb[p2].lines ← newL;
    ltb[newL].class ← SELECT TRUE FROM
      pos1.x = pos2.x => vert,
      pos1.y = pos2.y => horiz,
      ABS[pos1.x-pos2.x] <= ABS[pos1.y-pos2.y] => steep,
      ENDCASE => shallow;
    SELECT ltb[newL].class FROM
      horiz => AddHoriz[newL];
      vert => AddVert[newL];
      ENDCASE =>
	{ltb[newL].thread ← header.diagLines; header.diagLines ← newL};
    END;

  AllocateLine: PROC RETURNS [l: LTIndex] =
    BEGIN
    IF (l ← header.freeLine) # LTNull THEN
      BEGIN
      header.freeLine ← ltb[LOOPHOLE[header.freeLine, FNIndex]].next;
      RETURN
      END;
    l ← Table.Allocate[ltType, SIZE[Line]];
    END;

  InsertPoint: PROC [pos: APosition] RETURNS [newP: PTIndex] =
    BEGIN
    hi: INTEGER = FindHList[pos.y, TRUE];
    follows: PTIndex ← PTNull;
    next: PTIndex;
    newP ← hArray[hi].points;
    WHILE newP # PTNull DO
      SELECT ptb[newP].pos.x FROM
	< pos.x => NULL;
	> pos.x => EXIT;
	ENDCASE => RETURN;
      follows ← newP;
      newP ← ptb[follows].thread;
      ENDLOOP;
    next ← IF follows = PTNull THEN hArray[hi].points
      ELSE ptb[follows].thread;
    newP ← AllocatePoint[];
    ptb[newP] ← [pos: pos, thread: next];
    IF follows = PTNull THEN hArray[hi].points ← newP
    ELSE ptb[follows].thread ← newP;
    hArray[hi].p.max ← MAX[hArray[hi].p.max, pos.x];
    hArray[hi].p.min ← MIN[hArray[hi].p.min, pos.x];
    END;

  AllocatePoint: PROC RETURNS [p: PTIndex] =
    BEGIN
    IF (p ← header.freePoint) # PTNull THEN
      BEGIN
      header.freePoint ← ptb[LOOPHOLE[header.freePoint, FNIndex]].next;
      RETURN
      END;
    p ← Table.Allocate[ptType, SIZE[Point]];
    END;

  DrawnWidth: ARRAY [-3..4] OF CARDINAL = [1, 1, 1, 1, 1, 2, 4, 4];

  BoxForLine: PUBLIC PROC [l: LTIndex] RETURNS [box: Window.Box] =
    BEGIN
    pos1: APosition = ptb[ltb[l].p1].pos;
    pos2: APosition = ptb[ltb[l].p2].pos;
    x1: ADistance = MIN[pos1.x, pos2.x];
    y1: ADistance = MIN[pos1.y, pos2.y];
    width: CARDINAL = ltb[l].width * DrawnWidth[state.magnify];
    w: CARDINAL ← MAX[DotsForADistance[ABS[pos1.x-pos2.x]], 1];
    h: CARDINAL ← MAX[DotsForADistance[ABS[pos1.y-pos2.y]], 1];
    IF ltb[l].class >= steep THEN w ← w + width
    ELSE h ← h + width;
    IF ltb[l].class = horiz THEN
      w ← w + ShouldLengthen[l] * DrawnWidth[state.magnify];
    RETURN [[PicturePlace[[x: x1, y: y1]], [w: w, h: h]]];
    END;

  DisplayLine: PUBLIC PROC [l: LTIndex, box: Window.Box] =
    BEGIN
    aBox: ABox ← ABoxForBox[box];
    ChopUpLine[l, @aBox, DisplaySolidLine];
    END;

  Hypot: PUBLIC PROC [a,b: LONG INTEGER] RETURNS [LONG INTEGER] =
    BEGIN
    factor: LONG INTEGER ← 1;
    c: LONG INTEGER;
    a ← ABS[a]; b ← ABS[b];
    WHILE a > LAST[INTEGER]/2 OR b > LAST[INTEGER]/2 DO
      a ← a/2; b ← b/2; factor ← factor * 2;
      ENDLOOP;
    c ← Sqrt[a*a+b*b];
    RETURN [c*factor];
    END;

  ChopUpLine: PUBLIC PROC [l: LTIndex, box: POINTER TO ABox, pproc: PROC [
      pos1, pos2: APosition, 
      class: LineClass, 
      color: LineColor, 
      lWidth: LineWidth, 
      lengthen: [0..4], 
      box: POINTER TO ABox]] =
    BEGIN
    marksRemoved: BOOLEAN ← FALSE;
    inch: ADistance = 16;
    tex: LineTexture = ltb[l].texture;
    dash: ADistance ← SELECT tex FROM
      d2 => 2*inch,
      d4 => 4*inch,
      ENDCASE => 6*inch; -- solid will ignore this value
    gap: ADistance ← inch;
    class: LineClass = ltb[l].class;
    width: LineWidth = ltb[l].width;
    pos1: APosition ← ptb[ltb[l].p1].pos;
    pos2: APosition ← ptb[ltb[l].p2].pos;
    segstart: APosition;
    solidColor: LineColor = IF ltb[l].selected THEN grey ELSE black;
    length, ends: ADistance;
    lengthen: [0..4] ← 0;
    SELECT class FROM
      horiz => {length ← pos2.x - pos1.x; lengthen ← ShouldLengthen[l]};
      vert => length ← pos2.y - pos1.y;
      ENDCASE =>
	BEGIN
        MarksOut[]; marksRemoved ← TRUE;
        length ← Hypot[pos2.x - pos1.x, pos2.y - pos1.y];
	END;
    IF tex = solid OR length < dash+2*gap THEN 
      {pproc[pos1, pos2, class, solidColor, width, lengthen, box];
      IF marksRemoved THEN MarksIn[];
      RETURN};
    SELECT class FROM
      horiz => 
	BEGIN
	DoSeg: PROC [sl: ADistance, sc: LineColor, lengthen: [0..4] ← 0] =
          BEGIN
	  segstop: APosition ← [x: segstart.x + sl, y: segstart.y];
	  pproc[
	    pos1: segstart, 
	    pos2: segstop, 
	    class: class, 
	    color: sc, 
	    lWidth: width, 
	    lengthen: lengthen, 
	    box: box];
	  segstart ← segstop;
	  END;
	ends ← (dash + (length - dash - gap) MOD (dash+gap))/2;
	segstart ← pos1;
	DoSeg[ends, solidColor];
	DoSeg[gap, white];
	WHILE segstart.x + dash + gap < pos2.x DO
	  DoSeg[dash, solidColor];
	  DoSeg[gap, white];
	  ENDLOOP;
	DoSeg[pos2.x - segstart.x, solidColor, lengthen];
	END;
      vert => 
	BEGIN
	DoSeg: PROC [sl: ADistance, sc: LineColor] =
          BEGIN
	  segstop: APosition ← [x: segstart.x, y: segstart.y + sl];
	  pproc[
	    pos1: segstart, 
	    pos2: segstop, 
	    class: class, 
	    color: sc, 
	    lWidth: width, 
	    lengthen: 0, 
	    box: box];
	  segstart ← segstop;
	  END;
	ends ← (dash + (length - dash - gap) MOD (dash+gap))/2;
	segstart ← pos1;
	DoSeg[ends, solidColor];
	DoSeg[gap, white];
	WHILE segstart.y + dash + gap < pos2.y DO
	  DoSeg[dash, solidColor];
	  DoSeg[gap, white];
	  ENDLOOP;
	DoSeg[pos2.y - segstart.y, solidColor];
	END;
      steep =>
	BEGIN
        deltaY: ADistance = pos2.y - pos1.y;
	deltaX: ADistance = pos2.x - pos1.x;
	dir: INTEGER = IF deltaY < 0 THEN -1 ELSE 1;
	G: PROC [y: ADistance] RETURNS [x: ADistance] =
	  BEGIN
	  x ← pos1.x +  MulDiv[(y - pos1.y), deltaX, deltaY];
	  END;
	DoSeg: PROC [sl: ADistance, sc: LineColor] =
          BEGIN
	  segstop: APosition;
	  segstop.y ← segstart.y + sl*dir;
	  segstop.x ← G[segstop.y];
	  pproc[
	    pos1: segstart, 
	    pos2: segstop, 
	    class: class, 
	    color: sc, 
	    lWidth: width, 
	    lengthen: 0, 
	    box: box];
	  segstart ← segstop;
	  END;
	dash ← MulDiv[dash, ABS[deltaY], length];
	gap ← MulDiv[gap, ABS[deltaY], length];
	gap ← MAX [gap, ADistanceForDots[1]];
	ends ← (dash + (ABS[pos1.y-pos2.y] - dash - gap) MOD (dash+gap))/2;
	segstart ← pos1;
	DoSeg[ends, solidColor];
	DoSeg[gap, white];
	WHILE (segstart.y + dir*(dash + gap) - pos2.y)*dir < 0 DO
	  DoSeg[dash, solidColor];
	  DoSeg[gap, white];
	  ENDLOOP;
	DoSeg[ABS[pos2.y - segstart.y], solidColor];
	END;
      ENDCASE => -- shallow
	BEGIN
        deltaY: ADistance = pos2.y - pos1.y;
	deltaX: ADistance = pos2.x - pos1.x;
	F: PROC [x: ADistance] RETURNS [y: ADistance] =
	  BEGIN
	  y ← pos1.y + MulDiv[(x - pos1.x), deltaY, deltaX];
	  END;
	DoSeg: PROC [sl: ADistance, sc: LineColor] =
          BEGIN
	  segstop: APosition;
	  segstop.x ← segstart.x + sl;
	  segstop.y ← F[segstop.x];
	  pproc[
	    pos1: segstart, 
	    pos2: segstop, 
	    class: class, 
	    color: sc, 
	    lWidth: width, 
	    lengthen: 0, 
	    box: box];
	  segstart ← segstop;
	  END;
	dash ← MulDiv[dash, deltaX, length];
	gap ← MulDiv[gap, deltaX, length];
	gap ← MAX [gap, ADistanceForDots[1]];
	ends ← (dash + (pos2.x - pos1.x - dash - gap) MOD (dash+gap))/2;
	segstart ← pos1;
	DoSeg[ends, solidColor];
	DoSeg[gap, white];
	WHILE segstart.x + dash + gap < pos2.x DO
	  DoSeg[dash, solidColor];
	  DoSeg[gap, white];
	  ENDLOOP;
	DoSeg[pos2.x - segstart.x, solidColor];
	END;
    IF marksRemoved THEN MarksIn[];
    END;

  DisplaySolidLine: PROC [pos1, pos2: APosition, class: LineClass, color: LineColor, lWidth: LineWidth, lengthen: [0..4], box: POINTER TO ABox] =
    BEGIN
    deltaY, deltaX: LONG INTEGER;
    start, stop, current, leftCorner, rightCorner: Window.Place;
    width: CARDINAL = lWidth * DrawnWidth[state.magnify];
    drawn: BOOLEAN ← FALSE;
    chunkBox: Window.Box;
    F: PROC [x: INTEGER] RETURNS [y: INTEGER] =
      BEGIN
      y ← start.y + Inline.LowHalf[ MulDiv[(x - start.x), deltaY, deltaX]]
      END;
    G: PROC [y: INTEGER] RETURNS [x: INTEGER] =
      BEGIN
      x ← start.x + Inline.LowHalf[ MulDiv[(y - start.y), deltaX, deltaY]]
      END;

    IF color = white THEN RETURN; -- try not painting white part

    leftCorner ← PicturePlace[[box.x1, box.y1]];
    rightCorner ← PicturePlace[[box.x2, box.y2]];
    start ← PicturePlace[pos1];
    stop ← PicturePlace[pos2];
    SELECT class FROM
      horiz => 
	BEGIN
	start.x ← MAX[start.x, leftCorner.x];
	stop.x ← MIN[stop.x, rightCorner.x];
        IF start.x < stop.x THEN Window.DisplayShade[
	  pictureWindow,
	  [start, 
	    [w: stop.x - start.x + lengthen*DrawnWidth[state.magnify], h: width]],
	  GreyColor[color]];
	RETURN
	END;
      vert => 
	BEGIN
	start.y ← MAX[start.y, leftCorner.y];
	stop.y ← MIN[stop.y, rightCorner.y];
        IF start.y < stop.y THEN Window.DisplayShade[
	  pictureWindow,
	  [start, [w: width, h: stop.y - start.y]],
	  GreyColor[color]];
	RETURN
	END;
      ENDCASE;
    deltaY ← stop.y - start.y;
    deltaX ← stop.x - start.x;
    IF class = steep THEN
      BEGIN
      negSlope: BOOLEAN ← FALSE;
      limitY: INTEGER;
      PaintChunk: PROC [Window.Handle] RETURNS [Window.Box, INTEGER] =
        BEGIN
        h: INTEGER;
        DO
          IF negSlope THEN {IF current.y <= limitY THEN EXIT}
          ELSE IF current.y >= limitY THEN EXIT;
	  h ← MAX [ABS[current.y - F[current.x+1]], 1];
          h ← MIN [ABS[limitY - current.y], h];
          IF negSlope THEN
            chunkBox ← [[current.x, current.y-h], [w: width, h: h]]
          ELSE chunkBox ← [current, [w: width, h: h]];
          current.x ← current.x + 1;
          current.y ← current.y + (IF negSlope THEN -h ELSE h);
          RETURN [chunkBox, 0];
          ENDLOOP;
        RETURN [Window.NullBox, 0];
        END;
      IF deltaY < 0 THEN
	{limitY ← MAX[stop.y, leftCorner.y];
	current.y ← MIN[start.y, rightCorner.y];
	negSlope ← TRUE}
      ELSE
	{limitY ← MIN[stop.y, rightCorner.y];
	current.y ← MAX[start.y, leftCorner.y]};
      current.x ← G[current.y];
      Window.Trajectory[
        window: pictureWindow,
        box: WindowBox[box],
        proc: PaintChunk,
	bbop: replace,
        bbsource: gray, -- why can't they agree on spelling?
        grey: GreyColor[color]];
      END
    ELSE -- shallow
      BEGIN
      negSlope: BOOLEAN ← FALSE;
      dy: INTEGER;
      limitX: INTEGER = MIN[stop.x, rightCorner.x];
      PaintChunk: PROC [Window.Handle] RETURNS [Window.Box, INTEGER] =
        BEGIN
        w: INTEGER;
        DO
          IF current.x >= limitX THEN EXIT;
          w ← MAX[G[current.y+dy] - current.x, 1];
          w ← MIN[stop.x - current.x, w];
          IF negSlope THEN
            chunkBox ← [[current.x, current.y-1], [w: w, h: width]]
          ELSE chunkBox ← [current, [w: w, h: width]];
          current.x ← current.x + w;
          current.y ← current.y + dy;
          RETURN [chunkBox, 0];
          ENDLOOP;
        RETURN [Window.NullBox, 0];
        END;
      IF deltaY < 0 THEN {dy ← -1; negSlope ← TRUE} ELSE dy ← 1;
      current.x ← MAX[start.x, leftCorner.x];
      current.y ← F[current.x];
      Window.Trajectory[
        window: pictureWindow,
        box: WindowBox[box],
        proc: PaintChunk,
	bbop: replace,
        bbsource: gray,
        grey: GreyColor[color]];
      END;
    END;

  WindowBox: PROC [box: POINTER TO ABox] RETURNS [Window.Box] =
    BEGIN
    place: Window.Place ← PicturePlace[[box.x1, box.y1]];
    RETURN [[place,
      [w: MAX[DotsForADistance[box.x2-box.x1], 1],
      h: MAX[DotsForADistance[box.y2 - box.y1], 1]]]];
    END;

  pending: Redraw ← NIL;

  CopySelections: PUBLIC PROC [delta: APosition] =
    BEGIN
    originalSource: APosition = Absolute[GetSourcePos[FALSE]];
    CopyLine: LineScan =
      BEGIN
      IF lth.selected THEN
	BEGIN
	new: Redraw = Storage.Node[SIZE[line RedrawObject]];
	pos1: APosition = ptb[lth.p1].pos;
	pos2: APosition = ptb[lth.p2].pos;
	new↑ ← [next: pending, var: line[
	  pos1: [pos1.x + delta.x, pos1.y + delta.y],
	  pos2: [pos2.x + delta.x, pos2.y + delta.y],
	  width: lth.width,
	  texture: lth.texture]];
        lth.selected ← FALSE;
	MaybeDisplayLine[l];
	pending ← new;
	END;
      RETURN[FALSE];
      END;
    CopyLabel: LabelScan =
      BEGIN
      IF lbh.selected THEN
	BEGIN
	new: Redraw = Storage.Node[SIZE[label RedrawObject]];
	pos: APosition = lbh.pos;
	new↑ ← [next: pending, var: label[
	  font: lbh.font, mode: lbh.mode,
	  pos: [pos.x + delta.x, pos.y + delta.y],
	  hti: lbh.hti]];
	pending ← new;
        lbh.selected ← FALSE;
	PaintLabel[lb];
	END;
      RETURN[FALSE];
      END;
    IF BadMove[delta] THEN RETURN;
    [] ← SelectedLines[CopyLine];
    [] ← SelectedLabels[CopyLabel];
    header.selectedLines ← LTNull;
    header.selectedLabels ← LBNull;
    RedrawItems[pending];
    pending ← NIL;
    ASetSourcePos[[x: originalSource.x + delta.x,
      y: originalSource.y + delta.y]];
    ASetDestPos[[x: originalSource.x + 2*delta.x,
      y: originalSource.y + 2*delta.y]];
    END;

  BadMove: PROC [delta: APosition] RETURNS [BOOLEAN] =
    BEGIN
    BadPoint: PointScan =
      BEGIN
      RETURN [pth.selected AND 
	(pth.pos.x + delta.x < 0 OR pth.pos.y + delta.y < 0)];
      END;
    BadLine: LineScan =
      BEGIN
      p1: PTIndex = lth.p1;
      p2: PTIndex = lth.p2;
      RETURN [lth.selected AND
	(ptb[p1].pos.x + delta.x < 0 OR ptb[p1].pos.y + delta.y < 0 OR
	 ptb[p2].pos.x + delta.x < 0 OR ptb[p2].pos.y + delta.y < 0)]
      END;
    BadLabel: LabelScan =
      BEGIN
      RETURN [lbh.selected AND 
	(lbh.pos.x + delta.x < 0 OR lbh.pos.y + delta.y < 0)];
      END;
    IF delta.x > 0 AND delta.y > 0 THEN RETURN[FALSE];
    IF AllPoints[BadPoint] # PTNull THEN GO TO bad;
    IF AllLines[BadLine] # LTNull THEN GO TO bad;
    IF AllLabels[BadLabel] # LBNull THEN GO TO bad;
    RETURN[FALSE];
    EXITS
      bad => {UserTerminal.BlinkDisplay[]; RETURN[TRUE]};
    END;

  MoveSelections: PUBLIC PROC [delta: APosition] =
    BEGIN
    originalSource: APosition = Absolute[GetSourcePos[FALSE]];
    MoveLine: LineScan =
      BEGIN
      IF lth.selected THEN
	BEGIN
	new: Redraw = Storage.Node[SIZE[line RedrawObject]];
	pos1: APosition = ptb[lth.p1].pos;
	pos2: APosition = ptb[lth.p2].pos;
	new↑ ← [next: pending, var: line[
	  pos1: [pos1.x + delta.x, pos1.y + delta.y],
	  pos2: [pos2.x + delta.x, pos2.y + delta.y],
	  width: lth.width,
	  texture: lth.texture]];
	pending ← new;
	DeleteLine[l];
	END;
      RETURN[FALSE];
      END;
    MoveLabel: LabelScan =
      BEGIN
      IF lbh.selected THEN
	BEGIN
	new: Redraw = Storage.Node[SIZE[label RedrawObject]];
	pos: APosition = lbh.pos;
	new↑ ← [next: pending, var: label[
	  font: lbh.font, mode: lbh.mode,
	  pos: [pos.x + delta.x, pos.y + delta.y],
	  hti: lbh.hti]];
	pending ← new;
	DeleteLabel[lb];
	END;
      RETURN[FALSE];
      END;
    MovePoint: PointScan =
      BEGIN
      IF pth.selected THEN
	BEGIN
	pos1: APosition = [x: pth.pos.x + delta.x, y: pth.pos.y + delta.y];
	pBox: Window.Box = BoxForPoint[p];
        StretchLine: LineScan =
	  BEGIN
	  other: PTIndex = IF lth.p1 = p THEN lth.p2 ELSE lth.p1;
	  pos2: APosition ← ptb[other].pos;
          new: Redraw = Storage.Node[SIZE[line RedrawObject]];
	  IF ptb[other].selected THEN
	    {pos2.x ← pos2.x + delta.x; pos2.y ← pos2.y + delta.y};
	  new↑ ← [next: pending, var: line[
	    pos1: pos1, pos2: pos2, width: lth.width, texture: lth.texture]];
	  pending ← new;
	  DeleteLine[l];
	  RETURN[FALSE];
	  END;
	Window.InvalidateBox[pictureWindow, pBox];
	[] ← LinesThru[p, StretchLine];
	END;
      RETURN[FALSE];
      END;
    IF BadMove[delta] THEN RETURN;
    [] ← SelectedLines[MoveLine];
    [] ← SelectedLabels[MoveLabel];
    [] ← SelectedPoints[MovePoint];
    header.selectedLines ← LTNull;
    header.selectedPoints ← PTNull;
    header.selectedLabels ← LBNull;
    Window.ValidateTree[];
    RedrawItems[pending];
    pending ← NIL;
    ASetSourcePos[[x: originalSource.x + delta.x,
      y: originalSource.y + delta.y]];
    ASetDestPos[originalSource];
    END;

  BadRotate: PROC [source, dest: APosition] RETURNS [BOOLEAN] =
    BEGIN
    BadLine: LineScan =
      BEGIN
      p1: PTIndex = lth.p1;
      p2: PTIndex = lth.p2;
      RETURN [lth.selected AND
	(dest.x - (ptb[p1].pos.y - source.y) < 0 OR 
         dest.y + (ptb[p1].pos.x - source.x) < 0 OR
	 dest.x - (ptb[p2].pos.y - source.y) < 0 OR 
         dest.y + (ptb[p2].pos.x - source.x) < 0)]
      END;
    BadLabel: LabelScan =
      BEGIN
      RETURN [lbh.selected AND 
	(dest.x - (lbh.pos.y - source.y) < 0 OR 
         dest.y + (lbh.pos.x - source.x) < 0)];
      END;
    IF SelectedLines[BadLine] # LTNull THEN GO TO bad;
    IF SelectedLabels[BadLabel] # LBNull THEN GO TO bad;
    RETURN[FALSE];
    EXITS
      bad => {UserTerminal.BlinkDisplay[]; RETURN[TRUE]};
    END;

  MoveAndRotate: PUBLIC PROC [source, dest: APosition] =
    BEGIN
    originalSource: APosition = Absolute[GetSourcePos[FALSE]];
    MoveLine: LineScan =
      BEGIN
      IF lth.selected THEN
	BEGIN
	new: Redraw = Storage.Node[SIZE[line RedrawObject]];
	pos1: APosition = ptb[lth.p1].pos;
	pos2: APosition = ptb[lth.p2].pos;
	new↑ ← [next: pending, var: line[
	  pos1: [dest.x - (pos1.y - source.y), dest.y + (pos1.x - source.x)],
	  pos2: [dest.x - (pos2.y - source.y), dest.y + (pos2.x - source.x)],
	  width: lth.width,
	  texture: lth.texture]];
	pending ← new;
	DeleteLine[l];
	END;
      RETURN[FALSE];
      END;
    MoveLabel: LabelScan =
      BEGIN
      IF lbh.selected THEN
	BEGIN
	new: Redraw = Storage.Node[SIZE[label RedrawObject]];
	pos: APosition = lbh.pos;
	new↑ ← [next: pending, var: label[
	  font: lbh.font, mode: lbh.mode,
	  pos: [dest.x - (pos.y - source.y), dest.y + (pos.x - source.x)],
	  hti: lbh.hti]];
	pending ← new;
	DeleteLabel[lb];
	END;
      RETURN[FALSE];
      END;
    IF BadRotate[source, dest] THEN RETURN;
    [] ← SelectedLines[MoveLine];
    [] ← SelectedLabels[MoveLabel];
    header.selectedLines ← LTNull;
    header.selectedLabels ← LBNull;
    ClearSelections[];
    Window.ValidateTree[];
    RedrawItems[pending];
    pending ← NIL;
    END;

  RedrawItems: PUBLIC PROC [rd: Redraw] =
    BEGIN
    WHILE rd # NIL DO
      this: Redraw = rd;
      WITH this SELECT FROM
	line => 
	  BEGIN
	  l: LTIndex = InsertLine[
	    InsertPoint[pos1], InsertPoint[pos2], width, texture];
	  ltb[l].selNext ← header.selectedLines;
	  header.selectedLines ← l;
	  MaybeDisplayLine[l];
	  END;
	label => 
	  BEGIN
	  lb: LBIndex = 
	    InsertLabel[pos: pos, hti: hti, font: font, mode: mode];
	  lbb[lb].selNext ← header.selectedLabels;
	  header.selectedLabels ← lb;
	  PaintLabel[lb];
	  END;
        ENDCASE;
      rd ← this.next;
      ENDLOOP;
    END;

  END.