-- ALELines.mesa -- Edited by Sweet, September 12, 1980 12:01 AM DIRECTORY ALEOps, InlineDefs, Storage, Table, UserTerminal, Window; ALELines: PROGRAM IMPORTS ALEOps, InlineDefs, Storage, Table, UserTerminal, Window EXPORTS ALEOps = BEGIN OPEN ALEOps; header: PUBLIC ALEHeader ← [0, 0, 0, 0, 0, 0, 0, LTNull, LBNull, PTNull, LTNull, LBNull]; 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; 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]]; END; InitLines: PUBLIC PROC = BEGIN weights: ARRAY [0..nTables) OF CARDINAL ← [10, 10, 10, 10, 10, 10, 10]; p: POINTER; header ← [0, 0, 0, 0, 0, 0, 0, LTNull, LBNull, PTNull, LTNull, LBNull]; tableSpace ← p ← Storage.Pages[60]; hArray ← DESCRIPTOR[NIL, 0]; vArray ← DESCRIPTOR[NIL, 0]; lastH ← lastV ← -1; Table.Create[[LOOPHOLE[p], 60*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 ← [0, 0, 0, 0, 0, 0, 0, LTNull, LBNull, PTNull, LTNull, LBNull] END; lastHY: ADistance; FindHList: 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; 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; 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: INTEGER ← 0; min: INTEGER ← LAST[INTEGER]; 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: INTEGER ← 0; min: INTEGER ← LAST[INTEGER]; 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: 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; 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; AllPoints: PUBLIC PROC [action: PointScan] RETURNS [p: PTIndex] = BEGIN header.pointTableSize ← Table.Bounds[ptType].size; FOR p ← FIRST[PTIndex], p + SIZE[Point] WHILE LOOPHOLE[p, CARDINAL] < header.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 next: PTIndex; FOR p ← header.selectedPoints, next WHILE p # PTNull DO next ← ptb[p].selNext; IF action[p, @ptb[p]] THEN RETURN; ENDLOOP; END; AllLines: PUBLIC PROC [action: LineScan] RETURNS [l: LTIndex] = BEGIN header.lineTableSize ← Table.Bounds[ltType].size; FOR l ← FIRST[LTIndex], l + SIZE[Line] WHILE LOOPHOLE[l, CARDINAL] < header.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: PUBLIC 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: PUBLIC 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[magnify]*w-1]}; DLinesInABox: PUBLIC PROC [box: POINTER TO ABox, action: LineScan, completely: BOOLEAN ← FALSE] RETURNS [l: LTIndex] = BEGIN 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 aPlace: APosition = APosForPlace[box.place]; aBox ← [ x1: aPlace.x, x2: aPlace.x + ADistanceForDots[box.dims.w], y1: aPlace.y, y2: aPlace.y + ADistanceForDots[box.dims.h]]; 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; MaybeDisplayLine: 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; 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: INTEGER ← ADistanceForDots[2 * DrawnWidth[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: INTEGER ← 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 + INTEGER[InlineDefs.LowHalf[ (LONG[pos.x - pos1.x]*LONG[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 + INTEGER[InlineDefs.LowHalf[ (LONG[pos1.y - pos.y]*LONG[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]; 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; ASetSourcePos[ptb[p].pos]; RETURN END; l ← CloseLine[pos]; IF l # LTNull THEN {ltb[l].selected ← TRUE; MaybeDisplayLine[l]; ltb[l].selNext ← header.selectedLines; header.selectedLines ← l; ASetSourcePos[ptb[ltb[l].p1].pos]; RETURN}; lb ← CloseLabel[pos]; IF lb # LBNull THEN {lbb[lb].selected ← TRUE; PaintLabel[lb]; lbb[lb].selNext ← header.selectedLabels; header.selectedLabels ← lb; ASetSourcePos[lbb[lb].pos]; RETURN}; 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; 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[INTEGER], LAST[INTEGER]]; 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[INTEGER], LAST[INTEGER]] 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]}; 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; 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], currentWidth, 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; FreeLine: PROC [l: LTIndex] = BEGIN ltb[LOOPHOLE[l, FNIndex]] ← [next: header.freeLine]; header.freeLine ← l; 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; 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; DrawnWidth: ARRAY [0..4] OF CARDINAL = [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[magnify]; w: CARDINAL ← DotsForADistance[ABS[pos1.x-pos2.x]]; h: CARDINAL ← DotsForADistance[ABS[pos1.y-pos2.y]]; IF ltb[l].class >= steep THEN w ← w + width ELSE h ← h + width; IF ltb[l].class = horiz THEN w ← w + ShouldLengthen[l] * DrawnWidth[magnify]; RETURN [[PicturePlace[[x: x1, y: y1]], [w: w, h: h]]]; END; DeleteLine: 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; DisplayLine: PUBLIC PROC [l: LTIndex, box: Window.Box] = BEGIN 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; solidColor: LineColor = IF ltb[l].selected THEN grey ELSE black; length, ends: ADistance; SELECT class FROM horiz => length ← pos2.x - pos1.x; vert => length ← pos2.y - pos1.y; ENDCASE => BEGIN dx: CARDINAL = pos2.x - pos1.x; dy: CARDINAL = ABS[pos2.y - pos1.y]; length ← Sqrt[ InlineDefs.LongMult[dx,dx] + InlineDefs.LongMult[dy,dy]]; END; IF tex = solid OR length < dash+4*gap THEN {DisplaySolidLine[pos1, pos2, class, solidColor, width, @box]; RETURN}; END; LineColor: TYPE = {white, grey, black}; GreyColor: ARRAY LineColor OF Window.GreyArray = [ [0, 0, 0, 0], [125252B,52525B,125252B, 52525B], [177777B, 177777B, 177777B, 177777B]]; DisplaySolidLine: PROC [pos1, pos2: APosition, class: LineClass, color: LineColor, lWidth: LineWidth, box: POINTER TO Window.Box] = BEGIN deltaY, deltaX: LONG INTEGER; start, stop, current: Window.Place; width: CARDINAL = lWidth * DrawnWidth[magnify]; drawn: BOOLEAN ← FALSE; chunkBox: Window.Box; start ← PicturePlace[pos1]; stop ← PicturePlace[pos2]; SELECT class FROM horiz => BEGIN Window.DisplayShade[ pictureWindow, [start, [w: stop.x - start.x, h: width]], GreyColor[color]]; RETURN END; vert => BEGIN Window.DisplayShade[ pictureWindow, [start, [w: width, h: stop.y - start.y]], GreyColor[color]]; RETURN END; ENDCASE; MarksOut[]; current ← start; deltaY ← stop.y - start.y; deltaX ← stop.x - start.x; IF class = steep THEN BEGIN negSlope: BOOLEAN ← FALSE; PaintChunk: PROC [Window.Handle] RETURNS [Window.Box, INTEGER] = BEGIN longH: LONG INTEGER; h: INTEGER; DO IF negSlope THEN {IF current.y <= stop.y THEN EXIT} ELSE IF current.y >= stop.y THEN EXIT; longH ← ABS[start.y - current.y + ((current.x - start.x + 1)*deltaY)/deltaX]; h ← MAX[INTEGER[InlineDefs.LowHalf[longH]], 1]; h ← MIN [ABS[stop.y - 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); IF Disjoint[box, @chunkBox] THEN {IF drawn THEN RETURN [Window.NullBox, 0]} ELSE BEGIN drawn ← TRUE; RETURN [chunkBox, 0]; END; ENDLOOP; RETURN [Window.NullBox, 0]; END; IF deltaY < 0 THEN negSlope ← TRUE; Window.Trajectory[ window: pictureWindow, box: 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; PaintChunk: PROC [Window.Handle] RETURNS [Window.Box, INTEGER] = BEGIN longW: LONG INTEGER; w: INTEGER; DO IF current.x >= stop.x THEN EXIT; longW ← start.x - current.x + ((current.y - start.y + dy)*deltaX)/deltaY; w ← MAX[INTEGER[InlineDefs.LowHalf[longW]], 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; IF Disjoint[box, @chunkBox] THEN {IF drawn THEN RETURN [Window.NullBox, 0]} ELSE BEGIN drawn ← TRUE; RETURN [chunkBox, 0]; END; ENDLOOP; RETURN [Window.NullBox, 0]; END; IF deltaY < 0 THEN {dy ← -1; negSlope ← TRUE} ELSE dy ← 1; Window.Trajectory[ window: pictureWindow, box: box↑, proc: PaintChunk, bbop: replace, bbsource: gray, grey: GreyColor[color]]; END; MarksIn[]; 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; RedrawItems: 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.