-- File: MBWindowsImpl.mesa
-- Contents: package implementing set of buttons in viewer when there are too many things to actually create buttons for all. Client passes procedures to go forward and backward through enumeration, and MBWindowsImpl implements scrolling, calling client button proc on appropriate thing whenever user pushes a button.
-- Last Edited by: Cattell, July 28, 1983 6:29 pm
-- Last Edited by: Elabbadi, July 28, 1983 11:28 am

DIRECTORY Buttons, MBQueue, MBWindows, NutViewer, Rope, ViewerLocks, ViewerOps, ViewerClasses;

MBWindowsImpl: PROGRAM
IMPORTS NutViewer, ViewerLocks, ViewerOps
EXPORTS MBWindows =

-- Future modifications:
-- make independent of NutViewer by copying code
-- allow shift selection to get text

BEGIN OPEN MBWindows;

ButtonCount: CARDINAL = 50; -- number of buttons that fit vertically on screen
ButtonHeight: CARDINAL = 16; -- height of button in pixels

MBWindowData: TYPE = REF MBWindowDataRec;
MBWindowDataRec: TYPE = RECORD [
scrolledTo: INTEGER, -- position of first button in the full enumeration
prevProc, nextProc: EnumProc, -- to go forward and back in enumeration
enum: REF ANY, -- always kept pointing to last button in window
buttonProc: Buttons.ButtonProc, -- to call when user pushes a button
getThingCount: PROC [enum: REF ANY] RETURNS [INT] -- to get total count
];
Viewer: TYPE = ViewerClasses.Viewer;
CreateMBWindow: PUBLIC PROC[
getThingCount: PROC [enum: REF ANY] RETURNS[INT], -- number of things in enum
enum: REF ANY, -- client object used to represent state of enumeration
next, prev: EnumProc,
buttonProc: Buttons.ButtonProc, -- called with the thing associated with button
info: ViewerClasses.ViewerRec← [], -- allows client to fill in ViewerRec fields
q: MBQueue.Queue← NIL] -- MBQueue under which to synchronize
RETURNS [viewer: ViewerClasses.Viewer] =
BEGIN
button: ViewerClasses.Viewer;
info.data← NEW[MBWindowDataRec←
[0, next, prev, enum, buttonProc, getThingCount]];
viewer← ViewerOps.CreateViewer[
flavor: $MBWindow,
info: info,
paint: FALSE];
-- Create empty buttons in the viewer, in order from top to bottom.
button← NutViewer.Initialize[viewer];
FOR i: INT IN [0..ButtonCount) DO
button← NutViewer.MakeButton[q, NIL, buttonProc, button];
ENDLOOP;
-- WARNING: The following line depends on the fact that children of a viewer are in
-- the sibling list in the reverse order they are created by MakeButton. After the list
-- is reversed, they are in order down the screen.
viewer.child← ReverseChildList[viewer.child].first;
-- Fill in button labels and data by faking scroll to zero position
NARROW[viewer.data, MBWindowData].scrolledTo← 0;
[]← MBWindowScroll[viewer, thumb, ButtonCount];
END;

ReverseChildList: PROC[child: Viewer] RETURNS[first, last: Viewer] = {
IF child.sibling=NIL THEN RETURN[child, child];
[first, last]← ReverseChildList[child.sibling];
last.sibling← child; child.sibling← NIL;
RETURN[first, child]
};

-- MBWindow maintenance strategy:
-- The scrolledTo variable says where the last non-empty button on the screen lies in the
-- enumeration. The first button is at MAX[scrolledTo-ButtonCount, 0] in the enumeration.
-- If we are initially scrolled to the beginning, it is zero. The enum variable is left pointing
-- at the last button currently on the screen; that turns out to be more efficent than leaving
-- it pointing at the first. When we scroll up, we move the label and data fields of the
-- buttons up, changing scrolledTo and enum in the process; then we repaint the screen.
-- And conversely scrolling down.

MBWindowScroll: ViewerClasses.ScrollProc = TRUSTED BEGIN
my: MBWindowData ← NARROW[self.data];
incr: INTEGER;
height: INTEGER;
LockedScroll: SAFE PROC = TRUSTED {
-- We assume that enum has been moved to first item on screen, we simply re-fill in
-- the buttons. We assume that the buttons are in order top to bottom in the viewer.
-- (Scott says, well... it's not likely to change).
FOR v: Viewer ← self.child, v.sibling UNTIL v=NIL DO
[v.name, v.data] ← my.nextProc[my.enum];
ENDLOOP;
my.scrolledTo ← my.scrolledTo+incr;
ViewerOps.PaintViewer[self, client]};
IF my = NIL THEN RETURN;
IF op=query OR op=thumb THEN BEGIN-- compute total height
thumbPos: LONG INTEGER;
height ← my.getThingCount[my.enum];
IF op=thumb THEN BEGIN
thumbPos ← LONG[amount]*height/100;
height ← thumbPos; -- narrow to short integer
END;
END;
IF op=query THEN BEGIN
top, bottom: INT;
IF self.child = NIL OR height=0 THEN RETURN [0, 100];
top ← LONG[100]*TopOfWindow[my]/height;
bottom ← 100 - my.scrolledTo/height;
RETURN[top, bottom];
END;
incr ← SELECT op FROM
up   => MIN[LONG[amount]/ButtonHeight, my.getThingCount[my.enum]-my.scrolledTo],
down  => MIN[LONG[amount]/ButtonHeight, my.scrolledTo],
thumb => height-my.scrolledTo+ButtonCount,
ENDCASE => ERROR;
IF incr=0 THEN RETURN;
MoveEnum[my, incr];
IF incr#0 THEN ViewerLocks.CallUnderWriteLock[LockedScroll, self];
END;
MoveEnum: PROC[my: MBWindowData, incr: INTEGER] = BEGIN
-- Moves enumeration forward or backward by incr. Just move as far as can if incr
-- is farther than nextProc and prevProc are willing to go.
up: BOOL← TRUE;
IF incr<0 THEN {up← FALSE; incr← -incr};
FOR i: INT IN [1..incr) DO
IF up THEN []← my.nextProc[my.enum] ELSE []← my.prevProc[my.enum] ENDLOOP;
END;
TopOfWindow: PROC[my: MBWindowData] RETURNS [INT] =
{RETURN[MAX[0, my.scrolledTo-ButtonCount]]};
mbWindowClass: ViewerClasses.ViewerClass ← NEW[ViewerClasses.ViewerClassRec ← [
scroll: MBWindowScroll,
coordSys: top,
icon: tool,
bltContents: top
]];
ViewerOps.RegisterViewerClass[$MBWindow, mbWindowClass]; -- plug in to Viewers
END.