home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pmos2002.zip / SRC / multiscr.mod < prev    next >
Text File  |  1998-01-23  |  24KB  |  647 lines

  1. IMPLEMENTATION MODULE MultiScreen;
  2.  
  3.         (****************************************************************)
  4.         (*                                                              *)
  5.         (*  This module allows the creation of multiple virtual screens *)
  6.         (*  - complete screens, not just windows - with two keyboard    *)
  7.         (*      "hot keys" used to navigate around the screens.         *)
  8.         (*                                                              *)
  9.         (*  We support a two-level hierarchy: a virtual screen is a     *)
  10.         (*  collection of windows, and each virtual screen is a member  *)
  11.         (*  of a group.  (In addition, there is a pseudo-group made up  *)
  12.         (*  of all windows which are not associated with any virtual    *)
  13.         (*  screen, to handle the "normal output" which bypasses this   *)
  14.         (*  module.)  One hot key cycles through the groups, and the    *)
  15.         (*  second cycles through the virtual screens of the currently  *)
  16.         (*  active group.                                               *)
  17.         (*                                                              *)
  18.         (*  In the present version, the client module has to call       *)
  19.         (*  EnableHotKeys to define the two hot keys.                   *)
  20.         (*                                                              *)
  21.         (*  Programmer:         P. Moylan                               *)
  22.         (*  Last edited:        23 January 1998                         *)
  23.         (*  Status:             Mostly working                          *)
  24.         (*      Still don't properly support duplicating the same       *)
  25.         (*      window on multiple virtual screens.  At this stage      *)
  26.         (*      I'm not convinced that this would be worth the trouble. *)
  27.         (*                                                              *)
  28.         (****************************************************************)
  29.  
  30. FROM Storage IMPORT
  31.     (* proc *)  ALLOCATE, DEALLOCATE;
  32.  
  33. FROM TaskControl IMPORT
  34.     (* proc *)  CreateTask;
  35.  
  36. FROM Semaphores IMPORT
  37.     (* type *)  Semaphore,
  38.     (* proc *)  CreateSemaphore, Wait, Signal;
  39.  
  40. FROM Keyboard IMPORT
  41.     (* proc *)  HotKey;
  42.  
  43. FROM Windows IMPORT
  44.     (* type *)  Window, DisplayPage,
  45.     (* proc *)  Hide, PutOnTop, PutOnPage, SetActivePage, InstallCloseHandler;
  46.  
  47. (************************************************************************)
  48.  
  49. TYPE
  50.     (* A WindowRecord identifies one window. *)
  51.  
  52.     WRpointer = POINTER TO WindowRecord;
  53.     WindowRecord =  RECORD
  54.                         next: WRpointer;
  55.                         window: Window;
  56.                     END (*RECORD*);
  57.  
  58.     (* A WindowList is a linear list of all the windows which have      *)
  59.     (* been associated with a given virtual screen.                     *)
  60.  
  61.     WindowList = RECORD
  62.                      head, tail: WRpointer;
  63.                  END (*RECORD*);
  64.  
  65.     (* Each group of virtual screens is arranged as a circular list.    *)
  66.     (* Each virtual screen has an associated list of windows.           *)
  67.  
  68.     VirtualScreen = POINTER TO VirtualScreenRecord;
  69.     VirtualScreenRecord = RECORD
  70.                               next: VirtualScreen;
  71.                               group: ScreenGroup;
  72.                               windowlist: WindowList;
  73.                           END (*RECORD*);
  74.  
  75.     (* The groups are also arranged as a circular list.  There is       *)
  76.     (* one special "sentinel" group, called NormalOutput, which refers  *)
  77.     (* to normal non-virtual screen output.                             *)
  78.     (* The currentVS field selects the virtual screen which is at       *)
  79.     (* present the active virtual screen in this group, and - for ease  *)
  80.     (* of insertion - the preVS field identifies the predecessor of     *)
  81.     (* currentVS in the circular list.                                  *)
  82.  
  83.     ScreenGroup = POINTER TO ScreenGroupRecord;
  84.     ScreenGroupRecord = RECORD
  85.                             previous, next: ScreenGroup;
  86.                             page: DisplayPage;
  87.                             preVS, currentVS: VirtualScreen;
  88.                         END (*RECORD*);
  89.  
  90. (************************************************************************)
  91.  
  92. CONST testing = TRUE;
  93.  
  94. VAR
  95.     (* The wakeup semaphores are signalled (by the keyboard module)     *)
  96.     (* whenever the hot keys are typed.  We interpret this as a command *)
  97.     (* to switch to the next group, or the next virtual screen within   *)
  98.     (* the current group, depending on which hot key was typed.         *)
  99.  
  100.     wakeup1, wakeup2: Semaphore;
  101.  
  102.     (* ActiveGroup is the currently active group, and NormalOutput is a *)
  103.     (* pointer to a sentinel record in the master list of all groups.   *)
  104.     (* The condition ActiveGroup = NormalOutput is interpreted to mean  *)
  105.     (* that no group should be active, i.e. the only windows displayed  *)
  106.     (* are those not associated with any virtual screen.                *)
  107.  
  108.     ActiveGroup, NormalOutput: ScreenGroup;
  109.  
  110.     (* We use a single lock to protect references to the entire group   *)
  111.     (* structure, including references to ActiveGroup and Occupant.     *)
  112.     (* The alternative, of separate protection for the various          *)
  113.     (* substructures, turns out to create potential deadlock problems,  *)
  114.     (* whose solution becomes messy.                                    *)
  115.     (* Remark: I'd rather use a Lock rather than a semaphore here.      *)
  116.     (* However this will have to wait until I can remove semaphore      *)
  117.     (* waits from procedures Hide and PutOnTop (in module Windows).     *)
  118.  
  119.     guard: Semaphore;
  120.  
  121.     (* Occupant[p] is the virtual screen currently occupying hardware   *)
  122.     (* page p.  We use this in our Hide/Unhide decisions.  Note that    *)
  123.     (* windows not belonging to any virtual screen will not show up in  *)
  124.     (* the Occupant information.                                        *)
  125.  
  126.     Occupant: ARRAY DisplayPage OF VirtualScreen;
  127.  
  128. (************************************************************************)
  129. (*              REMOVING A WINDOW FROM THE GROUP STRUCTURE              *)
  130. (************************************************************************)
  131.  
  132. PROCEDURE RemoveFromVirtualScreen (w: Window;  VS: VirtualScreen);
  133.  
  134.     (* Removes window w from virtual screen VS (without closing it)     *)
  135.     (* if it is found there.                                            *)
  136.  
  137.     VAR previous, current: WRpointer;
  138.  
  139.     BEGIN
  140.         previous := NIL;  current := VS^.windowlist.head;
  141.         LOOP
  142.             IF current = NIL THEN
  143.                 EXIT (*LOOP*);
  144.             ELSIF current^.window = w THEN
  145.                 IF previous = NIL THEN
  146.                     VS^.windowlist.head := current^.next;
  147.                 ELSE
  148.                     previous^.next := current^.next;
  149.                 END (*IF*);
  150.                 IF VS^.windowlist.tail = current THEN
  151.                     VS^.windowlist.tail := previous;
  152.                 END (*IF*);
  153.                 DISPOSE (current);
  154.                 EXIT (*LOOP*);
  155.             ELSE
  156.                 previous := current;  current := current^.next;
  157.             END (*IF*);
  158.         END (*LOOP*);
  159.     END RemoveFromVirtualScreen;
  160.  
  161. (************************************************************************)
  162.  
  163. PROCEDURE RemoveFromGroup (w: Window;  group: ScreenGroup);
  164.  
  165.     (* See description of RemoveWindow, below.  *)
  166.  
  167.     VAR current: VirtualScreen;
  168.  
  169.     BEGIN
  170.         WITH group^ DO
  171.             current := currentVS;
  172.             IF current <> NIL THEN
  173.                 REPEAT
  174.                     RemoveFromVirtualScreen (w, current);
  175.                     current := current^.next;
  176.                 UNTIL current = currentVS;
  177.             END (*IF*);
  178.         END (*WITH*);
  179.     END RemoveFromGroup;
  180.  
  181. (************************************************************************)
  182.  
  183. PROCEDURE RemoveWindow (w: Window;  page: DisplayPage);
  184.  
  185.     (* Removes the association, if any, between window w and the        *)
  186.     (* virtual screen(s) with which it is associated.  It is possible   *)
  187.     (* that no such association exists, in which case we do nothing.    *)
  188.  
  189.     VAR current: ScreenGroup;
  190.  
  191.     BEGIN
  192.         Wait (guard);
  193.         current := NormalOutput^.next;
  194.         WHILE current <> NormalOutput DO
  195.             IF current^.page = page THEN
  196.                 RemoveFromGroup (w, current);
  197.             END (*IF*);
  198.             current := current^.next;
  199.         END (*WHILE*);
  200.         Signal (guard);
  201.     END RemoveWindow;
  202.  
  203. (************************************************************************)
  204. (*                          FINDING A WINDOW                            *)
  205. (************************************************************************)
  206.  
  207. PROCEDURE WindowIsInScreen (w: Window;  VS: VirtualScreen): BOOLEAN;
  208.  
  209.     (* Returns TRUE iff w is a window in VS. *)
  210.  
  211.     VAR current: WRpointer;
  212.  
  213.     BEGIN
  214.         current := VS^.windowlist.head;
  215.         LOOP
  216.             IF current = NIL THEN
  217.                 RETURN FALSE;
  218.             END (*IF*);
  219.             IF current^.window = w THEN
  220.                 RETURN TRUE;
  221.             END (*IF*);
  222.             current := current^.next;
  223.         END (*LOOP*);
  224.     END WindowIsInScreen;
  225.  
  226. (************************************************************************)
  227.  
  228. PROCEDURE FindInGroup (w: Window;  G: ScreenGroup): VirtualScreen;
  229.  
  230.     (* Like VirtualScreenOf (see below), except that the caller         *)
  231.     (* specifies which group to search.                                 *)
  232.  
  233.     VAR result: VirtualScreen;
  234.  
  235.     BEGIN
  236.         result := G^.currentVS;
  237.         WHILE (result <> NIL) AND NOT WindowIsInScreen(w, result) DO
  238.             IF result = G^.preVS THEN
  239.                 result := NIL;
  240.             ELSE
  241.                 result := result^.next;
  242.             END (*IF*);
  243.         END (*WHILE*);
  244.         RETURN result;
  245.     END FindInGroup;
  246.  
  247. (************************************************************************)
  248.  
  249. PROCEDURE VirtualScreenOf (w: Window): VirtualScreen;
  250.  
  251.     (* Returns the virtual screen, if any, with which window w is       *)
  252.     (* associated.  It is possible that no such association exists, in  *)
  253.     (* which case we return a NIL result.                               *)
  254.  
  255.     VAR current: ScreenGroup;  result: VirtualScreen;
  256.  
  257.     BEGIN
  258.         result := NIL;
  259.         Wait (guard);
  260.         current := NormalOutput^.next;
  261.         LOOP
  262.             IF current = NormalOutput THEN EXIT(*LOOP*) END(*IF*);
  263.             result := FindInGroup (w, current);
  264.             IF result <> NIL THEN EXIT(*LOOP*) END(*IF*);
  265.             current := current^.next;
  266.         END (*LOOP*);
  267.         Signal (guard);
  268.         RETURN result;
  269.     END VirtualScreenOf;
  270.  
  271. (************************************************************************)
  272. (*                  HIDING AND UNHIDING SCREEN OUTPUT                   *)
  273. (************************************************************************)
  274.  
  275. PROCEDURE HideAll (MP: VirtualScreen);
  276.  
  277.     (* Makes all windows in this virtual screen invisible.      *)
  278.  
  279.     VAR list: WRpointer;
  280.  
  281.     BEGIN
  282.         IF MP <> NIL THEN
  283.             list := MP^.windowlist.head;
  284.             WHILE list <> NIL DO
  285.                 Hide (list^.window);
  286.                 list := list^.next;
  287.             END (*WHILE*);
  288.         END (*IF*);
  289.     END HideAll;
  290.  
  291. (************************************************************************)
  292.  
  293. PROCEDURE UnhideAll (MP: VirtualScreen);
  294.  
  295.     (* Makes all windows in this virtual screen visible.        *)
  296.  
  297.     VAR list: WRpointer;
  298.  
  299.     BEGIN
  300.         IF MP <> NIL THEN
  301.             list := MP^.windowlist.head;
  302.             WHILE list <> NIL DO
  303.                 PutOnTop (list^.window);
  304.                 list := list^.next;
  305.             END (*WHILE*);
  306.         END (*IF*);
  307.     END UnhideAll;
  308.  
  309. (************************************************************************)
  310. (*                      TASKS TO HANDLE KEYBOARD INPUT                  *)
  311. (************************************************************************)
  312.  
  313. PROCEDURE StepToNextGroup;
  314.  
  315.     VAR newgroup: ScreenGroup;
  316.  
  317.     BEGIN
  318.         newgroup := ActiveGroup^.next;
  319.         WITH newgroup^ DO
  320.             IF page <> ActiveGroup^.page THEN
  321.                 SetActivePage (page);
  322.             END (*IF*);
  323.             IF Occupant[page] <> currentVS THEN
  324.                 HideAll (Occupant[page]);
  325.                 UnhideAll (currentVS);
  326.                 Occupant[page] := currentVS;
  327.             END (*IF*);
  328.         END (*WITH*);
  329.         ActiveGroup := newgroup;
  330.     END StepToNextGroup;
  331.  
  332. (************************************************************************)
  333.  
  334. PROCEDURE HotKeyHandler1;
  335.  
  336.     (* Each time an Alt/P key is typed, this task switches to the next  *)
  337.     (* group of virtual pages.                                          *)
  338.  
  339.     BEGIN
  340.         LOOP    (* forever *)
  341.             Wait (wakeup1);
  342.             Wait (guard);
  343.             StepToNextGroup;
  344.             Signal (guard);
  345.         END (*LOOP*);
  346.     END HotKeyHandler1;
  347.  
  348. (************************************************************************)
  349.  
  350. PROCEDURE HotKeyHandler2;
  351.  
  352.     (* Each time the F6 key is typed, this task switches to the next    *)
  353.     (* virtual page within the current group.                           *)
  354.  
  355.     BEGIN
  356.         LOOP    (* forever *)
  357.             Wait (wakeup2);
  358.             Wait (guard);
  359.             WITH ActiveGroup^ DO
  360.                 IF (currentVS <> NIL) AND (currentVS^.next <> currentVS) THEN
  361.                     preVS := currentVS;  currentVS := currentVS^.next;
  362.                     HideAll (preVS);
  363.                     UnhideAll (currentVS);
  364.                     Occupant[page] := currentVS;
  365.                 END (*IF*);
  366.             END (*WITH*);
  367.             Signal (guard);
  368.         END (*LOOP*);
  369.     END HotKeyHandler2;
  370.  
  371. (************************************************************************)
  372. (*                      EXTERNALLY CALLABLE PROCEDURES                  *)
  373. (************************************************************************)
  374.  
  375. PROCEDURE EnableHotKeys (flag1: BOOLEAN;  key1: CHAR;
  376.                          flag2: BOOLEAN;  key2: CHAR);
  377.  
  378.     (* Creates the "hot key" tasks, and enable the hot keys.  The first *)
  379.     (* pair of parameters defines the group switching hot key, and the  *)
  380.     (* second pair defines the hot key for cycling within a group.      *)
  381.     (* The "flag" part of each parameter pair is FALSE for a normal     *)
  382.     (* key, and TRUE for an extended key defined by a two-character     *)
  383.     (* sequence (where the first character is CHR(0).                   *)
  384.  
  385.     BEGIN
  386.         HotKey (flag1, key1, wakeup1);
  387.         CreateTask (HotKeyHandler1, 9, "Group hot key");
  388.         HotKey (flag2, key2, wakeup2);
  389.         CreateTask (HotKeyHandler2, 9, "Cycle hot key");
  390.     END EnableHotKeys;
  391.  
  392. (************************************************************************)
  393.  
  394. PROCEDURE CreateScreenGroup (hardwarepage: DisplayPage): ScreenGroup;
  395.  
  396.     (* Creates a new screen group, and maps it to the specified display *)
  397.     (* page in the screen hardware.  It is permissible to map more than *)
  398.     (* one group to the same hardware page.  Note that putting a group  *)
  399.     (* on hardware page 0 means that it shares the screen with the      *)
  400.     (* "normal output" which does not belong to any virtual page.  This *)
  401.     (* is permitted, but usually inadvisable on aesthetic grounds.      *)
  402.  
  403.     VAR result: ScreenGroup;
  404.  
  405.     BEGIN
  406.         NEW (result);
  407.         WITH result^ DO
  408.             previous := NIL;  next := NIL;
  409.             page := hardwarepage;  currentVS := NIL;  preVS := NIL;
  410.         END (*WITH*);
  411.  
  412.         (* Insert the new result into the circular list of groups.      *)
  413.         (* In the present version, we insert it just ahead of the       *)
  414.         (* special group NormalOutput, which gives the effect of        *)
  415.         (* putting the groups in order of creation.                     *)
  416.  
  417.         Wait (guard);
  418.         NormalOutput^.previous^.next := result;
  419.         result^.previous := NormalOutput^.previous;
  420.         result^.next := NormalOutput;
  421.         NormalOutput^.previous := result;
  422.         Signal (guard);
  423.  
  424.         RETURN result;
  425.  
  426.     END CreateScreenGroup;
  427.  
  428. (************************************************************************)
  429.  
  430. PROCEDURE CreateVirtualScreen (group: ScreenGroup): VirtualScreen;
  431.  
  432.     (* Adds a new virtual screen to the specified group.        *)
  433.  
  434.     VAR result: VirtualScreen;
  435.  
  436.     BEGIN
  437.         NEW (result);
  438.         WITH result^ DO
  439.             next := NIL;  windowlist.head := NIL;  windowlist.tail := NIL;
  440.         END (*WITH*);
  441.         result^.group := group;
  442.  
  443.         (* Insert the new result into the list of virtual screens       *)
  444.         (* belonging to the specified group.  It's inserted just before *)
  445.         (* the current virtual screen for this group; in most cases     *)
  446.         (* this means that virtual screens are ordered according to the *)
  447.         (* order of their creation.                                     *)
  448.  
  449.         Wait (guard);
  450.         WITH group^ DO
  451.             IF currentVS = NIL THEN
  452.                 currentVS := result;
  453.                 preVS := result;
  454.                 result^.next := result;
  455.             ELSE
  456.                 result^.next := currentVS;
  457.                 preVS^.next := result;
  458.                 preVS := result;
  459.             END (*IF*);
  460.         END (*WITH*);
  461.         Signal (guard);
  462.  
  463.         RETURN result;
  464.  
  465.     END CreateVirtualScreen;
  466.  
  467. (************************************************************************)
  468.  
  469. PROCEDURE MapToVirtualScreen (w: Window;  screen: VirtualScreen);
  470.  
  471.     (* Before calling this procedure, both w and screen must have been  *)
  472.     (* created.  This procedure ensures that window w is visible on     *)
  473.     (* the screen only when the given virtual screen page is active.    *)
  474.     (* The association lasts until the window is closed or the virtual  *)
  475.     (* screen is removed.                                               *)
  476.  
  477.     VAR p: WRpointer;
  478.  
  479.     BEGIN
  480.         NEW (p);
  481.         WITH p^ DO
  482.             next := NIL;  window := w;
  483.         END (*WITH*);
  484.  
  485.         Wait (guard);
  486.  
  487.         WITH screen^ DO
  488.             WITH windowlist DO
  489.                 IF head = NIL THEN
  490.                     head := p;
  491.                 ELSE
  492.                     tail^.next := p;
  493.                 END (*IF*);
  494.                 tail := p;
  495.             END (*WITH*);
  496.             PutOnPage (w, group^.page);
  497.         END (*WITH*);
  498.  
  499.         (* Hide or display the new window as appropriate.  *)
  500.  
  501.         WITH screen^.group^ DO
  502.             IF Occupant[page] = NIL THEN
  503.                 Occupant[page] := screen;
  504.             END (*IF*);
  505.             IF Occupant[page] = screen THEN
  506.                 PutOnTop (w);
  507.             ELSE
  508.                 Hide(w);
  509.             END (*IF*);
  510.         END (*WITH*);
  511.  
  512.         InstallCloseHandler (w, RemoveWindow);
  513.         Signal (guard);
  514.  
  515.     END MapToVirtualScreen;
  516.  
  517. (************************************************************************)
  518. (*              REMOVING VIRTUAL SCREENS AND SCREEN GROUPS              *)
  519. (************************************************************************)
  520.  
  521. PROCEDURE RemoveVirtualScreen (VAR (*INOUT*) screen: VirtualScreen);
  522.  
  523.     (* Destroys all associations between the given virtual screen and   *)
  524.     (* its windows (but does not close the windows), and permanently    *)
  525.     (* removes this screen from the collection of virtual screens.      *)
  526.  
  527.     VAR wptr, wnext: WRpointer;
  528.         VSptr: VirtualScreen;
  529.  
  530.     BEGIN
  531.         Wait (guard);
  532.  
  533.         (* Detach the VirtualScreenRecord from its group.  This might   *)
  534.         (* require updating preVS and currentVS for the group.  Beware  *)
  535.         (* of the special case of a group with only one element.        *)
  536.  
  537.         WITH screen^.group^ DO
  538.             IF currentVS = preVS THEN
  539.                 preVS := NIL;  currentVS := NIL;
  540.             ELSIF screen = currentVS THEN
  541.                 currentVS := screen^.next;
  542.                 preVS^.next := currentVS;
  543.             ELSE
  544.                 (* Let VSptr point to the screen ahead of this one.     *)
  545.                 VSptr := currentVS;
  546.                 WHILE VSptr^.next <> screen DO
  547.                     VSptr := VSptr^.next;
  548.                 END(*WHILE*);
  549.                 IF screen = preVS THEN
  550.                     preVS := VSptr;
  551.                 END (*IF*);
  552.                 VSptr^.next := screen^.next;
  553.             END (*IF*);
  554.  
  555.             (* Deal with the special case of a virtual screen which is  *)
  556.             (* now the occupant of its hardware page - i.e. it is now   *)
  557.             (* visible, or will become visible as soon as that hardware *)
  558.             (* page is activated.                                       *)
  559.  
  560.             IF screen = Occupant[page] THEN
  561.                 HideAll (screen);
  562.                 UnhideAll (currentVS);
  563.                 Occupant[page] := currentVS;
  564.             END (*IF*);
  565.         END (*WITH*);
  566.  
  567.         (* Discard the windowlist for this screen.      *)
  568.  
  569.         wptr := screen^.windowlist.head;
  570.         WHILE wptr <> NIL DO
  571.             wnext := wptr^.next;
  572.             DISPOSE (wptr);
  573.             wptr := wnext;
  574.         END (*WHILE*);
  575.  
  576.         DISPOSE (screen);
  577.         Signal (guard);
  578.  
  579.     END RemoveVirtualScreen;
  580.  
  581. (************************************************************************)
  582.  
  583. PROCEDURE RemoveScreenGroup (VAR (*INOUT*) group: ScreenGroup);
  584.  
  585.     (* As above, but removes an entire group.   *)
  586.  
  587.     BEGIN
  588.         (* Start by emptying the group.  We can cut down the overhead   *)
  589.         (* a little by leaving group^.currentVS until last.             *)
  590.  
  591.         WITH group^ DO
  592.             WHILE currentVS <> NIL DO
  593.                 RemoveVirtualScreen (currentVS^.next);
  594.             END (*WHILE*);
  595.         END (*WITH*);
  596.  
  597.         (* Detach this group from the circular list of all groups.      *)
  598.  
  599.         Wait (guard);
  600.         IF ActiveGroup = group THEN
  601.             StepToNextGroup;
  602.         END (*IF*);
  603.         WITH group^ DO
  604.             previous^.next := next;
  605.             next^.previous := previous;
  606.         END (*WITH*);
  607.         Signal (guard);
  608.  
  609.         DISPOSE (group);
  610.  
  611.     END RemoveScreenGroup;
  612.  
  613. (************************************************************************)
  614. (*                         MODULE INITIALISATION                        *)
  615. (************************************************************************)
  616.  
  617. VAR p: DisplayPage;
  618.  
  619. BEGIN
  620.     (* There are initially no virtual screens.  *)
  621.  
  622.     FOR p := 0 TO MAX(DisplayPage) DO
  623.         Occupant[p] := NIL;
  624.     END (*FOR*);
  625.  
  626.     (* Create the global semaphores.    *)
  627.  
  628.     CreateSemaphore (wakeup1, 0);
  629.     CreateSemaphore (wakeup2, 0);
  630.     CreateSemaphore (guard, 1);
  631.  
  632.     (* Create the initial list of groups: a circular list with one      *)
  633.     (* element, that element being the special sentinel which refers to *)
  634.     (* normal screen output rather than one of the created groups.      *)
  635.  
  636.     Wait (guard);
  637.     NEW (ActiveGroup);
  638.     WITH ActiveGroup^ DO
  639.         previous := ActiveGroup;  next := ActiveGroup;
  640.         preVS := NIL;  currentVS := NIL;  page := 0;
  641.     END (*WITH*);
  642.     NormalOutput := ActiveGroup;
  643.     Signal (guard);
  644.  
  645. END MultiScreen.
  646.  
  647.