[comp.windows.news] Multiple desktops under NeWS

sheffler@gomez.mitre.org (Thomas J. Sheffler) (02/11/89)

I started wondering how a collection of desktops could be managed under
NeWS.  A desktop would be simply a set of windows.  Switching from one desk
to the other should be easy and should allow an arbitrary grouping
of windows.

My first attempt at this is presented below.  The application window has
a number of buttons.  The "ToDeskX" buttons bring up one of three desktops.
The "All" button brings up ALL windows. 

Sorting windows into the desktops is done by pressing "NextWindow".  This
selects a window and hilites the one selected by flashing it (you'll be
able to tell).  Put it in one or more desks with the "InDeskX" buttons,
or in all three with "InAll".

Because the creation of new windows is not caught and the exit of other
windows is not noticed, there is a "RESET" button.  This releases
all windows from the desks and allows you to start over.  The main reason
this is needed is to release zombie canvases.  When a window application
exits but is in a desktop, a reference to that canvas is still in the
desktop structure.  The canvas will not disappear until the reference
is released: "reset" does this.  

No guarantees come with this software.  As a hint, if something goes wrong,
use the "AllWindows" rootmenu menu to send "Open" or "Close" to all
windows to get them back.  This package achieves its effect by selectively
mapping and unmapping windows -- maybe there's a better way.

Any comments/improvements would be appreciated.  Send them to
	sheffler@gateway.mitre.org

	-Tom

===================== C U T   H E R E ======================================
#! /usr/NeWS/bin/psh

% Provide a multi-desktop capability for NeWS
% Upon startup, all existing windows are located and placed in a list.
% Pressing the "NextWindow" button causes the selection of the next
% window in the list - it is hilited by flashing it on the screen and
% bringing it to the top.  The window may be placed in one of the
% three desktops with the "InDesk" buttons, or in all three desktops
% with the "In All" button.  After windows have been placed in desks,
% a desktop may be selected with the "ToDesk" buttons, or the "All"
% button to re-map all windows.

% Because the desktop manager doesn't know about windows dying or starting
% up, the "Reset" button must be used to relocate windows.  This
% destroys desktop lists.

% Tom Sheffler
% MITRE
% February 1989

systemdict /DeskWin known not {		% only load the class if not known

systemdict begin

/DeskWin DefaultWindow
dictbegin				% instance variables
    
dictend

classbegin				% class variables first
    /MaxWin 50 def			% max number of windows?
    /Desk1 MaxWin dict def		% dictionaries for desk-tops
    /Desk2 MaxWin dict def
    /Desk3 MaxWin dict def

    /Every MaxWin dict def		% list EVERY window found (w/ AllWin)
    /UniqueCycle 0 def			% for cycling through windows
    /currentWin null def		% used with function /NextWindow

    /controlWin null def		% keep track of the ONE main window

    % Clear one entry of a dict in a /forall loop
    /clearone {				% key value => -
	pop null store
    } def

    % Clear all entries in the dicts (to return to VM mgr)
    /clearall {
	/currentWin null def		% release this one
	/Desk1 null def			% release all storage 
	/Desk2 null def
	/Desk3 null def
	/Desk1 MaxWin dict def		% and create new
	/Desk2 MaxWin dict def
	/Desk3 MaxWin dict def
    } def

    % Go thru the dicts clearing out DEAD windows.

    % Treat the buttons as frame controls.
    /CreateFrameControls {
	/CreateFrameControls super send
	/b1 (ToDesk1) {/ToDesk1 DeskWin send} FrameCanvas /new ButtonItem send
	    20 -60 /move 3 index send def
	/b2 (ToDesk2) {/ToDesk2 DeskWin send} FrameCanvas /new ButtonItem send
	    90 -60 /move 3 index send def
	/b3 (ToDesk3) {/ToDesk3 DeskWin send} FrameCanvas /new ButtonItem send
	    160 -60 /move 3 index send def
	/b4 (All     ) {/ToAll DeskWin send} FrameCanvas /new ButtonItem send
	    230 -60 /move 3 index send def
	/b5 (Next Window) {/HiliteNext DeskWin send}
	    	FrameCanvas /new ButtonItem send
	    70 -110 /move 3 index send def
%	/b6 (Find) {/Find DeskWin send} FrameCanvas /new ButtonItem send
%	    110 -110 /move 3 index send def
	/b7 (Reset) {/Reset DeskWin send} FrameCanvas /new ButtonItem send
	    180 -110 /move 3 index send def
	/b8 (InDesk1) {/PutDesk1 DeskWin send} FrameCanvas /new ButtonItem send
	    20 -160 /move 3 index send def
	/b9 (InDesk2) {/PutDesk2 DeskWin send} FrameCanvas /new ButtonItem send
	    90 -160 /move 3 index send def
	/b10 (InDesk3) {/PutDesk3 DeskWin send} FrameCanvas
	    /new ButtonItem send
	    160 -160 /move 3 index send def
	/b11 (In All) {/PutAll DeskWin send} FrameCanvas /new ButtonItem send
	    230 -160 /move 3 index send def


	% Resize them all for the first time
	[b1 b2 b3 b4 b5 b7 b8 b9 b10 b11] {
	    {
		location ObjectWidth ObjectHeight reshape paint
	    } exch send
	} forall

	% Start processes for listening to buttone
	[b1 b2 b3 b4 b5 b7 b8 b9 b10 b11] forkitems
    } def

    % Repaint the buttons whenever frame controls repainted
    /PaintFrameControls {
	/PaintFrameControls super send
	[b1 b2 b3 b4 b5 b7 b8 b9 b10 b11] {
	    { paint } exch send
	} forall
    } def

    % The /new method for this class only allows one /contolWin to Exist
    /new {				% only allow one to exist!
	controlWin null eq
	{
	    /new super send		% get the window
	    dup /controlWin exch def	% keep track of this one
	    begin			% open the new dict
	    end
	    500 500 310 200 /reshape controlWin send
	    /map controlWin send	% make visible
	} if
	DeskWin /currentWin controlWin put	% an initial value
    } def

    % Try to release all VM (so no zombie canvases)
    /DestroyClient {			% reset /controlWin
	DeskWin /Every null put
	clearall			% clear the rest of them

	DeskWin /currentWin null put
	DeskWin /controlWin null put	% before "super send" ??
					% else get ZOMBIE canvas
	/DestroyClient super send
    } def

    % Reset: clear everything and start over
    /Reset {
	{/map self send} AllWin		% just in case!
	/Every null def
	clearall			% clear the rest of them
	/currentWin controlWin def
	FindWindows
    } def

    % Add a window to /Every.  Each window is a key in a dict, the
    % value assoc w/ the key is an index.  These indices are incremented
    % when the win is accessed thru /NextWindow.  When no more values in the
    % dict match /UniqueCycle, a new cycle begins.
    /AddWin {				% window => -
%	dup				% => win win
%	[ exch ] (Add Window:%\n) exch dbgprintf % => win
	Every exch UniqueCycle put	% put window in /Every dict
    } def
	
    % Find every window and place in every
    /FindWindows {			% - => -
%	(IN FindWindows!\n) [ ] dbgprintf
	/Every null def			% free VM
	/Every MaxWin dict def		% a new dict
	% save each window
	{self /AddWin DeskWin send} AllWin
    } def

    % For debugging only, print all windows in dict /Every
    /PrintWindows {
	Every {
	    [ exch ] (Value:%) exch dbgprintf
	    [ exch ] (Key:%\n) exch dbgprintf
	} forall
    } def

    % Check if a window (kept in a list) is a ZOMBIE, return null if it is
    /CheckZombie {			% win => win/null
	dup				% win win
	/FrameEventMgr get		% win val/null?
%	(In ZOMBIE:) [] dbgprintf
%	dup [exch] (%\n) exch dbgprintf
	null eq {pop null} if		% replace w/ null if eq null
    } def

    % This function finds a window in the current cycle, or null if 
    % there are none.  A result is returned AND /currentWin is left
    % the value too.
    /NextInWindowCycle {
	/currentWin null def		% set to null
	Every {				% => win value 
	    UniqueCycle eq {
		dup			% => win win
		Every exch UniqueCycle	% => win dict win unique
		    1 add put		% => win
		/currentWin exch def
		exit			% exit the loop
	    } {
		pop			% suck up the win
	    } ifelse
	} forall
	currentWin				% the return value
    } def

    % Move on to the next cycle if cycling thru windows and none
    % found with NextWindow
    /NextCycle {
	/UniqueCycle UniqueCycle 1 add def
    } def

    % This function continually returns a new window each time called.
    /NextWindow {			% => win
	NextInWindowCycle dup null eq {
	    pop				% throw away null??
	    NextCycle NextInWindowCycle% begin next cycle
	} if				% otherwise, return the win
    } def

    % Hilite Window by bringing to top, hiliting
    /HiliteWindow {			% win => -
	dup /totop exch send		% bring to top
	dup /unmap exch send		% unmap it
	/map exch send			% map it
    } def

    % Hilite next window and select it
    /HiliteNext {			% - => win
	NextWindow HiliteWindow
    } def

    % Given a dict of windows, map only those
    /MapDesk {				% dict => -
	% Can't use 'AllWin' because it blocks
	Every {
	    pop				% get rid of value
	    CheckZombie			% see if win is a zombie
	    dup null eq			% => win/null f/t
	    {pop}
	    {/unmap exch send}
	    ifelse
	} forall
	/map controlWin send		% map the control window
	% DEBUG
%	dup				% dup the dict
%	{[exch] (Value:%) exch dbgprintf
%	 [exch] (Key:%\n) exch dbgprintf
%	} forall
	% (dict is still on the stack)
	{
	    pop				% get rid of value
	    CheckZombie dup null eq {pop} {/map exch send} ifelse
	} forall
    } def

    /MapAll {				% this one's easy
	{/map self send} AllWin
    } def

    % Put the current window, currentWin, in a desk
    /PutDesk {				% dict => -
	currentWin				% => dict currentWin
	UniqueCycle put
    } def

    % The methods called from the button's notify procs
    /ToDesk1 {Desk1 MapDesk} def
    /ToDesk2 {Desk2 MapDesk} def
    /ToDesk3 {Desk3 MapDesk} def
    /ToAll   { {/map self send} AllWin } def
    /PutDesk1 {Desk1 PutDesk} def
    /PutDesk2 {Desk2 PutDesk} def
    /PutDesk3 {Desk3 PutDesk} def
    /PutAll   {Desk1 PutDesk Desk2 PutDesk Desk3 PutDesk} def
classend def    
end					% of systemdict!

} if					% matches "known not" at beginning


framebuffer /new DeskWin send pop	% throw away?
/FindWindows DeskWin send