home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pmos2002.zip / SRC / TILES.MOD < prev    next >
Text File  |  1996-11-08  |  55KB  |  1,520 lines

  1. IMPLEMENTATION MODULE Tiles;
  2.  
  3.         (********************************************************)
  4.         (*                                                      *)
  5.         (*          Support module for screen graphics          *)
  6.         (*                                                      *)
  7.         (*  Programmer:         P. Moylan                       *)
  8.         (*  Last edited:        8 November 1996                 *)
  9.         (*  Status:             Working                         *)
  10.         (*      The logic for forcing a restart when checking   *)
  11.         (*       for things to merge could be made less         *)
  12.         (*       conservative - is this worth the extra effort? *)
  13.         (*      Still have to check for missing features.       *)
  14.         (*      Text implemented inefficiently.                 *)
  15.         (*                                                      *)
  16.         (*      Now adding a scrolling mechanism.  Procedure    *)
  17.         (*      ScrollContents is basically working but is      *)
  18.         (*      still missing the mechanism to fix up           *)
  19.         (*      scrolled points.                                *)
  20.         (*                                                      *)
  21.         (********************************************************)
  22.  
  23. FROM ScreenGeometry IMPORT
  24.     (* type *)  Point, Rectangle,
  25.     (* proc *)  Inside;
  26.  
  27. FROM Graphics IMPORT
  28.     (* type *)  ColourType,
  29.     (* proc *)  Fill, PlotDot, PlotLine, PlotRectangle, ClippedLine,
  30.                 ClippedString, ClippedUpString, ACopy;
  31.  
  32. FROM Queues IMPORT
  33.     (* type *)  Queue,
  34.     (* proc *)  CreateQueue, AddToQueue, TakeFromQueue, Empty, DestroyQueue;
  35.  
  36. FROM Storage IMPORT
  37.     (* proc *)  ALLOCATE, DEALLOCATE;
  38.  
  39. FROM TaskControl IMPORT
  40.     (* type *)  Lock,
  41.     (* proc *)  CreateLock, Obtain, Release;
  42.  
  43. FROM Keyboard IMPORT    (* for testing *)
  44.     (* proc *)  InKey;
  45.  
  46. (************************************************************************)
  47.  
  48. CONST testing = FALSE;          (* If TRUE, we display TileSlots *)
  49.  
  50. TYPE
  51.     <* m2extensions+ *>
  52.     TextPointer = POINTER TO ARRAY OF CHAR;
  53.     <* m2extensions- *>
  54.  
  55.     (* A PointList is a set of Points.  *)
  56.  
  57.     PointList = POINTER TO PointListRecord;
  58.     PointListRecord =
  59.                     RECORD
  60.                         next: PointList;
  61.                         datum: Point;
  62.                         colour: ColourType;
  63.                     END (*RECORD*);
  64.  
  65.     (* A LineList is a set of Lines defined by their endpoints. *)
  66.  
  67.     LineList = POINTER TO LineListRecord;
  68.     LineListRecord =
  69.                     RECORD
  70.                         next: LineList;
  71.                         end1, end2: Point;
  72.                         colour: ColourType;
  73.                     END (*RECORD*);
  74.  
  75.     (* A StringList is a set of text strings.   *)
  76.  
  77.     StringList = POINTER TO StringListRecord;
  78.     StringListRecord =
  79.                     RECORD
  80.                         next: StringList;
  81.                         location: Point;
  82.                         colour: ColourType;
  83.                         length: CARDINAL;
  84.                         textptr: TextPointer;
  85.                     END (*RECORD*);
  86.  
  87.     (* A TileSlot is defined later - see the TileSlotInfo declaration.  *)
  88.  
  89.     TileSlot = POINTER TO TileSlotInfo;
  90.  
  91.     (* A Tile is an object holding graphic data to be displayed on the  *)
  92.     (* screen.  Each Tile matches exactly one TileSlot, but multiple    *)
  93.     (* Tiles can occupy the same TileSlot.  The field "under" shows the *)
  94.     (* next Tile which occupies the same TileSlot, "next" shows the     *)
  95.     (* next Tile in the same TileSet, and "set" is the TileSet itself.  *)
  96.  
  97.     Tile = POINTER TO TileRecord;
  98.     TileRecord =    RECORD
  99.                         under: Tile;
  100.                         next: Tile;
  101.                         set: TileSet;
  102.                         slot: TileSlot;
  103.                         points: PointList;
  104.                     END (*RECORD*);
  105.  
  106.     (* A TileSet - which is the only data type of interest to clients   *)
  107.     (* of this module - is a set of Tiles, plus a set of lines and text *)
  108.     (* strings to be plotted.  The background field gives the common    *)
  109.     (* background colour for all the tiles in the set.  The retain      *)
  110.     (* field specifies whether to save newly plotted data for future    *)
  111.     (* refreshing.                                                      *)
  112.  
  113.     TileSet = POINTER TO TileSetRecord;
  114.     TileSetRecord = RECORD
  115.                         head, tail: Tile;
  116.                         background: ColourType;
  117.                         retain: BOOLEAN;
  118.                         lines: LineList;
  119.                         strings, upstrings: StringList;
  120.                     END (*RECORD*);
  121.  
  122.     (* A TileStack is a stack of tiles - each belonging to a distinct   *)
  123.     (* TileSet - occupying the same position on the screen.             *)
  124.  
  125.     TileStack = Tile;
  126.  
  127.     (* A TileSlot represents a rectangular region on the screen, which  *)
  128.     (* can contain zero or more Tiles.  This module maintains a master  *)
  129.     (* list of TileSlots, showing how the screen is currently tiled.    *)
  130.     (* Initially there is just one TileSlot covering the whole screen.  *)
  131.     (* Each time CreateTileSet is called there is a likelihood that     *)
  132.     (* existing TileSlots will have to be broken up, so the master list *)
  133.     (* of TileSlots changes with time.  When a TileSlot is broken up,   *)
  134.     (* the corresponding Tiles must of course also be broken up.        *)
  135.     (* The nextslot field in a TileSlot record points to the next       *)
  136.     (* TileSlot in the master list, and the stacktop field points to a  *)
  137.     (* a stack of tiles which occupy this TileSlot.  The mark field is  *)
  138.     (* normally FALSE; we set it to indicate a candidate for merging    *)
  139.     (* with adjacent slots.                                             *)
  140.     (* So far I haven't given enough thought as to the most desirable   *)
  141.     (* ordering of the TileSlot list.  It should possibly be ordered    *)
  142.     (* to make searching faster.                                        *)
  143.  
  144.     TileSlotInfo =  RECORD
  145.                         shape: Rectangle;
  146.                         nextslot: TileSlot;
  147.                         stacktop: TileStack;
  148.                         mark: BOOLEAN;
  149.                     END (*RECORD*);
  150.  
  151. (************************************************************************)
  152.  
  153. VAR
  154.     (* SlotListHead is the head of the master list of TileSlots.        *)
  155.  
  156.     SlotListHead: TileSlot;
  157.  
  158.     (* MainLock protects all entry points to this module.  Because of   *)
  159.     (* the heavily interlocked nature of the data structures maintained *)
  160.     (* by this module, and in particular because operations on one      *)
  161.     (* TileSet can affect the internal structure of other TileSets,     *)
  162.     (* there is little point in trying for finer granularity in the     *)
  163.     (* critical section protection.                                     *)
  164.  
  165.     MainLock: Lock;
  166.  
  167. (************************************************************************)
  168.  
  169. (*
  170. PROCEDURE DebugPause (message: ARRAY OF CHAR);
  171.  
  172.     (* For testing - can remove from production version.        *)
  173.  
  174.     VAR dummy: CHAR;
  175.  
  176.     BEGIN
  177.         GlassTTY.SetCursor (24, 0);
  178.         GlassTTY.WriteString (message);
  179.         dummy := InKey();
  180.     END DebugPause;
  181. *)
  182.  
  183. (************************************************************************)
  184. (*        OPERATIONS ON TYPES PointList, LineList, AND StringList       *)
  185. (************************************************************************)
  186.  
  187. PROCEDURE DiscardPointList (VAR (*INOUT*) PL: PointList);
  188.  
  189.     (* Destroys a PointList.    *)
  190.  
  191.     VAR following: PointList;
  192.  
  193.     BEGIN
  194.         WHILE PL <> NIL DO
  195.             following := PL^.next;
  196.             DISPOSE (PL);
  197.             PL := following;
  198.         END (*WHILE*);
  199.     END DiscardPointList;
  200.  
  201. (************************************************************************)
  202.  
  203. PROCEDURE DiscardLineList (VAR (*INOUT*) LL: LineList);
  204.  
  205.     (* Destroys a LineList.     *)
  206.  
  207.     VAR following: LineList;
  208.  
  209.     BEGIN
  210.         WHILE LL <> NIL DO
  211.             following := LL^.next;
  212.             DISPOSE (LL);
  213.             LL := following;
  214.         END (*WHILE*);
  215.     END DiscardLineList;
  216.  
  217. (************************************************************************)
  218.  
  219. PROCEDURE PlotLineListClipped (LL: LineList;  R: Rectangle);
  220.  
  221.     (* Plots all lines in LL, clipping them on the display such that    *)
  222.     (* only the part inside R is shown.                                 *)
  223.  
  224.     BEGIN
  225.         WHILE LL <> NIL DO
  226.             WITH LL^ DO
  227.                 WITH R DO
  228.                     ClippedLine (end1.x, end1.y, end2.x, end2.y, colour,
  229.                                                 left, right, bottom, top);
  230.                 END (*WITH*);
  231.             END (*WITH*);
  232.             LL := LL^.next;
  233.         END (*WHILE*);
  234.     END PlotLineListClipped;
  235.  
  236. (************************************************************************)
  237.  
  238. PROCEDURE DiscardStringList (VAR (*INOUT*) SL: StringList);
  239.  
  240.     (* Destroys a StringList.   *)
  241.  
  242.     VAR following: StringList;
  243.  
  244.     BEGIN
  245.         WHILE SL <> NIL DO
  246.             following := SL^.next;
  247.             <* storage+ *>
  248.             DISPOSE (SL^.textptr);
  249.             <* storage- *>
  250.             DISPOSE (SL);
  251.             SL := following;
  252.         END (*WHILE*);
  253.     END DiscardStringList;
  254.  
  255. (************************************************************************)
  256.  
  257. PROCEDURE PlotStringListClipped (SL: StringList;  R: Rectangle);
  258.  
  259.     (* Plots all strings in SL, clipping them on the display such that  *)
  260.     (* only the part inside R is shown.                                 *)
  261.  
  262.     BEGIN
  263.         WHILE SL <> NIL DO
  264.             WITH SL^ DO
  265.                 WITH R DO
  266.                     ClippedString (textptr^, location.x, location.y,
  267.                                                 length, colour,
  268.                                                 left, right, bottom, top);
  269.                 END (*WITH*);
  270.             END (*WITH*);
  271.             SL := SL^.next;
  272.         END (*WHILE*);
  273.     END PlotStringListClipped;
  274.  
  275. (************************************************************************)
  276.  
  277. PROCEDURE PlotUpStringListClipped (USL: StringList;  R: Rectangle);
  278.  
  279.     (* Plots all strings in USL, clipping them on the display such      *)
  280.     (* that only the part inside R is shown.                            *)
  281.  
  282.     BEGIN
  283.         WHILE USL <> NIL DO
  284.             WITH USL^ DO
  285.                 WITH R DO
  286.                     ClippedUpString (textptr^, location.x, location.y,
  287.                                                 length, colour,
  288.                                                 left, right, bottom, top);
  289.                 END (*WITH*);
  290.             END (*WITH*);
  291.             USL := USL^.next;
  292.         END (*WHILE*);
  293.     END PlotUpStringListClipped;
  294.  
  295. (************************************************************************)
  296. (*                      OPERATIONS ON TYPE Tile                         *)
  297. (************************************************************************)
  298.  
  299. PROCEDURE Unlink (tile: Tile);
  300.  
  301.     (* Removes the tile from its stack, without otherwise changing it.  *)
  302.  
  303.     VAR slot: TileSlot;  current, above: Tile;
  304.  
  305.     BEGIN
  306.         slot := tile^.slot;
  307.         current := slot^.stacktop;  above := NIL;
  308.         WHILE current <> tile DO
  309.             above := current;  current := current^.under;
  310.         END (*WHILE*);
  311.         IF above = NIL THEN
  312.             slot^.stacktop := current^.under;
  313.         ELSE
  314.             above^.under := current^.under;
  315.         END (*IF*);
  316.         current^.under := NIL;
  317.     END Unlink;
  318.  
  319. (************************************************************************)
  320.  
  321. PROCEDURE Display (slot: TileSlot);
  322.  
  323.     (* Displays the contents of the tile on top of the stack for the    *)
  324.     (* given slot.  If the slot is empty, clears the screen area        *)
  325.     (* described by slot^.shape.                                        *)
  326.  
  327.     (* Remark: should perhaps include a parameter to say whether the    *)
  328.     (* entire TileSet is being redisplayed - in which case it would be  *)
  329.     (* more efficient to skip displaying lines and strings in this      *)
  330.     (* procedure, and let the caller display the unclipped versions.    *)
  331.  
  332.     VAR PL: PointList;  toptile: Tile;
  333.         background: ColourType;  R: Rectangle;
  334.  
  335.     BEGIN
  336.         WITH slot^ DO
  337.             R := shape;
  338.             toptile := stacktop;
  339.             IF toptile = NIL THEN background := 0;
  340.             ELSE background := toptile^.set^.background;
  341.             END (*IF*);
  342.         END (*WITH*);
  343.  
  344.         (* Fill in the background *)
  345.  
  346.         WITH R DO
  347.             Fill (left, bottom, right, top, background);
  348.         END (*WITH*);
  349.  
  350.         (* Is there a tile present?     *)
  351.  
  352.         IF toptile = NIL THEN
  353.             RETURN;
  354.         END (*IF*);
  355.  
  356.         (* Yes, display its contents.   *)
  357.  
  358.         PL := toptile^.points;
  359.         WHILE PL <> NIL DO
  360.             WITH PL^ DO
  361.                 WITH datum DO
  362.                     PlotDot (x, y, colour);
  363.                 END (*WITH*);
  364.             END (*WITH*);
  365.             PL := PL^.next;
  366.         END (*WHILE*);
  367.  
  368.         WITH toptile^.set^ DO
  369.             PlotLineListClipped (lines, R);
  370.             PlotStringListClipped (strings, R);
  371.             PlotUpStringListClipped (upstrings, R);
  372.         END (*WITH*);
  373.  
  374.     END Display;
  375.  
  376. (************************************************************************)
  377.  
  378. PROCEDURE CreateTile (VAR (*OUT*) T: Tile;  Slot: TileSlot);
  379.  
  380.     (* Creates a new tile T on top of the stack for the given slot.     *)
  381.     (* The tile is not attached to any set.                             *)
  382.  
  383.     BEGIN
  384.         NEW (T);
  385.         WITH T^ DO
  386.             under := Slot^.stacktop;  next := NIL;  set := NIL;
  387.             slot := Slot;  points := NIL;
  388.         END (*WITH*);
  389.         Slot^.stacktop := T;
  390.     END CreateTile;
  391.  
  392. (************************************************************************)
  393.  
  394. PROCEDURE DiscardTile (tile: Tile);
  395.  
  396.     (* Destroys a tile.  This includes removing the tile from its stack *)
  397.     (* and updating the screen display if necessary.                    *)
  398.  
  399.     VAR wasontop: BOOLEAN;
  400.  
  401.     BEGIN
  402.         WITH tile^ DO
  403.             wasontop := tile = slot^.stacktop;
  404.             Unlink (tile);
  405.             IF wasontop THEN Display (slot) END(*IF*);
  406.             DiscardPointList (points);
  407.         END (*WITH*);
  408.         DISPOSE (tile);
  409.     END DiscardTile;
  410.  
  411. (************************************************************************)
  412.  
  413. PROCEDURE ClearTileData (tile: Tile);
  414.  
  415.     (* Removes all points from the tile, and displays the blanked       *)
  416.     (* region if the tile is on top of its stack.                       *)
  417.  
  418.     BEGIN
  419.         WITH tile^ DO
  420.             DiscardPointList (points);
  421.             IF tile = slot^.stacktop THEN
  422.                 Display (slot);
  423.             END (*IF*);
  424.         END (*WITH*);
  425.     END ClearTileData;
  426.  
  427. (************************************************************************)
  428.  
  429. PROCEDURE TileSetMemory (T: TileSet;  memory: BOOLEAN);
  430.  
  431.     (* Specifying a FALSE value for the memory parameter means that     *)
  432.     (* subsequent data sent to this TileSet will be written to the      *)
  433.     (* screen but not remembered.  This saves time and memory, the only *)
  434.     (* penalty being that data covered by an overlapping TileSet will   *)
  435.     (* be lost.  Specifying TRUE restores the default condition, where  *)
  436.     (* all data are retained for refreshing the screen when necessary.  *)
  437.  
  438.     BEGIN
  439.         T^.retain := memory;
  440.     END TileSetMemory;
  441.  
  442. (************************************************************************)
  443.  
  444. PROCEDURE PutTileOnTop (tile: Tile);
  445.  
  446.     (* Puts a tile on the top of its stack and displays it.  (Does      *)
  447.     (* nothing if the tile is already on top of its stack.)             *)
  448.  
  449.     VAR slot: TileSlot;
  450.  
  451.     BEGIN
  452.         slot := tile^.slot;
  453.         IF slot^.stacktop <> tile THEN
  454.             Unlink (tile);
  455.             tile^.under := slot^.stacktop;
  456.             slot^.stacktop := tile;
  457.             Display (slot);
  458.         END (*IF*);
  459.     END PutTileOnTop;
  460.  
  461. (************************************************************************)
  462.  
  463. PROCEDURE FindTile (TS: TileSet;  p: Point): Tile;
  464.  
  465.     (* Returns the tile in TS whose TileSlot contains p.  Also puts     *)
  466.     (* this tile on the top of its stack and displays it.  NOTE: we     *)
  467.     (* assume that the caller has checked that p lies in the region     *)
  468.     (* covered by TS.                                                   *)
  469.  
  470.     VAR current: Tile;  x, y: CARDINAL;
  471.  
  472.     BEGIN
  473.         x := p.x;  y := p.y;
  474.         current := TS^.head;
  475.         LOOP
  476.             IF Inside (x, y, current^.slot^.shape) THEN
  477.                 PutTileOnTop (current);
  478.                 RETURN current;
  479.             END (*IF*);
  480.             current := current^.next;
  481.         END (*LOOP*);
  482.     END FindTile;
  483.  
  484. (************************************************************************)
  485.  
  486. PROCEDURE AddPointToTile (p: Point;  colour: ColourType;  T: Tile);
  487.  
  488.     (* Appends p to the list of points in T.    *)
  489.  
  490.     VAR PL: PointList;
  491.  
  492.     BEGIN
  493.         NEW (PL);
  494.         WITH PL^ DO
  495.             next := T^.points;  datum := p;
  496.         END (*WITH*);
  497.         PL^.colour := colour;
  498.         T^.points := PL;
  499.     END AddPointToTile;
  500.  
  501. (************************************************************************)
  502.  
  503. PROCEDURE SplitTile (VAR (*INOUT*) T: Tile;  VAR (*OUT*) T2: Tile;
  504.                         S2: TileSlot;  bound: CARDINAL;  Xsplit: BOOLEAN);
  505.  
  506.     (* Creates a new tile T2, to fit into slot S2 and in the same       *)
  507.     (* TileSet as T; and moves some of the data from T to T2.  The data *)
  508.     (* moved are those with horizontal coordinate >= bound in the case  *)
  509.     (* Xsplit = TRUE, or those with vertical coordinate >= bound in the *)
  510.     (* case Xsplit = FALSE.  T2 is left on the top of the S2 stack.     *)
  511.     (* Remark: the order of the points in their PointList is altered    *)
  512.     (* as they are shifted, but this shouldn't matter since the list    *)
  513.     (* is not being used as an ordered set.                             *)
  514.  
  515.     VAR pcurrent, pprevious, pfollowing: PointList;
  516.         test: CARDINAL;
  517.  
  518.     BEGIN
  519.         CreateTile (T2, S2);  T2^.set := T^.set;
  520.         T2^.next := T^.next;  T^.next := T2;
  521.  
  522.         (* Work through the points in T^.points, shifting them to       *)
  523.         (* T2^.points as necessary.                                     *)
  524.  
  525.         pcurrent := T^.points;  pprevious := NIL;
  526.         WHILE pcurrent <> NIL DO
  527.             pfollowing := pcurrent^.next;
  528.             WITH pcurrent^.datum DO
  529.                 IF Xsplit THEN test := x ELSE test := y END(*IF*);
  530.             END (*WITH*);
  531.             IF test >= bound THEN
  532.  
  533.                 (* Remove the point from T^.points.     *)
  534.  
  535.                 IF pprevious = NIL THEN T^.points := pfollowing
  536.                 ELSE pprevious^.next := pfollowing;
  537.                 END (*IF*);
  538.  
  539.                 (* Put the point into T2^.points.       *)
  540.  
  541.                 pcurrent^.next := T2^.points;  T2^.points := pcurrent;
  542.  
  543.             ELSE
  544.                 pprevious := pcurrent;
  545.             END (*IF*);
  546.             pcurrent := pfollowing;
  547.  
  548.         END (*WHILE*);
  549.  
  550.     END SplitTile;
  551.  
  552. (************************************************************************)
  553.  
  554. PROCEDURE MergeTiles (VAR (*INOUT*) T1, T2: Tile);
  555.  
  556.     (* The opposite operation to SplitTile: all data from T2 are moved  *)
  557.     (* into T1, and T2 is destroyed.                                    *)
  558.  
  559.     VAR previous, current: Tile;  plast: PointList;
  560.  
  561.     BEGIN
  562.         (* Find the predecessor of T2 in its TileSet. *)
  563.  
  564.         previous := NIL;  current := T2^.set^.head;
  565.         WHILE current <> T2 DO
  566.             previous := current;  current := current^.next;
  567.         END (*WHILE*);
  568.  
  569.         (* Remove T2 from the set. *)
  570.  
  571.         IF previous = NIL THEN T2^.set^.head := T2^.next
  572.         ELSE previous^.next := T2^.next
  573.         END (*IF*);
  574.         IF T2 = T2^.set^.tail THEN
  575.             T2^.set^.tail := previous;
  576.         END (*IF*);
  577.  
  578.         (* Move T2's PointList into T1. *)
  579.  
  580.         plast := T1^.points;
  581.         IF plast = NIL THEN
  582.             T1^.points := T2^.points;
  583.         ELSE
  584.             WHILE plast^.next <> NIL DO
  585.                 plast := plast^.next;
  586.             END (*WHILE*);
  587.             plast^.next := T2^.points;
  588.         END (*IF*);
  589.  
  590.         (* All done, discard T2. *)
  591.  
  592.         DISPOSE (T2);
  593.  
  594.     END MergeTiles;
  595.  
  596. (************************************************************************)
  597.  
  598. PROCEDURE MatchingStack (stack1, stack2: TileStack): BOOLEAN;
  599.  
  600.     (* The input parameters each point to the top of a stack of tiles.  *)
  601.     (* We return TRUE if the two stacks are equal in the following      *)
  602.     (* sense: for each tile in stack1, there is a corresponding tile in *)
  603.     (* stack2 (and vice versa) belonging to the same TileSet.           *)
  604.  
  605.     BEGIN
  606.         (* Simplification: if the tiles on the two stacks really do     *)
  607.         (* belong to the same TileSet, then they have gone through the  *)
  608.         (* same history, and therefore should be stacked in the same    *)
  609.         (* order.                                                       *)
  610.  
  611.         LOOP
  612.             IF stack1 = NIL THEN
  613.                 RETURN (stack2 = NIL);
  614.             END (*IF*);
  615.             IF stack2 = NIL THEN
  616.                 RETURN FALSE;
  617.             END (*IF*);
  618.             IF stack1^.set <> stack2^.set THEN
  619.                 RETURN FALSE;
  620.             END (*IF*);
  621.             stack1 := stack1^.under;
  622.             stack2 := stack2^.under;
  623.         END (*LOOP*);
  624.  
  625.     END MatchingStack;
  626.  
  627. (************************************************************************)
  628.  
  629. PROCEDURE MergeStacks (VAR (*OUT*) stack1: TileStack;  stack2: TileStack);
  630.  
  631.     (* The input parameters each point to the top of a stack of tiles.  *)
  632.     (* On exit all data from tiles in stack2 have been moved into the   *)
  633.     (* corresponding tiles in stack1, and the tiles in stack2 have been *)
  634.     (* destroyed.                                                       *)
  635.  
  636.     VAR T1, T2: Tile;
  637.  
  638.     BEGIN
  639.         T1 := stack1;
  640.         WHILE stack2 <> NIL DO
  641.             T2 := stack2;  stack2 := stack2^.under;
  642.             MergeTiles (T1, T2);
  643.             T1 := T1^.under;
  644.         END (*WHILE*);
  645.     END MergeStacks;
  646.  
  647. (************************************************************************)
  648. (*                      OPERATIONS ON TYPE TileSlot                     *)
  649. (************************************************************************)
  650.  
  651. PROCEDURE DisplayAllSlots (S: TileSlot;  colour: ColourType);
  652.  
  653.     (* For testing: draws the boundaries of all TileSlots, pauses a     *)
  654.     (* while, and then erases the drawing.  Because this procedure is   *)
  655.     (* used only during module testing, we're not particularly fussy    *)
  656.     (* about leaving the screen picture in a completely clean state.    *)
  657.     (* The parameters specify the slot we're currently working on - we  *)
  658.     (* display S in colour "colour".                                    *)
  659.  
  660.     VAR dummy: CHAR;
  661.  
  662.     PROCEDURE DrawSlotOutlines (colour: ColourType);
  663.  
  664.         VAR current: TileSlot;
  665.  
  666.         BEGIN
  667.             current := SlotListHead;
  668.             WHILE current <> NIL DO
  669.                 PlotRectangle (current^.shape, colour);
  670.                 current := current^.nextslot;
  671.             END (*WHILE*);
  672.         END DrawSlotOutlines;
  673.  
  674.     (********************************************************************)
  675.  
  676.     BEGIN
  677.         DrawSlotOutlines (1);
  678.         IF S <> NIL THEN
  679.             PlotRectangle (S^.shape, colour);
  680.         END (*IF*);
  681.         Release (MainLock);
  682.         dummy := InKey();
  683.         Obtain (MainLock);
  684.         DrawSlotOutlines (0);
  685.     END DisplayAllSlots;
  686.  
  687. (************************************************************************)
  688.  
  689. PROCEDURE FindSlot (x, y: CARDINAL): TileSlot;
  690.  
  691.     (* Returns a slot containing the point (x,y).       *)
  692.  
  693.     VAR p: TileSlot;
  694.  
  695.     BEGIN
  696.         p := SlotListHead;
  697.         LOOP
  698.             IF Inside (x, y, p^.shape) THEN
  699.                 RETURN p;
  700.             END (*IF*);
  701.             p := p^.nextslot;
  702.         END (*LOOP*);
  703.     END FindSlot;
  704.  
  705. (************************************************************************)
  706.  
  707. PROCEDURE SplitSlot (VAR (*INOUT*) S: TileSlot;  bound: CARDINAL;
  708.                                                         Xsplit: BOOLEAN);
  709.  
  710.     (* Breaks S into two adjacent tile slots - side by side in the case *)
  711.     (* Xsplit = TRUE, or one on top of the other when Xsplit = FALSE.   *)
  712.     (* On return S is the leftmost or bottommost, as appropriate, and   *)
  713.     (* S^.nextslot is the other.                                        *)
  714.  
  715.     VAR S2: TileSlot;  T, T2, bottom: Tile;
  716.  
  717.     BEGIN
  718.         NEW (S2);
  719.         S2^ := S^;
  720.         S^.nextslot := S2;
  721.         IF Xsplit THEN
  722.             S^.shape.right := bound - 1;
  723.             S2^.shape.left := bound;
  724.         ELSE
  725.             S^.shape.top := bound - 1;
  726.             S2^.shape.bottom := bound;
  727.         END (*IF*);
  728.         S2^.stacktop := NIL;
  729.  
  730.         (* This completes the splitting of the TileSlot itself.  Now we *)
  731.         (* must also split every Tile in the stack for the original     *)
  732.         (* TileSlot, and construct a stack for the S2 TileSlot.  Note   *)
  733.         (* that, because a newly created tile goes on top of the stack  *)
  734.         (* for its slot, we need to shuffle stack elements to avoid a   *)
  735.         (* situation where the stack constructed for S2 would be upside *)
  736.         (* down relative to the stack for S.                            *)
  737.  
  738.         T := S^.stacktop;
  739.         IF T <> NIL THEN
  740.             SplitTile (T, bottom, S2, bound, Xsplit);
  741.             LOOP
  742.                 T := T^.under;
  743.                 IF T = NIL THEN EXIT(*LOOP*) END(*IF*);
  744.                 SplitTile (T, T2, S2, bound, Xsplit);
  745.  
  746.                 (* Move the newly created tile T2 and move it from the  *)
  747.                 (* top to the bottom of S2's stack.  This ensures that  *)
  748.                 (* the new stack is built in the same order as the      *)
  749.                 (* original stack.                                      *)
  750.  
  751.                 S2^.stacktop := T2^.under;  T2^.under := NIL;
  752.                 bottom^.under := T2;  bottom := T2;
  753.             END (*LOOP*);
  754.         END (*IF*);
  755.  
  756.         IF testing THEN
  757.             DisplayAllSlots (S, 4);
  758.         END (*IF*);
  759.  
  760.     END SplitSlot;
  761.  
  762. (************************************************************************)
  763. (*                      COMBINING ADJACENT SLOTS                        *)
  764. (************************************************************************)
  765.  
  766. PROCEDURE Join (VAR (*INOUT*) S1, S2: TileSlot;  preS2: TileSlot;
  767.                                                 newshape: Rectangle);
  768.  
  769.     (* On entry, S1 and S2 have already been found to be suitable for   *)
  770.     (* combining, preS2 is the predecessor of S2 in the master list of  *)
  771.     (* TileSlots, and newshape is the shape of the union of S1 and S2.  *)
  772.     (* On exit, S1 is the union, the old S2 has been destroyed, and     *)
  773.     (* S2 is the successor of the old S2 in the master list.            *)
  774.  
  775.     VAR following: TileSlot;
  776.  
  777.     BEGIN
  778.         (* Remove S2 from the master list of TileSlots. *)
  779.  
  780.         following := S2^.nextslot;
  781.         IF preS2 = NIL THEN SlotListHead := following
  782.         ELSE preS2^.nextslot := following
  783.         END (*IF*);
  784.  
  785.         (* Combine corresponding tiles in S1 and S2,    *)
  786.         (* leaving the result in S1.                    *)
  787.  
  788.         S1^.shape := newshape;
  789.         MergeStacks (S1^.stacktop, S2^.stacktop);
  790.         DISPOSE (S2);
  791.         S2 := following;
  792.  
  793.         IF testing THEN
  794.             DisplayAllSlots (S1, 3);
  795.         END (*IF*);
  796.  
  797.     END Join;
  798.  
  799. (************************************************************************)
  800.  
  801. PROCEDURE UpDownMatch (R1, R2: Rectangle;
  802.                                 VAR (*OUT*) union: Rectangle): BOOLEAN;
  803.  
  804.     (* If R1 and R2 are vertically adjacent rectangles, returns TRUE    *)
  805.     (* and sets "union" to be the combined rectangle.  Otherwise        *)
  806.     (* returns FALSE, and the "union" result is meaningless.            *)
  807.  
  808.     BEGIN
  809.         union := R1;
  810.         IF (R1.left = R2.left) AND (R1.right = R2.right) THEN
  811.             (* Possible above/below adjacency *)
  812.             IF R2.bottom = R1.top + 1 THEN
  813.                 union.top := R2.top;
  814.                 RETURN TRUE;
  815.             ELSIF R1.bottom = R2.top + 1 THEN
  816.                 union.bottom := R2.bottom;
  817.                 RETURN TRUE;
  818.             ELSE
  819.                 RETURN FALSE;
  820.             END (*IF*);
  821.         ELSE
  822.             RETURN FALSE;
  823.         END (*IF*);
  824.     END UpDownMatch;
  825.  
  826. (************************************************************************)
  827.  
  828. PROCEDURE SideMatch (VAR (*INOUT*) S1, S2, preS2: TileSlot;
  829.                         VAR (*OUT*) union: Rectangle;
  830.                         VAR (*OUT*) HaveSplit: BOOLEAN): BOOLEAN;
  831.  
  832.     (* Like UpDownMatch, but checks for left/right adjacency.  However  *)
  833.     (* we're more generous in this case about the meaning of "adjacent" *)
  834.     (* since we're prepared to split S1 and/or S2 to make the heights   *)
  835.     (* match.  (This is why the first two parameters have to specify    *)
  836.     (* TileSlots rather than simply their shapes.)  If a split occurs   *)
  837.     (* the pieces split off have their "mark" fields set - since those  *)
  838.     (* pieces will have to be re-evaluated for possible further         *)
  839.     (* combinations - and we return with "HaveSplit" set to TRUE.       *)
  840.     (* Exception: if we can guarantee that the slots needing to be      *)
  841.     (* rechecked come after S1 in the master list of slots, then we     *)
  842.     (* don't set HaveSplit.                                             *)
  843.     (* Note: parameter preS2 is the predecessor of S2 in the master     *)
  844.     (* list of tile slots; it's updated appropriately if we have to     *)
  845.     (* modify that list.                                                *)
  846.  
  847.     VAR R1, R2: Rectangle;  adjacent: BOOLEAN;
  848.  
  849.     BEGIN
  850.         HaveSplit := FALSE;
  851.         R1 := S1^.shape;  R2 := S2^.shape;
  852.         union := R1;
  853.         IF (R1.bottom > R2.top) OR (R1.top < R2.bottom) THEN
  854.             adjacent := FALSE;
  855.         ELSIF R2.left = R1.right + 1 THEN
  856.             union.right := R2.right;
  857.             adjacent := TRUE;
  858.         ELSIF R1.left = R2.right + 1 THEN
  859.             union.left := R2.left;
  860.             adjacent := TRUE;
  861.         ELSE
  862.             adjacent := FALSE;
  863.         END (*IF*);
  864.  
  865.         IF adjacent THEN
  866.  
  867.             (* The two rectangles are adjacent, but we haven't yet      *)
  868.             (* checked their heights.  Perform one or two splits, as    *)
  869.             (* necessary, to get the heights to line up.                *)
  870.  
  871.             IF R1.top > R2.top THEN
  872.                 S1^.mark := TRUE;
  873.                 SplitSlot (S1, R2.top+1, FALSE);
  874.                 union.top := R2.top;
  875.                 IF preS2 = S1 THEN
  876.                     preS2 := S1^.nextslot;
  877.                 END (*IF*);
  878.             ELSIF R1.top < R2.top THEN
  879.                 S2^.mark := TRUE;
  880.                 SplitSlot (S2, R1.top+1, FALSE);
  881.                 HaveSplit := TRUE;
  882.             END (*IF*);
  883.  
  884.             IF R1.bottom < R2.bottom THEN
  885.                 S1^.mark := TRUE;
  886.                 SplitSlot (S1, R2.bottom, FALSE);
  887.                 HaveSplit := TRUE;
  888.                 IF preS2 = S1 THEN
  889.                     preS2 := S1^.nextslot;
  890.                 END (*IF*);
  891.                 S1 := S1^.nextslot;
  892.                 union.bottom := R2.bottom;
  893.             ELSIF R1.bottom > R2.bottom THEN
  894.                 S2^.mark := TRUE;
  895.                 SplitSlot (S2, R1.bottom, FALSE);
  896.                 preS2 := S2;  S2 := S2^.nextslot;
  897.                 HaveSplit := TRUE;
  898.             END (*IF*);
  899.  
  900.         END (*IF*);
  901.  
  902.         RETURN adjacent;
  903.  
  904.     END SideMatch;
  905.  
  906. (************************************************************************)
  907.  
  908. PROCEDURE MergeWithNeighbours (VAR (*INOUT*) S: TileSlot): BOOLEAN;
  909.  
  910.     (* Combines S with its neighbours if this turns out to be possible. *)
  911.     (* The condition for combining slots is that the tiles in the two   *)
  912.     (* slots belong to the same TileSets, and that the union of their   *)
  913.     (* shapes is again a rectangular region.  A function result of      *)
  914.     (* TRUE indicates that all recombinations (if any) involving S      *)
  915.     (* have already been taken care of by this procedure, and further   *)
  916.     (* that no slots preceding S in the master list of TileSlots have   *)
  917.     (* been modified.  A result of FALSE indicates that this procedure  *)
  918.     (* may have created the conditions for further recombinations, i.e. *)
  919.     (* that the caller needs to rescan the master list of TileSlots.    *)
  920.  
  921.     VAR NoChange, HaveSplit, NoRescanNeeded: BOOLEAN;
  922.         previous, S2: TileSlot;  union: Rectangle;
  923.  
  924.     BEGIN
  925.         NoRescanNeeded := TRUE;
  926.         REPEAT
  927.             NoChange := TRUE;
  928.  
  929.             (* Search for a candidate S2 to be merged with S.   *)
  930.  
  931.             previous := NIL;  S2 := SlotListHead;
  932.  
  933.             WHILE S2 <> NIL DO
  934.  
  935.                 (* Check for adjacency.  In the above/below case we     *)
  936.                 (* simply merge.  In the left/right case we merge when  *)
  937.                 (* an exact match is found, but we also allow for the   *)
  938.                 (* possibility of a left or right neighbour which is    *)
  939.                 (* taller or shorter than S.  In the latter case, we    *)
  940.                 (* perform a split followed by a join.                  *)
  941.  
  942.                 IF (S2 <> S) AND MatchingStack (S^.stacktop, S2^.stacktop) AND
  943.                         (UpDownMatch (S^.shape, S2^.shape, union) OR
  944.                             SideMatch (S, S2, previous, union, HaveSplit)) THEN
  945.                     Join (S, S2, previous, union);
  946.                     NoChange := FALSE;
  947.                     IF HaveSplit THEN
  948.                         NoRescanNeeded := FALSE;  S2 := NIL;
  949.                     END (*IF*);
  950.                 ELSE
  951.                     previous := S2;
  952.                     S2 := S2^.nextslot;
  953.                 END (*IF*);
  954.  
  955.             END (*WHILE S2 <> NIL*);
  956.  
  957.         UNTIL NoChange;
  958.  
  959.         S^.mark := FALSE;
  960.         RETURN NoRescanNeeded;
  961.  
  962.     END MergeWithNeighbours;
  963.  
  964. (************************************************************************)
  965.  
  966. PROCEDURE RecombineSlots;
  967.  
  968.     (* Goes through the master list of slots, checking all slots which  *)
  969.     (* have their "mark" field set, and combining adjacent slots where  *)
  970.     (* possible.  Note: in the process of doing this we sometimes have  *)
  971.     (* to mark previously unmarked slots, so we have to keep looping    *)
  972.     (* until we are sure we have cleared all marks.                     *)
  973.  
  974.     VAR slot: TileSlot;  scanned: BOOLEAN;
  975.  
  976.     BEGIN
  977.         REPEAT
  978.             scanned := TRUE;
  979.             slot := SlotListHead;
  980.             WHILE slot <> NIL DO
  981.                 IF slot^.mark THEN
  982.                     scanned := scanned AND MergeWithNeighbours (slot);
  983.                 END (*IF*);
  984.                 slot := slot^.nextslot;
  985.             END (*WHILE*);
  986.         UNTIL scanned;
  987.     END RecombineSlots;
  988.  
  989. (************************************************************************)
  990. (*                      OPERATIONS ON TYPE TileSet                      *)
  991. (************************************************************************)
  992.  
  993. PROCEDURE CreateEmptyTileSet (VAR (*OUT*) TS: TileSet;  colour: ColourType);
  994.  
  995.     (* Creates a TileSet containing no tiles.   *)
  996.  
  997.     BEGIN
  998.         NEW (TS);
  999.         WITH TS^ DO
  1000.             head := NIL;  tail := NIL;  lines := NIL;  strings := NIL;
  1001.             upstrings := NIL;  background := colour;  retain := TRUE;
  1002.         END (*WITH*);
  1003.     END CreateEmptyTileSet;
  1004.  
  1005. (************************************************************************)
  1006.  
  1007. PROCEDURE AddToTileSet (VAR (*INOUT*) TS: TileSet;  S: TileSlot);
  1008.  
  1009.     (* Creates a new tile in slot S, adds it to set TS, and displays it.*)
  1010.  
  1011.     VAR p: Tile;
  1012.  
  1013.     BEGIN
  1014.         CreateTile (p, S);  p^.set := TS;
  1015.         Display (S);
  1016.         WITH TS^ DO
  1017.             IF tail = NIL THEN head := p
  1018.             ELSE tail^.next := p
  1019.             END (*IF*);
  1020.             tail := p;
  1021.         END (*WITH*);
  1022.     END AddToTileSet;
  1023.  
  1024. (************************************************************************)
  1025.  
  1026. PROCEDURE PutTileSetOnTop (TS: TileSet);
  1027.  
  1028.     (* Ensures that TS is fully displayed on the screen.        *)
  1029.  
  1030.     VAR tile: Tile;
  1031.  
  1032.     BEGIN
  1033.         tile := TS^.head;
  1034.         WHILE tile <> NIL DO
  1035.             PutTileOnTop (tile);
  1036.             tile := tile^.next;
  1037.         END (*WHILE*);
  1038.     END PutTileSetOnTop;
  1039.  
  1040. (************************************************************************)
  1041.  
  1042. PROCEDURE Redraw (TS: TileSet);
  1043.  
  1044.     (* Refreshes the visible parts of TS on the screen.  This procedure *)
  1045.     (* is for use in the case where the TileSet contents have been      *)
  1046.     (* changed and what is already on the screen is obsolete.  (For the *)
  1047.     (* case where we are simply adding to the existing contents there   *)
  1048.     (* are faster methods than calling this procedure.)                 *)
  1049.  
  1050.     VAR tile: Tile;
  1051.  
  1052.     BEGIN
  1053.         tile := TS^.head;
  1054.         WHILE tile <> NIL DO
  1055.             WITH tile^ DO
  1056.                 IF tile = slot^.stacktop THEN
  1057.                     Display (slot);
  1058.                 END (*IF*);
  1059.             END (*WITH*);
  1060.             tile := tile^.next;
  1061.         END (*WHILE*);
  1062.     END Redraw;
  1063.  
  1064. (************************************************************************)
  1065. (*              THE MAIN EXTERNALLY CALLABLE PROCEDURES                 *)
  1066. (************************************************************************)
  1067.  
  1068. PROCEDURE CreateTileSet (border: Rectangle; background: ColourType): TileSet;
  1069.  
  1070.     (* Creates a TileSet which covers the given rectangular region.     *)
  1071.     (* The second parameter specifies the background colour.            *)
  1072.     (* This will usually require breaking up tiles of previously        *)
  1073.     (* created TileSets, but since the caller does not have access to   *)
  1074.     (* the internal structure of a TileSet this restructuring is        *)
  1075.     (* transparent to the caller.                                       *)
  1076.  
  1077.     VAR result: TileSet;  S: TileSlot;
  1078.         StillToHandle: Queue;
  1079.         RLptr: POINTER TO Rectangle;
  1080.  
  1081.     BEGIN
  1082.         Obtain (MainLock);
  1083.  
  1084.         (* Start by creating an empty tile set, and a list of           *)
  1085.         (* rectangles still to be dealt with.                           *)
  1086.  
  1087.         CreateEmptyTileSet (result, background);
  1088.         CreateQueue (StillToHandle);
  1089.         NEW (RLptr);  RLptr^ := border;
  1090.         AddToQueue (StillToHandle, RLptr);
  1091.  
  1092.         (* The following loop breaks the rectangle into tiles by        *)
  1093.         (* gradually breaking up the rectangle.  The loop body has two  *)
  1094.         (* phases: (a) find a tile slot which matches or is contained   *)
  1095.         (* in the desired rectangle; (b) break off any parts of the     *)
  1096.         (* rectangle which do not fit into that slot, to be dealt with  *)
  1097.         (* in subsequent passes through the loop.                       *)
  1098.  
  1099.         REPEAT
  1100.             RLptr := TakeFromQueue (StillToHandle);
  1101.             border := RLptr^;  DISPOSE (RLptr);
  1102.  
  1103.             (* Find a tile slot containing the bottom left corner.      *)
  1104.  
  1105.             S := FindSlot (border.left, border.bottom);
  1106.  
  1107.             (* Break up S, if necessary, so that no part of S lies      *)
  1108.             (* outside the rectangular region.  The order of these      *)
  1109.             (* operations is significant: we work on the bottom and top *)
  1110.             (* edges before looking at the left and right edges, to     *)
  1111.             (* ensure that horizontal cuts are done before vertical     *)
  1112.             (* cuts.                                                    *)
  1113.  
  1114.             IF S^.shape.bottom < border.bottom THEN
  1115.                 SplitSlot (S, border.bottom, FALSE);
  1116.                 S := S^.nextslot;
  1117.             END (*IF*);
  1118.             IF S^.shape.top > border.top THEN
  1119.                 SplitSlot (S, border.top+1, FALSE);
  1120.             END (*IF*);
  1121.  
  1122.             IF S^.shape.left < border.left THEN
  1123.                 SplitSlot (S, border.left, TRUE);
  1124.                 S := S^.nextslot;
  1125.             END (*IF*);
  1126.             IF S^.shape.right > border.right THEN
  1127.                 SplitSlot (S, border.right+1, TRUE);
  1128.             END (*IF*);
  1129.  
  1130.             (* We have now split TileSlots to the point where the lower *)
  1131.             (* left corner of S is aligned with the lower left corner   *)
  1132.             (* of border, and the whole of S either matches or is       *)
  1133.             (* contained within the desired rectangle; so we have a     *)
  1134.             (* slot we can use in the final result.  All that remains   *)
  1135.             (* is to separate out those parts of the rectangle which    *)
  1136.             (* don't fit inside S, and which will be dealt with in      *)
  1137.             (* subsequent passes through the loop.                      *)
  1138.  
  1139.             IF border.right > S^.shape.right THEN
  1140.                 NEW (RLptr);
  1141.                 RLptr^ := border;  RLptr^.left := S^.shape.right + 1;
  1142.                 border.right := S^.shape.right;
  1143.                 AddToQueue (StillToHandle, RLptr);
  1144.             END (*IF*);
  1145.             IF border.top > S^.shape.top THEN
  1146.                 NEW (RLptr);
  1147.                 RLptr^ := border;  RLptr^.bottom := S^.shape.top + 1;
  1148.                 border.top := S^.shape.top;
  1149.                 AddToQueue (StillToHandle, RLptr);
  1150.             END (*IF*);
  1151.  
  1152.             AddToTileSet (result, S);
  1153.  
  1154.         UNTIL Empty (StillToHandle);
  1155.  
  1156.         DestroyQueue (StillToHandle);
  1157.         Release (MainLock);
  1158.         RETURN result;
  1159.  
  1160.     END CreateTileSet;
  1161.  
  1162. (************************************************************************)
  1163.  
  1164. PROCEDURE DiscardTileSet (VAR (*INOUT*) TS: TileSet);
  1165.  
  1166.     (* Destroys TileSet TS.     *)
  1167.  
  1168.     VAR current, following: Tile;
  1169.  
  1170.     BEGIN
  1171.         Obtain (MainLock);
  1172.         DiscardLineList (TS^.lines);
  1173.         DiscardStringList (TS^.strings);
  1174.         DiscardStringList (TS^.upstrings);
  1175.         current := TS^.head;
  1176.         WHILE current <> NIL DO
  1177.             current^.slot^.mark := TRUE;
  1178.             following := current^.next;
  1179.             DiscardTile (current);
  1180.             current := following;
  1181.         END (*WHILE*);
  1182.         DISPOSE (TS);
  1183.         RecombineSlots;
  1184.         Release (MainLock);
  1185.     END DiscardTileSet;
  1186.  
  1187. (************************************************************************)
  1188.  
  1189. PROCEDURE ClearTileSet (T: TileSet);
  1190.  
  1191.     (* Removes all points, lines, and text from T, and re-displays      *)
  1192.     (* the visible parts of T.                                          *)
  1193.  
  1194.     VAR current: Tile;
  1195.  
  1196.     BEGIN
  1197.         Obtain (MainLock);
  1198.         DiscardLineList (T^.lines);
  1199.         DiscardStringList (T^.strings);
  1200.         DiscardStringList (T^.upstrings);
  1201.         current := T^.head;
  1202.         WHILE current <> NIL DO
  1203.             ClearTileData (current);
  1204.             current := current^.next;
  1205.         END (*WHILE*);
  1206.         Release (MainLock);
  1207.     END ClearTileSet;
  1208.  
  1209. (************************************************************************)
  1210.  
  1211. PROCEDURE AddPoint (T: TileSet;  p: Point;  colour: ColourType);
  1212.  
  1213.     (* Adds a new point to TileSet T, and displays it on the screen.    *)
  1214.  
  1215.     VAR tile: Tile;
  1216.  
  1217.     BEGIN
  1218.         Obtain (MainLock);
  1219.         tile := FindTile (T, p);
  1220.         IF T^.retain THEN
  1221.             AddPointToTile (p, colour, tile);
  1222.         END (*IF*);
  1223.         PlotDot (p.x, p.y, colour);
  1224.         Release (MainLock);
  1225.     END AddPoint;
  1226.  
  1227. (************************************************************************)
  1228.  
  1229. PROCEDURE AddLine (T: TileSet;  start, finish: Point;  colour: ColourType);
  1230.  
  1231.     (* Adds a new line to TileSet T, and displays it on the screen.     *)
  1232.  
  1233.     VAR LL: LineList;
  1234.  
  1235.     BEGIN
  1236.         Obtain (MainLock);
  1237.         PutTileSetOnTop (T);
  1238.         IF T^.retain THEN
  1239.             NEW (LL);
  1240.             WITH LL^ DO
  1241.                 next := T^.lines;  end1 := start;  end2 := finish;
  1242.             END (*WITH*);
  1243.             LL^.colour := colour;
  1244.             T^.lines := LL;
  1245.         END (*IF*);
  1246.         PlotLine (start.x, start.y, finish.x, finish.y, colour);
  1247.         Release (MainLock);
  1248.     END AddLine;
  1249.  
  1250. (************************************************************************)
  1251.  
  1252. PROCEDURE AddRectangle (T: TileSet;  R: Rectangle;  colour: ColourType);
  1253.  
  1254.     (* Draws a rectangular shape.  A shorthand for four AddLine calls.  *)
  1255.  
  1256.     VAR start, end: Point;
  1257.  
  1258.     BEGIN
  1259.         WITH R DO
  1260.             WITH start DO
  1261.                 x := left;  y := bottom;
  1262.             END (*WITH*);
  1263.             WITH end DO
  1264.                 x := right;  y := bottom;
  1265.             END (*WITH*);
  1266.         END (*WITH*);
  1267.         AddLine (T, start, end, colour);
  1268.         end.x := R.left;  end.y := R.top;
  1269.         AddLine (T, start, end, colour);
  1270.         start.x := R.right;  start.y := R.top;
  1271.         AddLine (T, start, end, colour);
  1272.         end.x := R.right;  end.y := R.bottom;
  1273.         AddLine (T, start, end, colour);
  1274.     END AddRectangle;
  1275.  
  1276. (************************************************************************)
  1277.  
  1278. PROCEDURE AddString (T: TileSet;  place: Point;
  1279.                         VAR (*IN*) text: ARRAY OF CHAR;
  1280.                         count: CARDINAL;  colour: ColourType;  R: Rectangle);
  1281.  
  1282.     (* Adds a string of count characters to tileset T, and displays it. *)
  1283.     (* Points outside rectangle R are not displayed.                    *)
  1284.  
  1285.     VAR SL: StringList;  j: CARDINAL;
  1286.  
  1287.     BEGIN
  1288.         Obtain (MainLock);
  1289.         PutTileSetOnTop (T);
  1290.         IF T^.retain THEN
  1291.             NEW (SL);
  1292.             WITH SL^ DO
  1293.                 next := T^.strings;  location := place;
  1294.                 length := count;
  1295.                 <* storage+ *>
  1296.                 NEW (textptr, count);
  1297.                 <* storage- *>
  1298.                 FOR j := 0 TO count-1 DO
  1299.                     textptr^[j] := text[j];
  1300.                 END (*FOR*);
  1301.             END (*WITH*);
  1302.             SL^.colour := colour;
  1303.             T^.strings := SL;
  1304.         END (*IF*);
  1305.         WITH R DO
  1306.             ClippedString (text, place.x, place.y, count, colour,
  1307.                                                 left, right, bottom, top);
  1308.         END (*WITH*);
  1309.         Release (MainLock);
  1310.     END AddString;
  1311.  
  1312. (************************************************************************)
  1313.  
  1314. PROCEDURE AddRotatedString (T: TileSet;  place: Point;
  1315.                         VAR (*IN*) text: ARRAY OF CHAR;
  1316.                         count: CARDINAL;  colour: ColourType;  R: Rectangle);
  1317.  
  1318.     (* Like AddString, but writes in the +Y direction.  *)
  1319.  
  1320.     VAR USL: StringList;  j: CARDINAL;
  1321.  
  1322.     BEGIN
  1323.         Obtain (MainLock);
  1324.         PutTileSetOnTop (T);
  1325.         IF T^.retain THEN
  1326.             NEW (USL);
  1327.             WITH USL^ DO
  1328.                 next := T^.upstrings;  location := place;
  1329.                 length := count;
  1330.                 <* storage+ *>
  1331.                 NEW (textptr, count);
  1332.                 <* storage- *>
  1333.                 FOR j := 0 TO count-1 DO
  1334.                     textptr^[j] := text[j];
  1335.                 END (*FOR*);
  1336.             END (*WITH*);
  1337.             USL^.colour := colour;
  1338.             T^.upstrings := USL;
  1339.         END (*IF*);
  1340.         WITH R DO
  1341.             ClippedUpString (text, place.x, place.y, count, colour,
  1342.                                                 left, right, bottom, top);
  1343.         END (*WITH*);
  1344.         Release (MainLock);
  1345.  
  1346.     END AddRotatedString;
  1347.  
  1348. (************************************************************************)
  1349.  
  1350. PROCEDURE ShiftTextUp (VAR (*INOUT*) List: StringList;
  1351.                                 amount: CARDINAL;  limit: INTEGER);
  1352.  
  1353.     (* Moves all character strings up by "amount" rows, discarding what *)
  1354.     (* falls above the limit.                                           *)
  1355.  
  1356.     (* Layout fault: this procedure should be moved higher up in the    *)
  1357.     (* module after I have completed implemented scrolling.             *)
  1358.  
  1359.     VAR previous, current, following: StringList;
  1360.         felloff: BOOLEAN;
  1361.  
  1362.     BEGIN
  1363.         previous := NIL;  current := List;
  1364.         WHILE current <> NIL DO
  1365.             following := current^.next;
  1366.             WITH current^.location DO
  1367.                 INC (y, amount);
  1368.                 felloff := y > limit
  1369.             END (*WITH*);
  1370.             IF felloff THEN
  1371.                 IF previous = NIL THEN List := following
  1372.                 ELSE previous^.next := following;
  1373.                 END (*IF*);
  1374.                 <* storage+ *>
  1375.                 DISPOSE (current^.textptr);
  1376.                 <* storage- *>
  1377.                 DISPOSE (current);
  1378.             ELSE
  1379.                 previous := current;
  1380.             END (*IF*);
  1381.             current := following;
  1382.         END (*WHILE*);
  1383.     END ShiftTextUp;
  1384.  
  1385. (************************************************************************)
  1386.  
  1387. PROCEDURE ShiftLinesUp (List: LineList;  amount: CARDINAL;  limit: INTEGER);
  1388.  
  1389.     (* Moves all lines up by "amount" rows, discarding what falls above *)
  1390.     (* the limit.                                                       *)
  1391.  
  1392.     (* Layout fault: this procedure should be moved higher up in the    *)
  1393.     (* module after I have completed implemented scrolling.             *)
  1394.  
  1395.     VAR previous, current, following: LineList;
  1396.         felloff: BOOLEAN;
  1397.  
  1398.     BEGIN
  1399.         previous := NIL;  current := List;
  1400.         WHILE current <> NIL DO
  1401.             following := current^.next;
  1402.             WITH current^.end1 DO
  1403.                 INC (y, amount);
  1404.                 felloff := y > limit;
  1405.             END (*WITH*);
  1406.             WITH current^.end2 DO
  1407.                 INC (y, amount);
  1408.                 felloff := felloff AND (y > limit);
  1409.             END (*WITH*);
  1410.             IF felloff THEN
  1411.                 IF previous = NIL THEN List := following
  1412.                 ELSE previous^.next := following;
  1413.                 END (*IF*);
  1414.                 DISPOSE (current);
  1415.             ELSE
  1416.                 previous := current;
  1417.             END (*IF*);
  1418.             current := following;
  1419.         END (*WHILE*);
  1420.     END ShiftLinesUp;
  1421.  
  1422. (************************************************************************)
  1423.  
  1424. PROCEDURE ScrollContents (TS: TileSet;  amount: INTEGER;  R: Rectangle);
  1425.  
  1426.     (* Moves all data within R up by "amount" rows, discarding what     *)
  1427.     (* falls outside the rectangle.                                     *)
  1428.  
  1429.     BEGIN
  1430.         Obtain (MainLock);
  1431.         PutTileSetOnTop (TS);
  1432.  
  1433.         WITH R DO
  1434.  
  1435.             (* Physically move the data on the screen. *)
  1436.  
  1437.             ACopy (left, top-amount, right-left+1,
  1438.                                 top-amount-bottom+1, 0, amount);
  1439.  
  1440.             (* Clear the vacated section at the bottom. *)
  1441.  
  1442.             Fill (left, bottom, right, bottom+amount-1,
  1443.                                                 TS^.background);
  1444.  
  1445.         END (*WITH*);
  1446.  
  1447.         (* Modify our records of where everything is. *)
  1448.         (* The shifting of points is not yet properly implemented. *)
  1449. (*
  1450.         ShiftPointsUp (TS, amount, R.top);
  1451. *)
  1452.         ShiftLinesUp (TS^.lines, amount, R.top);
  1453.         ShiftTextUp (TS^.strings, amount, R.top);
  1454.         ShiftTextUp (TS^.upstrings, amount, R.top);
  1455.         Release (MainLock);
  1456.  
  1457.     END ScrollContents;
  1458.  
  1459. (************************************************************************)
  1460. (*                      CLEANUP ON TERMINATION                          *)
  1461. (************************************************************************)
  1462.  
  1463. PROCEDURE Cleanup;
  1464.  
  1465.     (* Tidies up all the leftover data on program termination. *)
  1466.  
  1467.     VAR current: TileSlot;
  1468.  
  1469.     BEGIN
  1470.         Obtain (MainLock);
  1471.         WHILE SlotListHead <> NIL DO
  1472.             IF SlotListHead^.stacktop = NIL THEN
  1473.                 current := SlotListHead;
  1474.                 SlotListHead := SlotListHead^.nextslot;
  1475.                 DISPOSE (current);
  1476.             ELSE
  1477.                 DiscardTileSet (SlotListHead^.stacktop^.set);
  1478.             END (*IF*);
  1479.         END (*WHILE*);
  1480.         Release (MainLock);
  1481.     END Cleanup;
  1482.  
  1483. (************************************************************************)
  1484. (*                      MODULE INITIALISATION                           *)
  1485. (************************************************************************)
  1486.  
  1487. PROCEDURE SetUpInitialTileSlot;
  1488.  
  1489.     (* Creates the master list of TileSlots.  Since we don't yet know   *)
  1490.     (* what video mode will be used, we assume a very large screen.     *)
  1491.     (* The fact that the initial tile is certainly too large is not a   *)
  1492.     (* problem, since it will be split as new TileSets are created,     *)
  1493.     (* and the overhead of holding TileSlots for the unusable parts is  *)
  1494.     (* minor.                                                           *)
  1495.  
  1496.     CONST Large = MAX(INTEGER)-1;
  1497.  
  1498.     BEGIN
  1499.         Obtain (MainLock);
  1500.         NEW (SlotListHead);
  1501.         WITH SlotListHead^ DO
  1502.             WITH shape DO
  1503.                 left := 0;  bottom := 0;
  1504.                 top := Large;  right := Large;
  1505.             END (*WITH*);
  1506.             nextslot := NIL;  stacktop := NIL;  mark := FALSE;
  1507.         END (*WITH*);
  1508.         Release (MainLock);
  1509.     END SetUpInitialTileSlot;
  1510.  
  1511. (************************************************************************)
  1512.  
  1513. BEGIN
  1514.     CreateLock (MainLock);
  1515.     SetUpInitialTileSlot;
  1516. FINALLY
  1517.     Cleanup;
  1518. END Tiles.
  1519.  
  1520.