[comp.windows.news] New Scroll data item

thaeler@hc.DSPO.GOV (Bret K. Thaeler) (11/17/88)

This is a NeWS item that allows you to specify a set data to be
put inside of it. It then allows you to scroll through this data
selecting stuff.

If you have ever seen the old Class Browser this code originally
did the scrolling of the item names and stuff. But, this code
has been cleaned up to remove cycles in the data to facilitate
garbage collection. This code also has just a whole bunch of
new options and capabilities.

A fairly complex example has been included at the bottom
of the first block of comments.

Happy NeWSing....

-------------------------------------------------------------------------------
Bret K. Thaeler               ARPA: thaeler@hc.dspo.gov |          ///  Amiga
Los Alamos National Labs, MEE-10     UUCP: hc!thaeler   |         ///  user
Programming Grunt                                       |   \\\  ///
Of: (505) 667-8495    Ho: (505) 662-3124                |    \\\///  And dam
I instinctively used the Vulcan death grip.             |     \///  proud of it


--------------------------- Starts here ---------------------------
% A large portion of this code originaly came from:
%%%%
%%%%
%%%%  This code was mostly written in August 1987 but was revised to work with
%%%%  NeWS 1.1 in May 1988.  Feel free to use it as you will.
%%%%
%%%%  Bruce V. Schwartz
%%%%  Sun Microsystems
%%%%  bvs@sun.com
%%%%  
%
%  Feel free to use this code as you like. Any questions, bugs, ideas,
%  problems, whatever would be grealy appreciated.
%
%  Bret K. Thaeler
%  Los Alamos National Labs (MEE-10)
%  thaeler@hc.dspo.gov
%
%
%  THIS is VERSION 1.0.....
%
%
% ScrollDataItem:
%	This item provides a simple way to display information in a 
%	scrollable data item. Furthermore the data display may be selected
%	causing it to by highlight and a notify routine to be called.
%
% Usefull methods:
%	parent_canvas notify_proc /new => instance
%
%	array,string /setdata => -
%		Array is an array of the data to be displayed. Each element
%		of the array can have the following forms...
%			string		-- display a simple string in scroll area.
%			array		-- parse each elemnt of the array in RIGHT
%							to LEFT order. Each element of the array
%							can be of the following forms...
%				string	-- display a simple string
%				array	-- parse array righ to left
%				color	-- set color for future commands
%				font	-- set font for future text
%				x y		-- perform a releative move by x y
%				/name	-- display icon called 'name'
%		A given entry may be as complex as you want with multiple
%		strings being display in different fonts with different colors
%		all moved around with respect to each other intermixed with
%		icons. Even very comples entries are considered as ONE object
%		for the purposes of selecting it..
%		WARNING: markers may NOT be used in the arrays. If the are the
%		item will stop displaying at that point while leaving trash
%		on the stack...
%
%	item_number /deleteitem => -
%		Delete an item from the list of displayable objects.
%		If the item_number is out of range then the command will
%		be ignored. item_number is zero referenced.
%
%	array,string item_number /additem => -
%		Add an item to the list of displayable objects.
%		The item added will appear BEFORE or above the item forwhich
%		the item_number is specified. To append an item to the list
%		you must add the item before the the first item after the
%		end of the list. No blank nodes will be added. If you add
%		an item to the list before an item that past the end
%		of the list then a suffecient number of EMPTY entries will
%		be added to the list to allow the new entry to be added.
%
%	array,string item_number /changeitem => -
%		Replace the object associated with an item. If the 
%		item_number is out of range this command is ignored.
%
%	- /getdata => array
%		Array that has been build up with with setdata and the
%		change data routines.
%
%	x y w h /reshape => -
%		Move and reshape the item...
%
%
% Usefull Class and instance variables:
%	/AllowMultipleSelect?	(default false)
%		If this is set to true then the user can selected move
%		then one object. If this is false then selecting a different
%		object unselects the previous one.
%		WARNING: This command MUST be issued before any data is
%		downloaded set with /setdata. Else a /DeclareItemValueArray
%		must be issued.
%
%	/HScrollBar?			(default true)
%	/VScrollBar?			(default true)
%		Should these scroll bars be displayed...
%
%	/BufferColor			(default 1 1 1 rgbcolor)
%		Background color to be used for the scroll area
%
%	/ItemTextColor			(default 0 0 0 rgbcolor)
%		Default color to be used by ites in the scroll area.
%
%	/ItemHighLightColor		(default 0 0 0 rgbcolor)
%		When an item is selected the background behind the item is
%		filled with this color. The item it then draw with a default
%		color of 'BufferColor'
%
%	/ItemLabel				(default null)
%		A label used at the top of the item
%
%	/ItemLabelFont			(default Times-Roman.12)
%		The font used to display the item label
%
%	/ItemLabelColor			(default 0 0 0 rgbcolor)
%		The color used to display the item label
%
%	/ShowItemSeperators		(default false)
%		Should seperator lines be draw between items
%		in the scroll area.
%
%	/ItemSpacing			(default 4)
%		How much extra space should every item in the scroll
%		area be seperated by.
%
%	/ItemBorderColor		(default 0 0 0 rgbcolor)
%		Color of the item border.
%
%	/ItemFillColor			(default 1 1 1 rgbcolor)
%		Color to be used to fill scroll areas and item label.
%
%	/ItemValue
%		Value of last item selected. (zero referenced)
%		Possible values for this variables may include 'null'
%		which would indicate that nothing has been selected or
%		0-x indicating the item selected.
%
%	/ItemValueArray
%		Array of items selected. This item ONLY exists if
%		'AllowMultipleSelect?' is set true. Possible values for
%		elements of this array include 0 or 'null' indicating
%		that the item is not selected. A 1 indicates that the
%		item has been selected.
%		
%	For changes to the following variables to be seen a
%	/ShowObjectData call must be issued:
%		/BufferColor, /ItemTextColor, /ItemHighLightColor
%		/ShowItemSeperators, /ItemSpacing
%
% Example:
%
%	% can is assumed to be a predefined canvas.
%
%   /foo can {ItemValue (%\n) printf} /new ScrollDataItem send def
%   {
%	   /AllowMultipleSelect? true def
%  	   /HScrollbar? false def
%  	   /BufferColor 1 1 0 rgbcolor def
%	   /ItemTextColor 0 0 1 rgbcolor def
%	   /ItemHighLightColor .7 .7 .7 rgbcolor def
%	   50 50 200 315 reshape
%	   /ItemLabel (This is a TEST []) def
%	   /ItemLabelFont /Times-Bold findfont 24 scalefont def
%	   /ItemLabelColor 1 0 0 rgbcolor def
%	   /ShowItemSeperators true def
%   } foo send
%
%   /fooa [foo] def
%   /foom fooa forkitems def
%   fooa paintitems
%   
%   [
%   	(hello)
%   	[(hello) 20 0 (there)]
%   	[(hello small) /Times-Roman findfont 10 scalefont]
%   	[(hello big) /Times-Bold findfont 30 scalefont]
%   
%   	[(hello red) 1 0 0 rgbcolor]
%   
%   	[(hello 1) /Times-Bold findfont 10 scalefont
%   	(hello 2) /Times-Italic findfont 20 scalefont]
%   
%   	[(hello 3) /Times-Bold findfont 10 scalefont
%   	  20 20
%   	  (hello 4) /Times-Italic findfont 20 scalefont]
%   	[/boy1 /boy2]
%   	[/horse5 0 1 0 rgbcolor]
%   ] /setdata foo send
%   
%   (padding) 30 /additem foo send
%   } def
%
%


systemdict /SimpleScrollbar known not { (NeWS/liteitem.ps) run} if

/PicScrollbar SimpleScrollbar
dictbegin
	/MouseInItem? false def
	/ScrollMonitor null def
	/ScrollProcess null def
    /ScrollDelay 1 60 div 20 div def % 1/10 second
    /LastX null def
    /LastY null def
dictend
classbegin
    /new {
        /new super send
        begin
            /ScrollMonitor createmonitor def
            currentdict
        end
    } def

	/StartItem {
		TrackInterests {
			exch pop
			/ClientData get begin
				/MyOwner /MyOwner GetFromCurrentEvent def
			end
		} forall
		/StartItem super send
		/maketrackinterests self send
	} def


    /ClientDown { % - => -
        CurrentEvent 
        begin 
            XLocation YLocation 
        end
        /LastY exch def
        /LastX exch def
        SetScrollMotion
        /MouseInItem? true def
        HiliteItem
        DoScroll

        ScrollMonitor {
            ScrollProcess null ne {
                ScrollProcess killprocess
            } if
            /ScrollProcess { 
                InteractiveScroll 
            } fork def
        } monitor
    } def

    /InteractiveScroll {
        {
            ScrollDelay sleep
            ScrollMonitor { 
                EventInItem? { 
                    DoScroll 
                } if 
            } monitor
        } loop 
    } def

    /ClientUp { % - => - 
        ScrollMonitor { 
            ScrollProcess null ne {
                ScrollProcess killprocess 
            } if
            /ScrollProcess null def
        } monitor
        /MouseInItem? false def
        UnhiliteItem
        ItemValue ItemInitialValue ne { 
            NotifyUser
            % This is used when we are using this scroll bar in other items...
			% NOTE: If we are in the context of an event then the opperand
			%       stack should look like:
			%              |- EventMgrDict ... mark ...
			%       If We are not in the context of an event the
			%       'GetFromCurrentEvent' will error out after pushing the
			%       first object on the opperand stack onto the stack again.
			%       This will result in pushing an extra mark onto the stack...
			%       Look in 'GetFromCurrentEvent', 'CurrentEvent',
			%       'EventMgrDict'.
			/foobar
				count 1 sub index type /dicttype eq {
					mark {
						/Notify /MyOwner GetFromCurrentEvent send
					} stopped pop cleartomark
				} if
			pop
        } if
    } def

    /ClientDrag { % - => -
        CurrentEvent 
        begin 
            XLocation YLocation 
        end
        /LastY exch def
        /LastX exch def
        CheckItem
    } def

    /PaintBar { } def
    /EraseBox { } def
    /PaintButtons {
        /PaintButtons super send 
    } def

    /PaintBox { % - => - (paint box)
        gsave
            10 dict 
            begin

                /x 1 def
                /w ItemWidth 2 sub def

                %
                % fill in the whole bar
                %
                ItemFillColor setcolor
                x ButtonSize w ItemHeight ButtonSize dup add sub rectpath fill

                %
                % fill in the view_window bar
                %
                BarViewPercent 1 gt {
                    .5 setgray
                    x ButtonSize w ItemHeight ButtonSize dup add sub 
                    rectpath fill
                } {
                    ItemValue BarMin sub BarMax BarMin sub div 
                    BarViewPercent mul /lower exch def

                    /y ItemValue ValueToY def

                    ItemHeight ButtonSize dup add BoxSize add sub dup
                    lower mul /d exch def
                    BarViewPercent mul /h exch def

                    .5 setgray
                    x y d sub w h BoxSize add rectpath fill
                } ifelse

                %
                % fill in the small scroll box
                %
                ItemValue BoxPath
                BoxFillColor setcolor gsave fill grestore
                ItemBorderColor setcolor eofill
            end
            /ItemPaintedValue ItemValue def
        grestore

        NotifyUser
        % This is used when we are using this scroll bar in other items...
		% NOTE: If we are in the context of an event then the opperand
		%       stack should look like:
		%              |- EventMgrDict ... mark ...
		%       If We are not in the context of an event the
		%       'GetFromCurrentEvent' will error out after pushing the
		%       first object on the opperand stack onto the stack again.
		%       This will result in pushing an extra mark onto the stack...
		%       Look in 'GetFromCurrentEvent', 'CurrentEvent', 'EventMgrDict'.
		/foobar
			count 1 sub index type /dicttype eq {
				mark {
					/Notify /MyOwner GetFromCurrentEvent send
				} stopped pop cleartomark
			} if
		pop
	} def

		/EventInItem? { % - => bool
			ScrollMotion
			{
				/ScrollAbsolute { 
					false 
				}
				/ScrollPageForward % top
				{
					LastX dup 0 ge exch ButtonSize le
					LastY ItemValue ValueToY ButtonSize add ge
					LastY ItemHeight ButtonSize sub le
					and and and
				}
				/ScrollPageBackward % bottom
				{
					LastX dup 0 ge exch ButtonSize le
					LastY ButtonSize ge
					LastY ItemValue ValueToY le
					and and and
				}
				/ScrollLineForward % top
				{
					LastX 0 ge
					LastX ButtonSize le
					LastY ItemHeight ButtonSize sub ge
					LastY ItemHeight le
					and and and
				}
				/ScrollLineBackward % bottom
				{
					LastX 0 ge
					LastX ButtonSize le
					LastY 0 ge
					LastY ButtonSize le
					and and and
				}
			} case
		} def 

		/CheckItem {
			ScrollMotion
			{
				/ScrollAbsolute {
					DoScroll
				}
				/ScrollPageForward % top
				{
					/MouseInItem? EventInItem? def
				}
				/ScrollPageBackward % bottom
				{
					/MouseInItem? EventInItem? def
				}
				/ScrollLineForward % top
				{
					EventInItem? dup
					{ 
						MouseInItem? not { 
							HiliteItem 
						} if 
					}
					{ 
						MouseInItem? { 
							UnhiliteItem 
						} if 
					}
					ifelse
					/MouseInItem? exch def
				}
				/ScrollLineBackward % bottom
				{
					EventInItem? dup
					{ 
						MouseInItem? not { 
							HiliteItem 
						} if 
					}
					{ 
						MouseInItem? { 
							UnhiliteItem 
						} if 
					}
					ifelse
					/MouseInItem? exch def
				}
			} case
		} def 

		/HiliteItem {
			ScrollMotion
			{
				/ScrollAbsolute { }
				/ScrollPageForward { }
				/ScrollPageBackward { }
				/ScrollLineForward % top
				{
					0 ItemHeight ButtonSize ButtonSize neg rectpath
					5 setrasteropcode fill
				}
				/ScrollLineBackward % bottom
				{
					0 0 ButtonSize ButtonSize rectpath
					5 setrasteropcode fill
				}
			} case
		} def


		/UnhiliteItem {
			gsave
				ScrollMotion
				{
					/ScrollAbsolute {}
					/ScrollPageForward {}
					/ScrollPageBackward {}
					/ScrollLineForward % top
					{
						0 ItemHeight ButtonSize sub ButtonSize ButtonSize rectpath
						clip
						PaintButtons
					}
					/ScrollLineBackward % bottom
					{
						0 0 ButtonSize ButtonSize rectpath
						clip
						PaintButtons
					}
				} case
			grestore
		} def

	classend
	def




	/ScrollRegionItem Item
	dictbegin
		/BufferCanvas null def
		/BufferWidth 0 def
		/BufferHeight 0 def

		/HScrollbar null def
		/VScrollbar null def
		/HScrollbar? true def
		/VScrollbar? true def
		/HScrollWidth 0 def
		/VScrollWidth 0 def

		/ScrollWidth 16 def
		/ZoomFactor 1 def

		/LabelHeight 0 def		% this gets set in 'ReshapeScrollbars'...

	dictend
	classbegin
		/ItemLabel null def
		/ItemLabelColor 0 0 0 rgbcolor def
		/ItemLabelFont /Times-Roman findfont 12 scalefont def

		% /fontoffset { % font => offset
		% 	dup fontheight exch /FontBBox get 0 get mul neg
		% } def

		/NotifyUserDown { % x y => -
			pop pop 
		} def

		/NotifyUserUp {  % x y => -
			pop pop 
		} def

		/NotifyUserDrag {  % x y => -
			pop pop 
		} def

		/NotifyUserEnter {  % x y => -
			pop pop 
		} def

		/NotifyUserExit {  % x y => -
			pop pop 
		} def

		/new { % parentcanvas width height => instance
			/new super send
			begin
				/BufferHeight ItemHeight def
				/BufferWidth ItemWidth def
				CreateScrollbars
				CreateBuffer
				currentdict
			end
		} def

		/destroy {
			% HScrollbar null ne { 
			%     null /setowner HScrollbar send 
			% } if
			% VScrollbar null ne { 
			%     null /setowner VScrollbar send 
			% } if
			%% BufferCanvas /Mapped false put
			%% /BufferCanvas null def
		} def


		/setzoom { % zoomfactor => -
			/ZoomFactor exch def
		} def


		/reshape { % x y w h => -
			/reshape super send
			ReshapeScrollbars
		} def

		/reshapebuffer { % w h => -
			/BufferHeight exch def
			/BufferWidth exch def
			ReshapeBuffer
			ReshapeScrollbars
		} def

		/getcanvas {
			BufferCanvas
		} def

		/updatecanvas {
			PaintBuffer
		} def

		/makestartinterests { 
			/makestartinterests HScrollbar send
			/makestartinterests VScrollbar send
			[ exch aload length 2 add -1 roll aload pop ] % join 2 arrays
			dup {
				/MyOwner self PutInEventMgrInterest
			} forall
			/makestartinterests super send
			[ exch aload length 2 add -1 roll aload pop ] % join 2 arrays
		} def

		/PaintItem {
			gsave
				PaintBuffer
				/paint VScrollbar send
				/paint HScrollbar send

				% paint the outline
				ItemCanvas setcanvas
				ItemBorderColor setcolor
				0 HScrollWidth 1 add
				ItemWidth VScrollWidth sub
				ItemHeight HScrollWidth sub LabelHeight sub 1 sub
				rectpath stroke

				ItemLabel null ne {
					% paint the label area
					ItemFillColor setcolor
					0 ItemHeight LabelHeight sub 2 sub
					ItemWidth
					LabelHeight 2 add
					rectpath fill

					% paint the label outline
					ItemBorderColor setcolor
					0 ItemHeight LabelHeight sub 2 sub
					ItemWidth 1 sub
					LabelHeight 2 add
					rectpath stroke

					% paint the label text
					ItemLabelColor setcolor
					ItemLabelFont setfont
					ItemWidth ItemLabel stringwidth pop sub 2 div
					ItemHeight LabelHeight sub
					ItemLabelFont fontdescent add 1 sub moveto
					ItemLabel show
				} if
			grestore
		} def

		/Notify {
			% (picture got notified\n) [] dbgprintf
			NotifyUser
			PaintBuffer
		} def

		/PaintBuffer {
			% (PaintBuffer begin \n) [ ] dbgprintf
			gsave
				ItemCanvas setcanvas

				%
				% compute clipping region
				%
				1
				HScrollWidth 1 add
				ItemWidth VScrollWidth sub 2 sub
				LabelHeight 0 ne {
					ItemHeight HScrollWidth sub LabelHeight sub 4 sub
				} {
					ItemHeight HScrollWidth sub 2 sub
				} ifelse
				rectpath
				% (clip to % % % %\n) [ pathbbox ] dbgprintf
				clip

				%
				% Clear the item....
				%
				/BufferColor where {
					pop
					BufferColor fillcanvas
				} if

				%
				% compute translation
				%
				BufferWidth ZoomFactor mul ItemWidth sub VScrollWidth add neg
				dup 0 lt
				{
					1 /getvalue HScrollbar send sub
					mul
				}
				{ 
					pop 0 
				} ifelse

				BufferHeight ZoomFactor mul ItemHeight sub
				HScrollWidth add LabelHeight add neg
				dup 0 lt
				{
					1 /getvalue VScrollbar send sub
					mul
				}
				{ } ifelse
				HScrollWidth add

				% 2 copy (translate by % %\n) [ 4 2 roll ] dbgprintf
				translate

				BufferWidth BufferHeight 
				% 2 copy (scale by % %\n) [ 4 2 roll ] dbgprintf
				scale
				ZoomFactor dup scale

				pause
				BufferCanvas imagecanvas
				pause
			grestore
			% (PaintBuffer end\n) [ ] dbgprintf
		} def

		/CreateBuffer { % - => -
			/BufferCanvas framebuffer newcanvas def
			BufferCanvas /Retained true put
			BufferCanvas /Opaque true put

			ReshapeBuffer
		} def

		/ReshapeBuffer { % - => -
			gsave
				framebuffer setcanvas
				0 0 BufferWidth BufferHeight
				rectpath
				BufferCanvas reshapecanvas
			grestore
		} def

		/CreateScrollbars { % - => - 
			% (begin CreateScrollbars\n) [] dbgprintf
			/HScrollWidth HScrollbar? { ScrollWidth } { 0 } ifelse def
			/VScrollWidth VScrollbar? { ScrollWidth } { 0 } ifelse def

			ItemWidth VScrollWidth le { /VScrollWidth ItemWidth 2 div def } if
			ItemHeight HScrollWidth le { /HScrollWidth ItemHeight 2 div def } if

			/HScrollbar
				[1 0 .01 .1 ItemWidth VScrollWidth sub BufferWidth div ]
				1 {} ItemCanvas
				/new PicScrollbar send
				dup /BarVertical? false put
			def
			/VScrollbar
				[1 0 .01 .1 ItemHeight HScrollWidth sub BufferHeight div ]
				0 {} ItemCanvas
				/new PicScrollbar send
			def

			% self /setowner HScrollbar send
			% self /setowner VScrollbar send
			% (end CreateScrollbars\n) [] dbgprintf
		} def

		/ReshapeScrollbars {
			/HScrollWidth HScrollbar? { ScrollWidth } { 0 } ifelse def
			/VScrollWidth VScrollbar? { ScrollWidth } { 0 } ifelse def
			/LabelHeight ItemLabelFont null ne ItemLabel null ne and
			{ItemLabelFont fontheight} {0} ifelse def

			ItemWidth VScrollWidth le { /VScrollWidth ItemWidth 2 div def } if
			ItemHeight HScrollWidth le { /HScrollWidth ItemHeight 2 div def } if
			ItemHeight LabelHeight HScrollWidth add le {
				/LabelHeight ItemHeight 4 div def
			} if

			10 dict 
			begin
				/h ItemHeight def 
				/w ItemWidth def

				[1 0 .01 .1 ItemWidth VScrollWidth sub BufferWidth div ]
				/setrange HScrollbar send
				[1 0 .01 .1 ItemHeight HScrollWidth sub LabelHeight sub
				BufferHeight div ]
				/setrange VScrollbar send


				HScrollbar?
					{ 0 0 w VScrollWidth sub HScrollWidth }
					{ 0 0 0 0 }
				ifelse
				% 4 copy (hscroll % % % %\n) [ 6 2 roll ] dbgprintf
				/reshape HScrollbar send

				LabelHeight 0 ne {
					VScrollbar?
						{ w VScrollWidth sub HScrollWidth
						  VScrollWidth h HScrollWidth sub LabelHeight sub 2 sub}
						{ 0 0 0 0 }
					ifelse
				} {
					VScrollbar?
						{ w VScrollWidth sub HScrollWidth
						  VScrollWidth h HScrollWidth sub}
						{ 0 0 0 0 }
					ifelse
				} ifelse
				% 4 copy (vscroll % % % %\n) [ 6 2 roll ] dbgprintf
			end
			/reshape VScrollbar send
		} def

		/ClientDown {
			% (Picture ClientDown\n) [] dbgprintf

			% compute translation
			%
			BufferWidth ZoomFactor mul ItemWidth sub VScrollWidth add neg
			dup 0 lt
			{
				1 /getvalue HScrollbar send sub
				mul
			}
			{ 
				pop 0 
			} ifelse

			BufferHeight ZoomFactor mul ItemHeight sub
			HScrollWidth add LabelHeight add neg
			dup 0 lt
			{
				1 /getvalue VScrollbar send sub
				mul
			}
			{ } ifelse
			HScrollWidth add

			% translatex translatey
			CurrentEvent /YLocation get sub neg exch
			CurrentEvent /XLocation get sub neg exch
			2 copy
			/tmpy exch def
			/tmpx exch def
			% tmpx tmpy (tmpx % tmpy %\n) printf
			% (n: % %\n) [ tmpx tmpy ] dbgprintf
			{ 
				/NotifyUserDown self send
			} fork

		} def

		/ClientUp {
			% (Picture ClientUp\n) [] dbgprintf
			CurrentEvent 
			begin 
				XLocation YLocation 
			end
			/NotifyUserUp self send
		} def

		/ClientDrag {
			% (client drag\n) [] dbgprintf
			CurrentEvent 
			begin 
				XLocation YLocation 
			end
			/NotifyUserDrag self send
		} def

		/ClientEnter {
			%% (client enter\n) [] dbgprintf
			CurrentEvent 
			begin 
				XLocation YLocation 
			end 
			/NotifyUserEnter self send
		} def

		/ClientExit {
			%% (client exit\n) [] dbgprintf
			CurrentEvent 
			begin 
				XLocation YLocation 
			end 
			/NotifyUserExit self send
		} def

	classend
	def




	/ScrollDataItem ScrollRegionItem
	dictbegin
		/ObjectData [] def
		/ObjectSizes [] def
		/ObjectOffsets [] def
		/LocalUserNotify {} def
		/NumItems 0 def
		/LastSelect null def
		/ItemValueArray [] def
	dictend
	classbegin
		% Class variables

		/BufferColor 1 1 1 rgbcolor def
		/AllowMultipleSelect? false def
		/ItemHighLightColor 0 0 0 rgbcolor def
		/ItemSpacing 4 def
		/ShowItemSeperators false def

		% Methods

		/new { % parent_canvas notify => instence
			exch 1 1 /new super send
			begin
				/LocalUserNotify exch def
				currentdict
			end
		} def

		/setdata { % array => -
			/ObjectData exch def
			/SizeAllData self send
			300 exch /reshapebuffer self send
			AllowMultipleSelect? {
				/DeclareItemValueArray self send
			} if
			/ShowObjectData self send
		} def

		/getdata { % - => array
			ObjectData
		} def

		% if item_number is out of bounds for the data then
		% the command will be ignored..
		% item_number is zero referenced.
		/deleteitem { % item_number => -
			10 dict begin
				/foo exch def
				/fool ObjectData length def
				foo 0 lt foo fool ge or not {
					% delete an item out of the ObjectData array
					/fooa fool 1 sub array def
					ObjectData 0 foo getinterval fooa copy pop
					ObjectData foo 1 add fool foo sub 1 sub getinterval
					fooa exch foo exch putinterval
					currentdict fooa end
					/ObjectData exch def begin

					% delete an item out of the ItemValueArray array
					AllowMultipleSelect? {
						/fooa fool 1 sub array def
						ItemValueArray 0 foo getinterval fooa copy pop
						ItemValueArray foo 1 add fool foo sub 1 sub getinterval
						fooa exch foo exch putinterval
						currentdict fooa end
						/ItemValueArray exch def begin
					} if

					end

					/SizeAllData self send
					300 exch /reshapebuffer self send

					/ShowObjectData self send
				} {
					end
				} ifelse
		} def

		% add an item to the list
		% if we are adding past the end of the list then we expand
		% the list with empty strings to add the item.
		% item_number is zero referenced.
		% This is all very convluted code put hopefully we can deal with
		% anysized array. We should never load the array in part or in
		% in whole onto the operand stack...
		/additem { % item_label item_number => -
			10 dict begin
				% some helpfull constants
				/foon exch def
				/fooi exch def
				/fool ObjectData length def

				% if we are out of bounds then don't do anything
				foon 0 lt not {
					% check to see if we need to extend the array with
					% null strings.
					foon fool gt {
						/fooa foon array def
						ObjectData fooa copy pop
						currentdict fooa end
						/ObjectData exch def begin
						/fool foon def

						AllowMultipleSelect? {
							/fooa foon array def
							ItemValueArray fooa copy pop
							currentdict fooa end
							/ItemValueArray exch def begin
						} if
					} if

					% if we are inserting before the first element after
					% the end of the list then just append to the list
					foon fool eq {
						% append to list
						/fooa fool 1 add array def
						ObjectData fooa copy pop
						fooa fool fooi put
						currentdict fooa end
						/ObjectData exch def begin

						AllowMultipleSelect? {
							/fooa fool 1 add array def
							ItemValueArray fooa copy pop
							currentdict fooa end
							/ItemValueArray exch def begin
						} if
					} {
						% move the list down and insert an item.
						/fooa fool 1 add array def
						ObjectData 0 foon getinterval
						fooa copy pop
						ObjectData foon fool foon sub getinterval
						fooa exch foon 1 add exch putinterval
						fooa foon fooi put
						currentdict fooa end
						/ObjectData exch def begin

						AllowMultipleSelect? {
							/fooa fool 1 add array def
							ItemValueArray 0 foon getinterval
							fooa copy pop
							ItemValueArray foon fool foon sub getinterval
							fooa exch foon 1 add exch putinterval
							currentdict fooa end
							/ItemValueArray exch def begin
						} if
					} ifelse
					end
					/SizeAllData self send
					300 exch /reshapebuffer self send
					/ShowObjectData self send
				} {
					end
				} ifelse
		} def

		% Change an item..
		% If item does not allready exist then add it..
		% If item was selected is will remain selected..
		/changeitem { % item_label item_number => -
			10 dict begin
				/foon exch def
				/fooi exch def
				/fool ObjectData length def

				% are we out of bounds?
				foon 0 ge {
					% should we call additem?
					foon fool ge {
						% call additem.
						fooi foon end /additem self send
					} {
						% just change the item
						ObjectData foon fooi put
						end
						/SizeAllData self send
						300 exch /reshapebuffer self send
						/ShowObjectData self send
					} ifelse
				} {
					end
				} ifelse
		} def

		/DeclareItemValueArray { % - => -
			/ItemValueArray ObjectData length array def
			ObjectData length 0 ne {
				0 1 ObjectData length 1 sub {
					ItemValueArray exch 0 put
				} for
			} if
		} def

		/SizeAllData { % - => total_size
			/ObjectSizes ObjectData length array def
			/ObjectOffsets ObjectData length array def
			10 dict begin
				/t ItemSpacing def
				ObjectData length 0 ne {
					gsave
						ItemFont setfont
						ObjectData length 1 sub -1 0 {
							/i exch def
							ObjectOffsets i t put
							ObjectData i get /ThingSize self send
							exch pop dup
							ObjectSizes i 3 -1 roll put
							t add ItemSpacing add /t exch def
						} for
					grestore
				} if
			t
			end
		} def

		/ShowObjectData { % - => -
			gsave
				% First we are going to fix up the buffer.

				% set up the defaults
				BufferCanvas setcanvas
				BufferColor fillcanvas
				ItemTextColor setcolor
				ItemFont setfont

				% loop over all the data items writing them out
				ObjectData length 0 ne {
					0 1 ObjectData length 1 sub {
						PutItemText
					} for
				} if

				% go through and hilight those items that have been
				% selected.
				AllowMultipleSelect? {
					% Redraw all the multiple selected stuff.
					% Are the arrays the same length and are they
					% none zero. This is a sanity check.
					ItemValueArray length ObjectData length eq
					ObjectData length 0 ne and {
						% Loop over all the items finding highlighed
						% items and redrawing them.
						10 dict begin
							0 1 ObjectData length 1 sub {
								/i exch def
								ItemValueArray i get 1 eq {
									ItemHighLightColor setcolor i FillItemBox
									BufferColor setcolor i PutItemText
								} if
							} for
						end
					} if
				} {
					% Redraw the single highlighted item.
					LastSelect null ne {
						ItemHighLightColor setcolor LastSelect FillItemBox
						BufferColor setcolor LastSelect PutItemText
					} if
				} ifelse
			grestore

			% Now that we are finished with the buffer paint the
			% the buffer and the items as a whole onto the canvas.
			/PaintItem self send
		} def

		/ShowThingDict 20 dict dup begin
			/fonttype {setfont dup type exec} def
			/colortype {setcolor dup type exec} def
			/integertype {rmoveto dup type exec} def
			/realtype {rmoveto dup type exec} def
			/stringtype {
				0 currentfont fontdescent rmoveto
				show
				0 currentfont fontdescent neg rmoveto
				dup type exec
			} def
			/nametype {
				gsave
					iconfont setfont icondict exch get cvis dup show
					stringbbox 4 2 roll pop pop pop
				grestore
				0 rmoveto

				dup type exec
			} def
			/arraytype {
				dup xcheck {
					/paint exch exec
					dup type exec
				} {
					aload pop dup type exec
				} ifelse
			} def
			/dicttype {/paint exch send dup type exec} def
			/marktype { pop } def
			/nulltype { pop dup type exec} def
		end def

		/ShowThing { % object => -
			gsave
				ShowThingDict begin
					mark exch dup type exec
				end
			grestore
		} def

		/ThingSizeDict 20 dict dup begin
			/x 0 def
			/y 0 def
			/mx 0 def
			/my 0 def
			/fonttype {setfont dup type exec} def

			% /colortype {setcolor dup type exec} def
			% color shouldn't affect size.
			/colortype {pop dup type exec} def

			/integertype {
				y exch add /y exch def
				y my gt { /my y def } if

				x exch add /x exch def
				x mx gt { /mx x def } if

				dup type exec
			} def
			/realtype {
				y exch add /y exch def
				y my gt { /my y def } if

				x exch add /x exch def
				x mx gt { /mx x def } if

				dup type exec
			} def
			/stringtype {
				stringwidth pop
				x exch add
				/x exch def
				x mx gt { /mx x def } if

				currentfont fontheight
				y exch add
				dup my gt { /my exch def } { pop } ifelse

				dup type exec
			} def
			/nametype {
				gsave
					iconfont setfont icondict exch get cvis
					stringbbox 							% x y w h

					y exch add 							% x y w y+h
					3 -1 roll							% x w y+h y
					neg dup 							% x w y+h -y -y
					3 1 roll add						% x w -y y+h+(-y)
					1 add
					dup my gt { /my exch def } { pop } ifelse
					y exch add /y exch def

					x exch add							% ... x x+w
					exch neg add						% ... x+w+(-x)
					/x exch def
					x mx gt { /mx x def } if
				grestore

				dup type exec
			} def

			/arraytype {
				dup xcheck { % x y /size => x y mx my
					x y /size 4 -1 roll exec
					/my exch def
					/mx exch def
					/y exch def
					/x exch def

					dup type exec
				} {
					aload pop dup type exec
				} ifelse
			} def
			/dicttype {
				% x y /size => x y mx my
				x y /size 4 -1 roll send
				/my exch def
				/mx exch def
				/y exch def
				/x exch def

				dup type exec
			} def
			/marktype { pop } def
			/nulltype { pop dup type exec} def
		end def

		/ThingSize { % object => xoff yoff
			gsave
				ThingSizeDict begin
					/x 0 def
					/y 0 def
					/mx 0 def
					/my 0 def
					mark exch dup type exec
					mx my
				end
			grestore
		} def

		/PutItemText { % item_number => -
			dup ObjectOffsets exch get
			5 exch moveto
			dup ObjectData exch get /ShowThing self send

			ShowItemSeperators {
				ObjectOffsets exch get 0 exch ItemSpacing 2 div sub moveto
				300 0 rlineto stroke
			} {
				pop
			} ifelse
		} def

		/FillItemBox { % item_number => -
			dup ObjectOffsets exch get
			0 exch ItemSpacing 2 div sub 1 sub moveto
			300 0 rlineto
			ObjectSizes exch get 0 exch ItemSpacing add rlineto
			-300 0 rlineto
			closepath fill
		} def

		/NotifyUserDown { % x y => -
			gsave
				/FindItem self send
				BufferCanvas setcanvas
				ItemFont setfont

				AllowMultipleSelect? not {
					LastSelect null ne {
						BufferColor setcolor LastSelect FillItemBox
						ItemTextColor setcolor LastSelect PutItemText
					} if

					dup LastSelect eq {
						pop
						/LastSelect null def
						/ItemValue null def
					} {
						dup /LastSelect exch def
						/ItemValue exch def
						ItemValue null ne {
							ItemHighLightColor setcolor ItemValue FillItemBox
							BufferColor setcolor ItemValue PutItemText
						} if
					} ifelse
				} {
					/ItemValue exch def
					ItemValue null ne {
						ItemValueArray ItemValue get 1 eq {
							BufferColor setcolor ItemValue FillItemBox

							ItemTextColor setcolor ItemValue PutItemText

							ItemValueArray ItemValue 0 put
						} {
							% 0 BufferHeight ItemValue 1 add 18 mul sub
							% 3 sub 300 18 rectpath
							ItemHighLightColor setcolor ItemValue FillItemBox
							BufferColor setcolor ItemValue PutItemText

							ItemValueArray ItemValue 1 put
						} ifelse
					} if
				} ifelse

			grestore
			/updatecanvas self send
			/LocalUserNotify self send
		} def

		/FindItem { % x y => item_number
			10 dict begin
				exch pop
				/y exch def

				y 0 lt y BufferHeight gt or not ObjectData length 0 gt and {
					/a null def
					0 1 ObjectData length 1 sub {
						/i exch def
						ObjectOffsets i get y le {
							/a i def
							exit
						} if
					} for
					a
				} {
					null
				} ifelse
			end
		} def

	classend
def