[comp.windows.news] X11/NeWS Tab Frames

don@CS.UMD.EDU (Don Hopkins) (02/11/90)

This is an implementation of tab frames, on top of the Open Look frame
class in tNt (the NeWS toolkit, aka NDE). It runs under Open Windows
1.0 (aka X11/NeWS). 

Instead of having the title at the top of the window, tab frames have
the title in a tab stuck to the window edge, that you can drag around to
any of the four edges by pressing the Adjust (middle) button down over
the title (when the cursor is a + sign) and dragging it to where you
want. Notice that the tab sticks to the end of an edge until you
actually go around the corner. (This is so it's easy to move it to the
end of an edge.)

Double clicking the Point (left) button on the border or tab "zooms" the
window as usual, except that the window is reshaped so it's on the left
edge of the screen, full height, with its tab along its right edge, just
below the previous tab. It is convenient to zoom a bunch of windows and
have them all overlapping, but with all of their tabs visible (showing
their titles). You can click the Point (left) button on the tab to bring
the window to the top. It could be smarter about the layout, but this is
just a start.

Also I have added resize controls to the window edges to complement
the Open Look resize corners. These are useful when you want to change
the window height without changing the width, or vice verse. (You'll
want to do that a lot with tab frames, like when you want to make a
zoomed window shorter.)

Least importantly, I added a fake 3-D look (wooo golly gee) to the
tabs.  When a tab frame has the input focus, its tab appears to pop
out (with beveled edges around the window title). Personally I don't
think 3-D window frames are particularly useful (unless they *really*
*are* 3-D, and fly out at you like the SGI button demo). It would be
poor form to make the window borders thicker and slow down the drawing
for no other reason than fake 3-D.  In this implementation, only the
tab of the window which has the input focus (where keyboard input is
directed) is displayed in fake 3-D.  This is less distracting than
having *all* the tabs in fake 3-D, and makes it painfully obvious
*which* tab frame has the keyboard focus.

I would appreciate feedback: what kind of automatic window and tab
layout policy would be most convenient, least obnoxious? I am thinking
of implementing snap dragging, to help line up windows and tabs, but for
now the Open Look screen edge snapping works pretty well.  One of these
days I will incorporate pie menus into the tab frame window manager (ala
quickwin.ps).

Summer before last, I implemented tab frames under NeWS 1.1 for UniPress
Emacs V2.20, and found them very convenient for editing lots of files at
once (UniPress Emacs supports multiple windows, which can get very
confusing without the help of the tabs). The idea is totally unoriginal,
as useful for window managment as it is for address books, so feel free
to implement it for other window systems without fear of being sued.  My
code is freely redistributable.

For a demo, put the enclosed code in the file NeWS/tabframe.ps, and type:

% psh NeWS/tabframe.ps
% psh
/demo TabBaseFrame send
/demo TabCommandFrame send

If you are pleased, and want to install tab frames as your default frame
class (so you get them around all your X windows (if you run pswm) as
well as your NeWS windows), put the following into your .startup.ps
file:

UserProfile /OpenLookFrame {
  ClassFrame /DefaultClass { TabFrame } put
} put

[
  /TabFrame (NeWS/tabframe.ps)
] DefineAutoLoads

There are still a few rough edges (of course the most obvious rough edge
is considered a feature), but I plan on developing this further, so
please tell me about any problems you have, and send me suggestions for
enhancements, top of the head comments, incoherent raving brainstorms,
etc.

	-Don

Here is the enclosed code (NeWS/tabframe.ps):

%!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% tNt Tab Frames, release 1.0
% Copyright (C) 1990
% By Don Hopkins, University of Maryland Human Computer Interaction Lab
% Started, May 13 1989.
% Reimplemented mostly from scratch for X11/NeWS FCS.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Copyright (C) 1990 by Don Hopkins. All rights reserved.
% This program is provided for unrestricted use, provided that this 
% copyright message is preserved. There is no warranty, and no author 
% or distributer accepts responsibility for any damage caused by this 
% program. 
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% To get TabFrame as the default frame class automatically upon startup, put 
% the following into your .startup.ps file:
%
% UserProfile /OpenLookFrame {
%   ClassFrame /DefaultClass { TabFrame } put
% } put
% 
% [
%   /TabFrame (NeWS/tabframe.ps)
% ] DefineAutoLoads
% 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

systemdict begin

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% TabFrame
%

/TabFrame OpenLookFrame
dictbegin
  /TabEdge /Top def
  /TabPosition 0 def
dictend
classbegin
  /ThreeDee? true def
%  /ThreeDee? framebuffer /Color get def
  /Contrast .1 def
  /Bright .75 dup dup rgbcolor def
  /Dim .25 dup dup rgbcolor def
  /LabelMinWidth 8 def
  /SelStroke 2 def
  /UnSelStroke 1 def

  /ZoomX 0 def
  /ZoomY 0 def
  /ZoomWidth 512 def
  framebuffer true getbbox /ZoomHeight exch def pop pop pop
  /ZoomTabEdge /Right def
  /ZoomTabPos 1 def
  /ZoomTabPosition {
    ZoomTabPos TabHeight ZoomHeight div sub
    dup 0 lt { pop 1 } if
    /ZoomTabPos 1 index store
  } def

  /LabelCreate { % - => -
      /Label [
        { /TabNotify parent send pop } OpenLookTabFrameLabel 
      ] /addclient self send
  } def

  /ReshapeCreate { % - => -
      /Reshape
      [{/ReshapeNotify /parent self send send} OpenLookTabFrameCorners]
      /addclient self send
  } def

  /ReshapeNotify { % object => -
      gsave
 	pop %
 	Parent setcanvas
 	currentcursorlocation
        /stretchany self send
      grestore
  } def

  /stretchany { % event|x y => -
      dup type /eventtype eq {
	  begin XLocation YLocation end
      } if

      20 dict begin
        /y exch def /x exch def
	bbox unfittab
	2 copy /h exch def /w exch def
	rect2points
	/y1 exch def /x1 exch def
	/y0 exch def /x0 exch def
	x  x0 w .3 mul add  le /Left {
	  x  x0 w .7 mul add ge  /Right /Middle ifelse
	} ifelse % xpart
	y  y0 h .3 mul add  le  /Bottom {
	  y  y0 h .7 mul add ge  /Top /Middle ifelse
	} ifelse % xpart ypart
	dup /Bottom eq {
	  /y0 y1 /y1 y0 def def
	} if
	1 index /Left eq {
	  /x0 x1 /x1 x0 def def
	} if
	(%%) sprintf cvn
	x1 y1 x1 x sub y1 y sub 	% edge x1 y1 x1-x y1-y
	[x0 y0] cvx 			% edge x1 y1 x1-x y1-y X0Y0proc
      end
      5 index {
	/MiddleRight /MiddleLeft {
          {begin XLocation DeltaX add Y1 end}
        }
	/TopMiddle /BottomMiddle /MiddleMiddle {
          {begin X1 YLocation DeltaY add end}
	}
	/Default {
          {begin XLocation DeltaX add YLocation DeltaY add end}
	}
      } case
      [/xhair /xhair_m Parent]
      {
	  InitOverlay
	  InstallXYProcs
	  /DeltaY exch def /DeltaX exch def
	  /Y1 exch def /X1 exch def
	  /Edge exch def
	  /Moved? false def
      }{
	  Changed? {erasepage BBox fittab /path self send stroke} if
	  /Moved? true def
      }{
	  erasepage
	  null SetGlobalCursor
	  Moved? {
            x0 x min y0 y min x0 x sub abs y0 y sub abs 
	    invalidatetab
	    % /TabWidth unpromote /HeaderWidth unpromote
	    fittab rect2points
	    true
	  } false ifelse
      }
      /UpTransition getfromuser {
	  /anchorbox self send
	  /reshape self send
      } if
  } def

  /TabNotify { % canvas => -
    pop
    DragTab
  } def

  /DragTab { % - => -
    gsave
      Parent setcanvas
      10 dict begin
	bbox unfittab rect2points
	/top exch def /right exch def
	/bottom exch def /left exch def

	parent setcanvas
	null null
	[/xhair /xhair_m Parent]
	{ InitOverlay InstallXYProcs
	}
	{ Changed? {

	    TabEdge {
	      /Left {
		x left gt {
		  y top gt {
		    /Top setedge
		  } {
		    y bottom lt {
		      /Bottom setedge
		    } {
		      x right gt {
			/Right setedge
		      } if
		    } ifelse
		  } ifelse
		} if
	      }
	      /Right {
		x right lt {
		  y top gt {
		    /Top setedge
		  } {
		    y bottom lt {
		      /Bottom setedge
		    } {
		      x left lt {
			/Left setedge
		      } if
		    } ifelse
		  } ifelse
		} if
	      }
	      /Top {
		y top lt {
		  x left lt {
		    /Left setedge
		  } {
		    x right gt {
		      /Right setedge
		    } {
		      y bottom lt {
			/Bottom setedge
		      } if
		    } ifelse
		  } ifelse
		} if
	      }
	      /Bottom {
		y bottom gt {
		  x left lt {
		    /Left setedge
		  } {
		    x right gt {
		      /Right setedge
		    } {
		      y top gt {
			/Top setedge
		      } if
		    } ifelse
		  } ifelse
		} if
	      }
	    } case

	    TabEdge {
	      /Left /Right {
		y bottom sub
		TabHeight .5 mul sub
		top bottom sub TabHeight sub 1 max div
		0 max 1 min
	      }
	      /Top /Bottom {
		x left sub
		TabWidth .5 mul sub
		right left sub TabWidth sub 1 max div
		0 max 1 min
	      }
	    } case
	    setposition

	    erasepage
	    left bottom right 2 index sub top 2 index sub % x y w h
	    fittab
	    /path self send stroke
	  } if }
	{erasepage null SetGlobalCursor Changed?}
	[/UpTransition /DownTransition]
	getfromuser {
	  left bottom right 2 index sub top 2 index sub % x y w h
	  ?validate % XXX
	  fittab
	  minsize % x y w h mw mh
	  3 -1 roll max % x y w mw H
	  3 1 roll max % x y H W
	  exch % x y W H
	  /reshape self send
	} {
	  % do something appropriate
	} ifelse
      end % tempdict
    grestore
  } def

  /invalidatetab {
    /TabWidth unpromote /TabHeight unpromote
    /TabX unpromote /TabY unpromote
    /HeaderWidth unpromote /HeaderHeight unpromote
  } def

  /setedge { % /Left|/Right|/Top|/Bottom => -
    /TabEdge exch store
    invalidatetab
    invalidate
  } def

  /resetedge { % edge => -
    bbox unfittab
    5 -1 roll setedge
    ?validate % XXX?
    fittab reshape
  } def

  /setposition { % 0..1 => -
    /TabPosition exch store
    invalidatetab
    invalidate
  } def

  /BorderLeft { % - => l
    BorderEdge
    TabEdge /Left eq { TabWidth add } if
  } def

  /BorderRight { % - => r
    BorderEdge
    TabEdge /Right eq { TabWidth add } if
  } def

  /BorderBottom { % - => b
    /BorderBottom super send
    TabEdge /Bottom eq { TabHeight add } if
  } def

  /BorderTop { % - => t
    BorderEdge
    TabEdge /Top eq { TabHeight add } if
  } def

  /TabSize { % - => w h
    BorderEdge dup add
%    ThreeDee? { SelStroke add } if
    dup HeaderWidth add
    exch HeaderHeight add
  } def

  /CalcTabXY { % width height => x y
    TabEdge {
      /Left { % width height
	exch pop 0 exch % 0 height
	TabHeight sub TabPosition mul round % 0 y
      }
      /Right { % width height
	exch TabWidth sub % height x
	exch TabHeight sub TabPosition mul round % x y
      }
      /Top { % width height
	exch TabWidth sub TabPosition mul round % height x
	exch TabHeight sub % x y
      }
      /Bottom { % width height
	pop TabWidth sub TabPosition mul round % x
	0 % x 0
      }
    } case
  } def

  /validate {
    invalidatetab
    /BorderLeft unpromote
    /BorderLeft /BorderLeft self send promote
    /BorderRight unpromote
    /BorderRight /BorderRight self send promote
    /validate super send
  } def

  /TabX {
    TabXY /TabY exch promote /TabX exch promote
    TabX
  } def

  /TabY {
    TabXY /TabY exch promote /TabX exch promote
    TabY
  } def

  /TabXY { % - => x y
    Width Height CalcTabXY
  } def

  /TabWidth {
    TabSize /TabHeight exch promote /TabWidth exch promote
    TabWidth
  } def

  /TabHeight {
    TabSize /TabHeight exch promote /TabWidth exch promote
    TabHeight
  } def

  /HeaderWidth {
    HeaderSize /HeaderHeight exch promote /HeaderWidth exch promote
    HeaderWidth
  } def

  /HeaderHeight {
    HeaderSize /HeaderHeight exch promote /HeaderWidth exch promote
    HeaderHeight
  } def

  /HeaderSize { % - => w h
    0 0 % W H
    /Label /getbyname self send {
      /preferredsize exch send % W H w h
      3 -1 roll max % W w H
      3 1 roll add % H W
      LabelMinWidth max HeaderPadding add
      exch % W H
    } if
    /Pin /getbyname self send {
      /minsize exch send % W H w h
      3 -1 roll max % W w H
      3 1 roll add % H W
      HeaderPadding add
      exch % W H
    } if
    /Close /getbyname self send {
      /minsize exch send % W H w h
      3 -1 roll max % W w H
      3 1 roll add % H W
      HeaderPadding add
      exch % W H
    } if
    2 {
      exch dup 0 ne {
        HeaderPadding dup add add
      } if
    } repeat
  } def

  % erase the current frame header. Used when changing busy state.
  %
  /EraseHeader { % - => -
    FillColor setcolor
    TabX BorderEdge add TabY BorderEdge add
    HeaderWidth HeaderHeight
    rectpath fill
  } def

  % Paint the frames current focus state.
  %
  /PaintHeader { % busy? focus? => -
    % Only paint the header if we have one of these ornaments.
    %
    Label Close or Pin or {
      exch {
	pop
	BusyColor setcolor
	TabX BorderEdge add TabY BorderEdge add
	HeaderWidth HeaderHeight
	rectpath fill
      } {
%	{TextColor} {FillColor} ifelse
%	setcolor

%	ClickToType {
%	  TabX BorderEdge add TabY BorderEdge add
%	  HeaderWidth HeaderHeight
%	  rectpath fill
%	}{
	  % Draw the two lines for follow-mouse focus.
	  % Having a different highlight depending on what
	  % focus mode got us here sounds like donkey-doo but
	  % it's in the spec so we implement it.
	  %
	  ThreeDee? {
	    gsave 10 dict begin
	      /focus? 1 index def
	      { 
		framebuffer /Color get {
	          FillColor colorhsb 3 copy % h s b h s b
	          Contrast sub 0 max hsbcolor % h s b dim
		  4 1 roll % dim r g b
	          Contrast add 1 min hsbcolor % dim bright
		} {
		  Dim Bright
		} ifelse
	      } {
	        FillColor dup % dim bright
	      } ifelse
	      /l BorderEdge SelStroke sub def

	      TabX BorderEdge add  TabY BorderEdge add  moveto
	      l neg dup rlineto
	      0  HeaderHeight l dup add add  rlineto
	      HeaderWidth l dup add add  0  rlineto
	      l neg dup rlineto
	      HeaderWidth neg  0  rlineto
	      0  HeaderHeight neg  rlineto
	      closepath
	      setcolor
	      fill

	      TabX BorderEdge add .75 add
	      TabY BorderEdge add .75 sub  moveto
	      l neg dup rlineto
	      HeaderWidth l dup add add  0  rlineto
	      0  HeaderHeight l dup add add  rlineto
	      l neg dup rlineto
	      0  HeaderHeight neg  rlineto
	      HeaderWidth neg  0  rlineto
	      closepath
	      setcolor
	      fill

	      TabX BorderEdge add
	      TabY BorderEdge add .25 add
	      2 copy % x y x y
	      HeaderWidth .5 sub HeaderHeight .5 sub % x y x y w h
	      rectpath % x y
	      2 copy moveto  l neg dup rlineto
	      2 copy HeaderHeight add .5 sub moveto  l dup neg exch rlineto
	      exch HeaderWidth add .5 sub exch 2 copy moveto
	      l dup neg rlineto
	      HeaderHeight add .5 sub moveto  l dup rlineto

	      focus? {TextColor} {FillColor} ifelse setcolor
	      stroke

	    end grestore
	  } {
            {TextColor} {FillColor} ifelse
	    setcolor
	    currentlinewidth
	    2 setlinewidth
	    TabX BorderEdge add TabY BorderEdge add
	    HeaderWidth HeaderHeight
	    rectpath stroke
	    setlinewidth
	  } ifelse
%	} ifelse
      } ifelse
    } {
      pop pop
    } ifelse
  } def

  % Overide: Take the label, the footer and the resize corners
  % into account.
  %
  /MinSize { % - => minwidth minheight
      %   Because of the unique factorization of client layout
      %   from client creation we must make sure the frame has
      %   been correctly layed out here in order to get the
      %   right minsize.
      %
      /?validate self send
      /Client getbyname { /minsize exch send } { 0 0 } ifelse
      fitclient
      TabEdge dup /Left eq exch /Right eq or {
        TabHeight max
      } {
        exch TabWidth max exch
      } ifelse
  } def

  % Overide: Take the label, the footer and the resize corners
  % into account.
  %
  /PreferredSize { % - => preferredwidth preferredheight
    %   Because of the unique factorization of client layout
    %   from client creation we must make sure the frame has
    %   been correctly layed out here in order to get the
    %   right preferredsize.
    %
    /?validate self send
    /PreferredSize OpenLookFrame /SuperSend load exec % XXX
  } def

  /SuperSend /supersend load def

% Layout

  /FooterLayout { % - => -
    /Left /getbyname self send {
      BorderEdge 2 mul 1 add
      dup TabEdge /Left eq { TabWidth add } if
      exch
      SelStroke FooterPad add
      TabEdge /Bottom eq { TabHeight add } if
      Width
      TabEdge dup /Right eq exch /Left eq or { TabWidth sub } if
      .5 mul 3 -1 roll sub 1 sub
      /preferredsize 4 index send exch pop
      /reshape 6 -1 roll send
    } if
    /Right /getbyname self send {
      Width
      TabEdge dup /Right eq exch /Left eq or { TabWidth sub } if
      .5 mul
      dup TabEdge /Left eq { TabWidth add } if
      exch
      SelStroke FooterPad add
      TabEdge /Bottom eq { TabHeight add } if
      exch BorderEdge 2 mul 1 add sub
      /preferredsize 4 index send exch pop
      /reshape 6 -1 roll send
    } if
  } def

  /ReshapeLayout { % - => -
    /Reshape /getbyname self send {
      bbox unfittab
      /reshape 6 -1 roll send
    } if
  } def

  /CloseLayout { % - => -
    /Close /getbyname self send { % close
      TabX BorderEdge add HeaderPadding add % close x
      /minsize 2 index send % close x w h
      HeaderHeight 1 index sub % close x w h hh-h
      .5 mul .5 add truncate % close x w h _(hh-h)/2+.5_
      TabY BorderEdge add add % close x w h y
      3 1 roll % close x y w h
      /reshape 6 -1 roll send %
    } if
  } def

  /PinLayout { % - => -
    /Pin /getbyname self send {
      TabX BorderEdge add HeaderPadding add % pin x
      /Close getbyname self send {
	/minsize exch send pop add
	HeaderPadding add
      } if
      /minsize 2 index send % pin x w h
      HeaderHeight 1 index sub % pin x w h hh-h
      .5 mul % pin x w h (hh-h)/2
      TabY BorderEdge add add % pin x w h y
      3 1 roll % pin x y w h
      /reshape 6 -1 roll send %
    } if
  } def

  /LabelLayout { % - => -
      /Label /getbyname self send {
	  TabX BorderEdge add HeaderPadding add % x
	  /Pin /getbyname self send {
	      /bbox exch send
	      rect2points pop 3 1 roll pop pop
	      HeaderPadding add max
	  } if
	  /Close /getbyname self send {
	      /bbox exch send
	      rect2points pop 3 1 roll pop pop
	      HeaderPadding add max
	  } if
	  /preferredsize 2 index send % x w h
	  exch 8 max exch
	  HeaderHeight 1 index sub .5 mul % x w h y
	  TabY BorderEdge add add % x w h y
	  3 1 roll % x y w h
	  /reshape 6 -1 roll send
      } if
  } def

  /setlabel { % graphic|thing|null => -
      /Label /getbyname self send {
	invalidate
	/setlabel exch send
      } {pop} ifelse
  } def

  % This is the next available position to place a window at.
  % It is where the upper left corner of the window should go.
  /NextPosition null store
  /WindowYIncrement -37 store
  /WindowXIncrement 0 store
  /WindowRows 0 store

  /setgravity {   % /Left|/Right|/Top|/Bottom => -
      /Gravity exch store
      /NextPosition null store
      /WindowRows 0 store
      Gravity {
	  /UpperLeft {
	      /InitialDefaultPosition { % w h => x y
		  pop pop
		  /bbox /parent self send send
		  exch pop add
	      } def
	      /WindowYIncrement -37 store
	      /WindowXIncrement 0 store
	  }
	  /UpperRight {
	      /InitialDefaultPosition { % w h => x y
		  pop
		  /bbox /parent self send send
		  3 -1 roll add 3 1 roll add 3 -1 roll sub exch
	      } store
	      /WindowYIncrement -37 store
	      /WindowXIncrement 0 store
	  }
	  /LowerLeft {
	      /InitialDefaultPosition { % w h => x y
		  exch pop
		  /location /parent self send send
		  3 -1 roll add
	      } store
	      /WindowYIncrement 37 store
	      /WindowXIncrement 0 store
	  }
	  /LowerRight {
	      /InitialDefaultPosition { % w h => x y
		  /bbox /parent self send send
		  pop 3 -1 roll add 4 -1 roll sub exch
		  3 -1 roll add
	      } store
	      /WindowYIncrement 37 store
	      /WindowXIncrement 0 store
	  }
	  /Default {}
      } case
  } def

  /UpperLeft setgravity

  /path { % x y w h => -
    10 dict begin
      /mat matrix currentmatrix def
      /h exch def /w exch def
      translate
      TabEdge {
	/Left {
	  TabWidth 0 moveto
	  0 h TabHeight sub TabPosition mul round rlineto
	  TabWidth neg 0 rlineto
	  0 TabHeight rlineto
	  TabWidth 0 rlineto
	  TabWidth h lineto
	  w h lineto
	  w 0 lineto
	  closepath
	}
	/Right {
	  h TabHeight sub TabPosition mul round
	  0 0 moveto
	  0 h rlineto
	  w TabWidth sub 0 rlineto
	  0 1 index TabHeight add h sub
	  rlineto
	  TabWidth 0 rlineto
	  0 TabHeight neg rlineto
	  TabWidth neg 0 rlineto
	  0 exch neg rlineto
	  closepath
	}
	/Top {
	  0 0 moveto
	  0 h TabHeight sub rlineto
	  w TabWidth sub TabPosition mul round 0 rlineto
	  0 TabHeight rlineto
	  TabWidth 0 rlineto
	  0 TabHeight neg rlineto
	  w h TabHeight sub lineto
	  w 0 lineto
	  closepath
	}
	/Bottom {
	  0 TabHeight moveto
	  0 h TabHeight sub rlineto
	  w 0 rlineto
	  0 h TabHeight sub neg rlineto
	  w TabWidth sub TabPosition mul round TabWidth add TabHeight lineto
	  0 TabHeight neg rlineto
	  TabWidth neg 0 rlineto
	  0 TabHeight rlineto
	  closepath
	}
      } case
      mat setmatrix
    end % tempdict
  } def

  % given tabbed frame bbox, returns bbox of frame w/out tab
  /unfittab { % x y w h => x' y' w' h'
      TabEdge {
        /Left {
	  4 -1 roll TabWidth add 4 1 roll
	  exch TabWidth sub exch
	}
	/Right {
	  exch TabWidth sub exch
	}
	/Top {
	  TabHeight sub
	}
	/Bottom {
	  3 -1 roll TabHeight add 3 1 roll
	  TabHeight sub
	}
      } case
  } def

  % given untabbed frame bbox, returns bbox of frame with tab
  /fittab { % x' y' w' h' => x y w h
      TabEdge {
        /Left {
	  4 -1 roll TabWidth sub 4 1 roll
	  exch TabWidth add exch
	}
	/Right {
	  exch TabWidth add exch
	}
	/Top {
	  TabHeight add
	}
	/Bottom {
	  3 -1 roll TabHeight sub 3 1 roll
	  TabHeight add
	}
      } case
  } def

  % Return the corner farthest from the given x y.
  % Coordinates are in the parents space.
  %
  /farthestcorner { % x y =>  x' y'
      /bbox self send
      4 copy rect2points 10 4 roll
      unfittab rect2points % x y x0 y0 x1 y1 X0 Y0 X1 Y1
      3 index 6 index sub abs 2 index 7 index sub abs gt
      {9} {7} ifelse index
      3 index 6 index sub abs 2 index 7 index sub abs gt
      {9} {7} ifelse index
      mark 13 3 roll cleartomark       % x y X0 Y0 X1 Y1 x' y' => x' y'
  } def

  /StrokeCanvas { % color inset => -
      % REMIND: Should this use thick lines?
      exch setcolor
      gsave
        dup add setlinewidth
        /bbox self send /path self send
        stroke
      grestore
  } def

  % Grow to full (screen) size  (true) or revert to normal size (false).
  %
  /zoom { % bool => -
      dup /zoomed? self send eq {pop} {
	  gsave                       % bool
	  framebuffer setcanvas       % bool
	      {
		  /UnZoomedSize [
		    /bbox self send unfittab 
		    TabEdge TabPosition ] promote
		  ZoomTabPosition setposition 
		  ZoomTabEdge setedge
		  ZoomX ZoomY ZoomWidth ZoomHeight
		  ?validate % XXX?
		  fittab
	      }{
		  UnZoomedSize aload pop
		  setposition setedge
		  ?validate % XXX?
		  fittab
		  /UnZoomedSize /unpromote self send
	      } ifelse
	      /reshape self send
	  grestore
      } ifelse
      /mapped? self send not {true /open self send} if
  } def

  % make a default base frame with footer & grow control; then call vanilla
  % initialization procs for labels, footer, reshape etc.
  /demo { % - => window
    10 dict begin
      % make a base frame and initialize it:
      [ FlexBag ]
      [/Footer true] framebuffer
	  /newdefault ClassName load send
      /frame exch def
      /bag /client frame send def
      [ (Top) null {
	  /Top /resetedge /parent target send send }
        (Bottom) null {
	  /Bottom /resetedge /parent target send send }
	(Left) null {
	  /Left /resetedge /parent target send send }
        (Right) null {
	  /Right /resetedge /parent target send send }
      ] framebuffer /new OpenLookMenu send
      /setmenu bag send

      /Right /setedge frame send
      random /setposition frame send
      null
      random random 4 div  random 4 div .75 add hsbcolor 
      null
      /setcolors frame send
      null
      random  random  random 4 div .75 add hsbcolor 
      null 
      /setcolors bag send
      (Tab Frame) /setlabel frame send
      (Take a look) (and feel free!) /setfooter frame send
      200 100 200 500 /reshape frame send
      /activate frame send
      /map frame send
      frame
    end
  } def

classend def

/TabBaseFrame [OpenLookBaseFrame TabFrame] []
classbegin classend def

/TabPropertyFrame [OpenLookPropertyFrame TabFrame] []
classbegin classend def

/TabCommandFrame [OpenLookCommandFrame TabFrame] []
classbegin classend def

/TabHelpFrame [OpenLookHelpFrame TabFrame] []
classbegin classend def

{ /BaseFrameClass { TabBaseFrame } def
  /PropertyFrameClass { TabPropertyFrame } def
  /CommandFrameClass { TabCommandFrame } def
  /HelpFrameClass { TabHelpFrame } def
} ClassFrame send

{  /DefaultClass { TabBaseFrame } def } ClassBaseFrame send
{  /DefaultClass { TabPropertyFrame } def } ClassPropertyFrame send
{  /DefaultClass { TabCommandFrame } def } ClassCommandFrame send
{  /DefaultClass { TabHelpFrame } def } ClassHelpFrame send

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% OpenLookTabFrameCorners
%

/OpenLookTabFrameCorners OpenLookFrameCorners []
classbegin

    /Edges { % x y w h delta => -
	20 dict begin
	    /d exch def /h exch def /w exch def /y exch def /x exch def
	    /-d d neg def /D d 2 mul def /-D D neg def
	    /X x w add def /Y y h add def

	    x y h .5 mul add [ 0  d  d -D -d ] polyrectpath
	    X y h .5 mul add [ 0 -d -d  D  d ] polyrectpath
	    x w .5 mul add y [ d  d -D -d ] polyrectpath
	    x w .5 mul add Y [ d -d -D  d ] polyrectpath
	end
    } def

    % Override
    %
    % Hack: the path should be made from Corners; however, because
    % in 1.1 a fat line cannot intercept the clip, we use a fudge:
    % we make the path slightly larger than desired.
    %
    /path { % x y w h => -
	Delta 2 add 5 copy Corners Edges
    } def

    /PaintCanvas { % - => -
	% Fat line problem: we can't do the obvious:
	% FillColor setcolor clippath fill
	% 1 setlinequality 2 setlinewidth
	% StrokeColor setcolor clippath stroke

	.5 .5 Width 1 sub Height 1 sub Delta 5 copy Corners Edges
	FillColor setcolor gsave fill grestore
	StrokeColor setcolor stroke
    } def

classend def

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% OpenLookTabFrameLabel
%

/OpenLookTabFrameLabel [ClassControl OpenLookFrameLabel] []
classbegin

  /EventHandler { % event => -
    % callnotify puts self on stack and calls NotifyUser
    /callnotify self send
  } def

  /EventsConsumed /MatchedEvents def

  /ControlButton AdjustButton def	% Which mouse button to use

classend def

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

end % systemdict