packages feed

language-Modula2-0.1: examples/Modula-2_Libraries/PMOS/sources/general/multiscr.mod

IMPLEMENTATION MODULE MultiScreen;

	(****************************************************************)
	(*								*)
	(*  This module allows the creation of multiple virtual	screens	*)
	(*  - complete screens, not just windows - with two keyboard	*)
	(*	"hot keys" used to navigate around the screens.		*)
	(*								*)
	(*  We support a two-level hierarchy: a virtual screen is a	*)
	(*  collection of windows, and each virtual screen is a member	*)
	(*  of a group.  (In addition, there is a pseudo-group made up	*)
	(*  of all windows which are not associated with any virtual	*)
	(*  screen, to handle the "normal output" which bypasses this	*)
	(*  module.)  One hot key cycles through the groups, and the	*)
	(*  second cycles through the virtual screens of the currently	*)
	(*  active group.						*)
	(*								*)
	(*  In the present version, Ctrl/P is the hot key used to	*)
	(*  select another group, and F6 cycles within a group.		*)
	(*								*)
	(*  Programmer:		P. Moylan				*)
	(*  Last edited:	21 February 1995			*)
	(*  Status:		Mostly working				*)
	(*	Still don't properly support duplicating the same	*)
	(*	window on multiple virtual screens.  At this stage	*)
	(*	I'm not convinced that this would be worth the trouble.	*)
	(*								*)
	(****************************************************************)

FROM Storage1 IMPORT
    (* proc *)	ALLOCATE, DEALLOCATE;

FROM TaskControl IMPORT
    (* proc *)	CreateTask;

FROM Semaphores IMPORT
    (* type *)	Semaphore,
    (* proc *)	CreateSemaphore, Wait, Signal;

FROM Keyboard IMPORT
    (* proc *)	HotKey;

FROM Windows IMPORT
    (* proc *)	Hide, PutOnTop, PutOnPage, SetActivePage, InstallCloseHandler;

(************************************************************************)

TYPE
    (* A WindowRecord identifies one window. *)

    WRpointer = POINTER TO WindowRecord;
    WindowRecord =  RECORD
			next: WRpointer;
			window: Window;
		    END (*RECORD*);

    (* A WindowList is a linear list of all the windows which have	*)
    (* been associated with a given virtual screen.			*)

    WindowList = RECORD
		     head, tail: WRpointer;
		 END (*RECORD*);

    (* Each group of virtual screens is arranged as a circular list.	*)
    (* Each virtual screen has an associated list of windows.		*)

    VirtualScreen = POINTER TO VirtualScreenRecord;
    VirtualScreenRecord = RECORD
			      next: VirtualScreen;
			      group: ScreenGroup;
			      windowlist: WindowList;
			  END (*RECORD*);

    (* The groups are also arranged as a circular list.  There is	*)
    (* one special "sentinel" group, called NormalOutput, which refers	*)
    (* to normal non-virtual screen output.				*)
    (* The currentVS field selects the virtual screen which is at	*)
    (* present the active virtual screen in this group, and - for ease	*)
    (* of insertion - the preVS field identifies the predecessor of	*)
    (* currentVS in the circular list.					*)

    ScreenGroup = POINTER TO ScreenGroupRecord;
    ScreenGroupRecord = RECORD
			    previous, next: ScreenGroup;
			    page: DisplayPage;
			    preVS, currentVS: VirtualScreen;
			END (*RECORD*);

(************************************************************************)

CONST testing = TRUE;
      CtrlP = CHR(16);
      F6 = '@';		(* second code returned by F6 function key	*)

VAR
    (* The wakeup semaphores are signalled (by the keyboard module)	*)
    (* whenever the hot keys are typed.  We interpret this as a command	*)
    (* to switch to the next group, or the next virtual screen within	*)
    (* the current group, depending on which hot key was typed.		*)

    wakeup1, wakeup2: Semaphore;

    (* ActiveGroup is the currently active group, and NormalOutput is a	*)
    (* pointer to a sentinel record in the master list of all groups.	*)
    (* The condition ActiveGroup = NormalOutput is interpreted to mean	*)
    (* that no group should be active, i.e. the only windows displayed	*)
    (* are those not associated with any virtual screen.		*)

    ActiveGroup, NormalOutput: ScreenGroup;

    (* We use a single lock to protect references to the entire group	*)
    (* structure, including references to ActiveGroup and Occupant.	*)
    (* The alternative, of separate protection for the various		*)
    (* substructures, turns out to create potential deadlock problems,	*)
    (* whose solution becomes messy.					*)
    (* Remark: I'd rather use a Lock rather than a semaphore here.	*)
    (* However this will have to wait until I can remove semaphore	*)
    (* waits from procedures Hide and PutOnTop (in module Windows).	*)

    guard: Semaphore;

    (* Occupant[p] is the virtual screen currently occupying hardware	*)
    (* page p.  We use this in our Hide/Unhide decisions.  Note that	*)
    (* windows not belonging to any virtual screen will not show up in	*)
    (* the Occupant information.					*)

    Occupant: ARRAY DisplayPage OF VirtualScreen;

(************************************************************************)
(*		REMOVING A WINDOW FROM THE GROUP STRUCTURE		*)
(************************************************************************)

PROCEDURE RemoveFromVirtualScreen (w: Window;  VS: VirtualScreen);

    (* Removes window w from virtual screen VS (without closing it)	*)
    (* if it is found there.						*)

    VAR previous, current: WRpointer;

    BEGIN
	previous := NIL;  current := VS^.windowlist.head;
	LOOP
	    IF current = NIL THEN
		EXIT (*LOOP*);
	    ELSIF current^.window = w THEN
		IF previous = NIL THEN
		    VS^.windowlist.head := current^.next;
		ELSE
		    previous^.next := current^.next;
		END (*IF*);
		IF VS^.windowlist.tail = current THEN
		    VS^.windowlist.tail := previous;
		END (*IF*);
		DISPOSE (current);
		EXIT (*LOOP*);
	    ELSE
		previous := current;  current := current^.next;
	    END (*IF*);
	END (*LOOP*);
    END RemoveFromVirtualScreen;

(************************************************************************)

PROCEDURE RemoveFromGroup (w: Window;  group: ScreenGroup);

    (* See description of RemoveWindow, below.	*)

    VAR current: VirtualScreen;

    BEGIN
	WITH group^ DO
	    current := currentVS;
	    IF current <> NIL THEN
		REPEAT
		    RemoveFromVirtualScreen (w, current);
		    current := current^.next;
		UNTIL current = currentVS;
	    END (*IF*);
	END (*WITH*);
    END RemoveFromGroup;

(************************************************************************)

PROCEDURE RemoveWindow (w: Window;  page: DisplayPage);

    (* Removes the association, if any, between window w and the	*)
    (* virtual screen(s) with which it is associated.  It is possible	*)
    (* that no such association exists, in which case we do nothing.	*)

    VAR current: ScreenGroup;

    BEGIN
	Wait (guard);
	current := NormalOutput^.next;
	WHILE current <> NormalOutput DO
	    IF current^.page = page THEN
		RemoveFromGroup (w, current);
	    END (*IF*);
	    current := current^.next;
	END (*WHILE*);
	Signal (guard);
    END RemoveWindow;

(************************************************************************)
(*		    HIDING AND UNHIDING SCREEN OUTPUT			*)
(************************************************************************)

PROCEDURE HideAll (MP: VirtualScreen);

    (* Makes all windows in this virtual screen invisible.	*)

    VAR list: WRpointer;

    BEGIN
	IF MP <> NIL THEN
	    list := MP^.windowlist.head;
	    WHILE list <> NIL DO
		Hide (list^.window);
		list := list^.next;
	    END (*WHILE*);
	END (*IF*);
    END HideAll;

(************************************************************************)

PROCEDURE UnhideAll (MP: VirtualScreen);

    (* Makes all windows in this virtual screen visible.	*)

    VAR list: WRpointer;

    BEGIN
	IF MP <> NIL THEN
	    list := MP^.windowlist.head;
	    WHILE list <> NIL DO
		PutOnTop (list^.window);
		list := list^.next;
	    END (*WHILE*);
	END (*IF*);
    END UnhideAll;

(************************************************************************)
(*			TASKS TO HANDLE KEYBOARD INPUT			*)
(************************************************************************)

PROCEDURE StepToNextGroup;

    VAR newgroup: ScreenGroup;

    BEGIN
	newgroup := ActiveGroup^.next;
	WITH newgroup^ DO
	    IF page <> ActiveGroup^.page THEN
		SetActivePage (page);
	    END (*IF*);
	    IF Occupant[page] <> currentVS THEN
		HideAll (Occupant[page]);
		UnhideAll (currentVS);
		Occupant[page] := currentVS;
	    END (*IF*);
	END (*WITH*);
	ActiveGroup := newgroup;
    END StepToNextGroup;

(************************************************************************)

PROCEDURE HotKeyHandler1;

    (* Each time a CTRL/P key is typed, this task switches to the next	*)
    (* group of virtual pages.						*)

    BEGIN
	LOOP	(* forever *)
	    Wait (wakeup1);
	    Wait (guard);
	    StepToNextGroup;
	    Signal (guard);
	END (*LOOP*);
    END HotKeyHandler1;

(************************************************************************)

PROCEDURE HotKeyHandler2;

    (* Each time the F6 key is typed, this task switches to the next	*)
    (* virtual page within the current group.				*)

    BEGIN
	LOOP	(* forever *)
	    Wait (wakeup2);
	    Wait (guard);
	    WITH ActiveGroup^ DO
		IF (currentVS <> NIL) AND (currentVS^.next <> currentVS) THEN
		    preVS := currentVS;  currentVS := currentVS^.next;
		    HideAll (preVS);
		    UnhideAll (currentVS);
		    Occupant[page] := currentVS;
		END (*IF*);
	    END (*WITH*);
	    Signal (guard);
	END (*LOOP*);
    END HotKeyHandler2;

(************************************************************************)
(*			EXTERNALLY CALLABLE PROCEDURES			*)
(************************************************************************)

PROCEDURE CreateScreenGroup (hardwarepage: DisplayPage): ScreenGroup;

    (* Creates a new screen group, and maps it to the specified display	*)
    (* page in the screen hardware.  It is permissible to map more than	*)
    (* one group to the same hardware page.  Note that putting a group	*)
    (* on hardware page 0 means that it shares the screen with the	*)
    (* "normal output" which does not belong to any virtual page.  This	*)
    (* is permitted, but usually inadvisable on aesthetic grounds.	*)

    VAR result: ScreenGroup;

    BEGIN
	NEW (result);
	WITH result^ DO
	    previous := NIL;  next := NIL;
	    page := hardwarepage;  currentVS := NIL;  preVS := NIL;
	END (*WITH*);

	(* Insert the new result into the circular list of groups.	*)
	(* In the present version, we insert it just ahead of the	*)
	(* special group NormalOutput, which gives the effect of	*)
	(* putting the groups in order of creation.			*)

	Wait (guard);
	NormalOutput^.previous^.next := result;
	result^.previous := NormalOutput^.previous;
	result^.next := NormalOutput;
	NormalOutput^.previous := result;
	Signal (guard);

	RETURN result;

    END CreateScreenGroup;

(************************************************************************)

PROCEDURE CreateVirtualScreen (group: ScreenGroup): VirtualScreen;

    (* Adds a new virtual screen to the specified group.	*)

    VAR result: VirtualScreen;

    BEGIN
	NEW (result);
	WITH result^ DO
	    next := NIL;  windowlist.head := NIL;  windowlist.tail := NIL;
	END (*WITH*);
	result^.group := group;

	(* Insert the new result into the list of virtual screens	*)
	(* belonging to the specified group.  It's inserted just before	*)
	(* the current virtual screen for this group; in most cases	*)
	(* this means that virtual screens are ordered according to the	*)
	(* order of their creation.					*)

	Wait (guard);
	WITH group^ DO
	    IF currentVS = NIL THEN
		currentVS := result;
		preVS := result;
		result^.next := result;
	    ELSE
		result^.next := currentVS;
		preVS^.next := result;
		preVS := result;
	    END (*IF*);
	END (*WITH*);
	Signal (guard);

	RETURN result;

    END CreateVirtualScreen;

(************************************************************************)

PROCEDURE MapToVirtualScreen (w: Window;  screen: VirtualScreen);

    (* Before calling this procedure, both w and screen must have been	*)
    (* created.  This procedure ensures that window w is visible on	*)
    (* the screen only when the given virtual screen page is active.	*)
    (* The association lasts until the window is closed or the virtual	*)
    (* screen is removed.						*)

    VAR p: WRpointer;

    BEGIN
	NEW (p);
	WITH p^ DO
	    next := NIL;  window := w;
	END (*WITH*);

	Wait (guard);

	WITH screen^ DO
	    WITH windowlist DO
		IF head = NIL THEN
		    head := p;
		ELSE
		    tail^.next := p;
		END (*IF*);
		tail := p;
	    END (*WITH*);
	    PutOnPage (w, group^.page);
	END (*WITH*);

	(* Hide the new window if appropriate.	*)

	WITH screen^.group^ DO
	    IF Occupant[page] = NIL THEN
		Occupant[page] := screen;
	    ELSIF Occupant[page] <> screen THEN
		Hide(w);
	    END (*IF*);
	END (*WITH*);

	InstallCloseHandler (w, RemoveWindow);
	Signal (guard);

    END MapToVirtualScreen;

(************************************************************************)
(*		REMOVING VIRTUAL SCREENS AND SCREEN GROUPS		*)
(************************************************************************)

PROCEDURE RemoveVirtualScreen (VAR (*INOUT*) screen: VirtualScreen);

    (* Destroys all associations between the given virtual screen and	*)
    (* its windows (but does not close the windows), and permanently	*)
    (* removes this screen from the collection of virtual screens.	*)

    VAR wptr, wnext: WRpointer;
	VSptr: VirtualScreen;

    BEGIN
	Wait (guard);

	(* Detach the VirtualScreenRecord from its group.  This might	*)
	(* require updating preVS and currentVS for the group.  Beware	*)
	(* of the special case of a group with only one element.	*)

	WITH screen^.group^ DO
	    IF currentVS = preVS THEN
		preVS := NIL;  currentVS := NIL;
	    ELSIF screen = currentVS THEN
		currentVS := screen^.next;
		preVS^.next := currentVS;
	    ELSE
		(* Let VSptr point to the screen ahead of this one.	*)
		VSptr := currentVS;
		WHILE VSptr^.next <> screen DO
		    VSptr := VSptr^.next;
		END(*WHILE*);
		IF screen = preVS THEN
		    preVS := VSptr;
		END (*IF*);
		VSptr^.next := screen^.next;
	    END (*IF*);

	    (* Deal with the special case of a virtual screen which is	*)
	    (* now the occupant of its hardware page - i.e. it is now	*)
	    (* visible, or will become visible as soon as that hardware	*)
	    (* page is activated.					*)

	    IF screen = Occupant[page] THEN
		HideAll (screen);
		UnhideAll (currentVS);
		Occupant[page] := currentVS;
	    END (*IF*);
	END (*WITH*);

	(* Discard the windowlist for this screen.	*)

	wptr := screen^.windowlist.head;
	WHILE wptr <> NIL DO
	    wnext := wptr^.next;
	    DISPOSE (wptr);
	    wptr := wnext;
	END (*WHILE*);

	DISPOSE (screen);
	Signal (guard);

    END RemoveVirtualScreen;

(************************************************************************)

PROCEDURE RemoveScreenGroup (VAR (*INOUT*) group: ScreenGroup);

    (* As above, but removes an entire group.	*)

    BEGIN
	(* Start by emptying the group.  We can cut down the overhead	*)
	(* a little by leaving group^.currentVS until last.		*)

	WITH group^ DO
	    WHILE currentVS <> NIL DO
		RemoveVirtualScreen (currentVS^.next);
	    END (*WHILE*);
	END (*WITH*);

	(* Detach this group from the circular list of all groups.	*)

	Wait (guard);
	IF ActiveGroup = group THEN
	    StepToNextGroup;
	END (*IF*);
	WITH group^ DO
	    previous^.next := next;
	    next^.previous := previous;
	END (*WITH*);
	Signal (guard);

	DISPOSE (group);

    END RemoveScreenGroup;

(************************************************************************)
(*			   MODULE INITIALISATION			*)
(************************************************************************)

VAR p: DisplayPage;

BEGIN
    (* There are initially no virtual screens.	*)

    FOR p := 0 TO MAX(DisplayPage) DO
	Occupant[p] := NIL;
    END (*FOR*);

    (* Create the global semaphores.	*)

    CreateSemaphore (wakeup1, 0);
    CreateSemaphore (wakeup2, 0);
    CreateSemaphore (guard, 1);

    (* Create the initial list of groups: a circular list with one	*)
    (* element, that element being the special sentinel which refers to	*)
    (* normal screen output rather than one of the created groups.	*)

    Wait (guard);
    NEW (ActiveGroup);
    WITH ActiveGroup^ DO
	previous := ActiveGroup;  next := ActiveGroup;
	preVS := NIL;  currentVS := NIL;  page := 0;
    END (*WITH*);
    NormalOutput := ActiveGroup;
    Signal (guard);

    (* Create the "hot key" tasks, and enable the hot keys.	*)

    HotKey (FALSE, CtrlP, wakeup1);
    CreateTask (HotKeyHandler1, 9, "^P hot key");
    HotKey (TRUE, F6, wakeup2);
    CreateTask (HotKeyHandler2, 9, "F6 hot key");

END MultiScreen.