file: QuickViewerImpl.mesa
Makes viewer (Init)
Sets up menu and buttons (BuildViewer)
Handles screen updates (PaintProc)
Handles graphic input (NotifyProc)
Last Edited by: Preas, September 11, 1984 3:31:49 pm PDT
Last Edited by: Shou, November 21, 1984 8:59:21 am PST
Last Edited by: CSChow, January 12, 1985 1:30:20 pm PST
DIRECTORY
Graphics USING [Context, Mark, Save, white, GetBounds, SetColor, DrawBox,
Restore, Translate, Scale],
Containers USING [Container, Create, ChildXBound, ChildYBound],
Menus USING [Menu, CreateMenu, AppendMenuEntry, CreateEntry, MenuLine, MenuProc, GetNumberOfLines, GetLine, SetLine, MenuEntry, ChangeNumberOfLines,
MouseButton],
ViewerClasses USING [Viewer, ViewerClass, ViewerClassRec, NotifyProc,
PaintProc, DestroyProc, ScrollProc],
ViewerOps USING [CreateViewer, PaintViewer, RegisterViewerClass],
TIPUser USING [InstantiateNewTIPTable ,TIPScreenCoords],
Atom USING [GetPName],
MessageWindow USING [Append, Blink],
Real USING [FixI],
Rope USING [ROPE, Equal, Concat],
ViewerBLT USING [ChangeNumberOfLines],
Process USING [Detach],
QuickViewer USING [];
QuickViewerImpl: CEDAR MONITOR
IMPORTS Graphics, Containers, Menus, ViewerOps, TIPUser, Real, Atom, MessageWindow, ViewerBLT, Rope, Process
EXPORTS QuickViewer
SHARES Menus =
--Menus is otherwise private.
BEGIN
okToProceed: BOOLTRUE;
prevEventActed: CONDITION;
numMenuButtonQd: NAT ← 0; --To count the number of menu buttons waiting. Click proc won't be called if numMenuButtonQd > 0--
numFixedLine: NAT ← 2; --This is the number of menu lines which aren't controlled
entryHeight: CARDINAL = 12; -- height of a line of items in a menu
entryVSpace: CARDINAL = 3; -- vertical leading between lines
entryHSpace: CARDINAL = 8; -- horizontal space between items on a line
quickView: QuickView ← NIL; -- This is the only instance allowed
QuickView: TYPE = REF QuickViewData;
QuickViewData: TYPE = RECORD
[outer: Containers.Container ← NIL, -- enclosing container
viewer: ViewerClasses.Viewer, -- graphics area within
xTranslation, yTranslation: REAL ← 0.,
xyScale: REAL ← 1.0,
autoScaling: BOOL,
xLeft, xRight, yBottom, yTop: REAL,
proc: PROC [context: Graphics.Context, scaleFactor: REAL]];
DrawProc: PROC[Graphics.Context, REAL]; -- name of procedure for redrawing on window resizing, etc.
ButtProc: PROC[ATOM, Menus.MouseButton, BOOL, BOOL]; -- name of procedure for acting on button pushes
ClickProc: PROC[ATOM, REAL, REAL]; -- name of procedure for acting on mouse clicks within the viewer
ExitProc: PROC[]; -- name of procedure for cleaning up on exit
line0Menu, line1Menu, line2Menu, line3Menu, line4Menu, line5Menu, line6Menu: Menus.MenuEntry;
line2Control, line3Control, line4Control, line5Control, line6Control: Rope.ROPE;
BuildViewer: PUBLIC PROC[
ReDrawProc: PROC[Graphics.Context, REAL],
QuitProc: PROC[],
MenuButtonProc: PROC[ATOM, Menus.MouseButton, BOOL, BOOL], --event, mouseButton, shift, control--
ViewerClickProc: PROC[ATOM, REAL, REAL], --ATOM is one of {$LeftButton, $MiddleButton, $RighButton}, xControlPt, yControlPt
viewerTitle: Rope.ROPE,
autoScaling: BOOL,
menuLabelsLine0, menuLabelsLine1, controlledLine2, controlledLine3, controlledLine4, controlledLine5, controlledLine6: LIST OF ATOMNIL] =
BEGIN
menu: Menus.Menu;
AppendEntry: PROC[name, doc: Rope.ROPE, proc: Menus.MenuProc, lineNo: INT] ={
Menus.AppendMenuEntry[menu: menu,
entry: Menus.CreateEntry[name: name,
proc: proc,
clientData: quickView,
documentation: doc],
line: lineNo];}; --AppendEntry--
SetUpMenuLine: PROC[labels: LIST OF ATOM, lineNo: INT] ={
WHILE labels # NIL DO
Menus.AppendMenuEntry[-- enter menu buttons
menu: menu,
entry: Menus.CreateEntry[
name: Atom.GetPName[labels.first],
proc: MenuProc,
clientData: labels.first,
documentation: " "],
line: lineNo];
labels ← labels.rest;
ENDLOOP;
}; --SetUpMenuLine--
IF quickView # NIL THEN {MessageWindow.Append["Only one instance of QuickViewer is allowed at a time!", TRUE]; MessageWindow.Blink[]; RETURN};
quickView ← NEW[QuickViewData]; -- allocate a data object
menu ← Menus.CreateMenu[lines: 2]; -- set up menu
DrawProc ← ReDrawProc; -- store away procedure names
ButtProc ← MenuButtonProc;
ClickProc ← ViewerClickProc;
ExitProc ← QuitProc;
line0Menu← line1Menu← line2Menu← line3Menu← line4Menu← line5Menu← line6Menu ← NIL;
line2Control← line3Control← line4Control← line5Control← line6Control ← NIL;
--First get the names of the controlling atoms--
IF controlledLine2 # NIL THEN line2Control ← Atom.GetPName[controlledLine2.first];
IF controlledLine3 # NIL THEN line3Control ← Atom.GetPName[controlledLine3.first];
IF controlledLine4 # NIL THEN line4Control ← Atom.GetPName[controlledLine4.first];
IF controlledLine5 # NIL THEN line5Control ← Atom.GetPName[controlledLine5.first];
IF controlledLine6 # NIL THEN line6Control ← Atom.GetPName[controlledLine6.first];
AppendEntry["Refresh", "Refresh the viewer", Refresh, 0]; -- enter "Refresh" button
AppendEntry["<<<", "Roll image to left", RollLeft, 0]; -- enter "<<<" button
AppendEntry[">>>", "Roll image to right", RollRight, 0]; -- enter ">>>" button
AppendEntry["Enlarge", "Increase maginification", Enlarge, 0]; -- enter "Enlarge" button
AppendEntry["Reduce", "Decrease maginification", Reduce, 0]; -- enter "Reduce" button
SetUpMenuLine[menuLabelsLine0, 0]; --Add (the rest of) menu line0
AppendEntry[line2Control, " ", Line2Menu, 0]; -- enter line 2 control button
AppendEntry[line3Control, " ", Line3Menu, 0]; -- enter line 3 control button
AppendEntry[line4Control, " ", Line4Menu, 0]; -- enter line 4 control button
AppendEntry[line5Control, " ", Line5Menu, 0]; -- enter line 5 control button
AppendEntry[line6Control, " ", Line6Menu, 0]; -- enter line 6 control button
SetUpMenuLine[menuLabelsLine1, 1]; --Add menu line1 now
line0Menu ← Menus.GetLine[menu, 0];
line1Menu ← Menus.GetLine[menu, 1];
--Now menu line1 and menu line2 are done
IF controlledLine2 # NIL THEN SetUpMenuLine[controlledLine2.rest, 2];
line2Menu ← Menus.GetLine[menu, 2];
Menus.SetLine[menu, 2, NIL];
IF controlledLine3 # NIL THEN SetUpMenuLine[controlledLine3.rest, 2];
line3Menu ← Menus.GetLine[menu, 2];
Menus.SetLine[menu, 2, NIL];
IF controlledLine4 # NIL THEN SetUpMenuLine[controlledLine4.rest, 2];
line4Menu ← Menus.GetLine[menu, 2];
Menus.SetLine[menu, 2, NIL];
IF controlledLine5 # NIL THEN SetUpMenuLine[controlledLine5.rest, 2];
line5Menu ← Menus.GetLine[menu, 2];
Menus.SetLine[menu, 2, NIL];
IF controlledLine6 # NIL THEN SetUpMenuLine[controlledLine6.rest, 2];
line6Menu ← Menus.GetLine[menu, 2];
Menus.SetLine[menu, 2, NIL];
Menus.ChangeNumberOfLines[menu, 2]; -- only show the top two levels to start with.
quickView.outer ← Containers.Create[[name: viewerTitle, -- define outer viewer
menu: menu,
iconic: TRUE,
column: left,
scrollable: FALSE]];
quickView.viewer ← ViewerOps.CreateViewer -- define graphics area
[flavor: $QuickViewer,
info: [parent: quickView.outer,
wx: 0, wy: 0, -- position WRT parent
ww: quickView.outer.ww, -- CHildXBound below
wh: quickView.outer.wh, -- CHildXBound below
data: quickView, -- describes the current scene
scrollable: TRUE]];
-- constrain graphics area to lie in viewer space left over after menu, etc. are drawn
Containers.ChildXBound[quickView.outer, quickView.viewer];
Containers.ChildYBound[quickView.outer, quickView.viewer];
quickView.autoScaling ← autoScaling;
ViewerOps.PaintViewer[quickView.outer, all]; -- load up the viewer (paint it)
quickView.xTranslation ← quickView.yTranslation ← 0.;
quickView.xLeft ← quickView.yBottom ← 0.;
quickView.xRight ← quickView.viewer.ww;
quickView.yTop ← quickView.viewer.wh;
END; --BuildViewer--
EraseViewer: PUBLIC PROC[] ={
DoErase: PROC [context: Graphics.Context, scaleFactor: REAL] = {
mark: Graphics.Mark ← Graphics.Save[context]; -- mark stack
Graphics.SetColor[context, Graphics.white]; -- set color to white
Graphics.DrawBox[context,Graphics.GetBounds[context]]; -- erase by drawing box
Graphics.Restore[context,mark]; -- restore stack
}; --DoErase--
DrawInViewer[DoErase];}; --EraseViewer--
SetViewerName: PUBLIC PROC[name: Rope.ROPE] ={quickView.outer.name ← name}; --SetViewerName
PaintProc: ViewerClasses.PaintProc = -- repaint screen for updates
[self: Viewer, context: Graphics.Context, whatChanged: REF ANY, clear: BOOL]
BEGIN x,y: REAL;
x ← quickView.xTranslation + quickView.viewer.ww/2.; -- center image on relative origin
y ← quickView.yTranslation + quickView.viewer.wh/2.;
IF whatChanged = NIL THEN
{Graphics.Translate[context, x, y];
IF quickView.autoScaling THEN Graphics.Scale[context, quickView.xyScale, quickView.xyScale];
DrawProc[context, quickView.xyScale]} -- window resized, redraw
ELSE
{Graphics.Translate[context, x, y];
IF quickView.autoScaling THEN Graphics.Scale[context, quickView.xyScale, quickView.xyScale];
NARROW[whatChanged, REF PROC[Graphics.Context, REAL]]^[context, quickView.xyScale]};
END;
DrawInViewer: PUBLIC PROCEDURE [proc: PROC [Graphics.Context, REAL]] = -- pass procedure to PaintProc
BEGIN
drawProc: REF PROC[Graphics.Context, REAL] ← NIL;
TRUSTED {drawProc ← NEW[PROC[Graphics.Context, REAL] ← proc];};
ViewerOps.PaintViewer[viewer: quickView.viewer, -- pass record to viewer painter
hint: client,
whatChanged: drawProc,
clearClient: FALSE];
END;

Refresh: PROCEDURE [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton,
shift, control: BOOL] = {
EraseViewer[];
DrawInViewer[DrawProc];
}; --Refresh--
RollLeft: PROCEDURE [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton,
shift, control: BOOL] = {
EraseViewer[];
quickView.xTranslation ← quickView.xTranslation - (SELECT mouseButton FROM red => 64, yellow => 128, blue => 256, ENDCASE => ERROR); -- Move image to left
DrawInViewer[DrawProc]; }; --RollLeft--
RollRight: PROCEDURE [parent: REF ANY, clientData: REF ANY,
mouseButton: Menus.MouseButton,
shift, control: BOOL] = {
EraseViewer[];
quickView.xTranslation ← quickView.xTranslation + (SELECT mouseButton FROM red => 64, yellow => 128, blue => 256, ENDCASE => ERROR); -- Move image to right
DrawInViewer[DrawProc]}; --RollRight--
Enlarge: PROCEDURE [parent: REF ANY, clientData: REF ANY,
mouseButton: Menus.MouseButton,
shift, control: BOOL] = {
EraseViewer[];
quickView.xyScale ← quickView.xyScale * (SELECT mouseButton FROM red => 1.2, yellow => 2.0, blue => 4.0, ENDCASE => ERROR); -- Enlarge image
DrawInViewer[DrawProc]}; --Enlarge--
Reduce: PROCEDURE [parent: REF ANY, clientData: REF ANY,
mouseButton: Menus.MouseButton,
shift, control: BOOL] = {
EraseViewer[];
quickView.xyScale ← quickView.xyScale * (SELECT mouseButton FROM red => 0.8, yellow => 0.5, blue => 0.25, ENDCASE => ERROR); -- Reduce image
DrawInViewer[DrawProc]}; --Reduce--
Line2Menu: Menus.MenuProc = { ChangeMenu[NARROW[parent], line2Menu] };
Line3Menu: Menus.MenuProc = { ChangeMenu[NARROW[parent], line3Menu] };
Line4Menu: Menus.MenuProc = { ChangeMenu[NARROW[parent], line4Menu] };
Line5Menu: Menus.MenuProc = { ChangeMenu[NARROW[parent], line5Menu] };
Line6Menu: Menus.MenuProc = { ChangeMenu[NARROW[parent], line6Menu] };
ChangeMenu: PROC [viewer: ViewerClasses.Viewer, subMenu: Menus.MenuEntry] = {
--This procedure is based on [Indigo]<Cedar5.2>TEditDocumentsImpl.mesa.
menu: Menus.Menu ← viewer.menu;
numLines: Menus.MenuLine ← Menus.GetNumberOfLines[menu];
FOR i: Menus.MenuLine IN [numFixedLine..numLines) DO -- See if already showing the submenu
IF Rope.Equal[Menus.GetLine[menu,i].name, subMenu.name] THEN { -- Yes, so remove it
FOR j: Menus.MenuLine IN (i..numLines) DO
Menus.SetLine[menu, j-1, Menus.GetLine[menu, j]];
ENDLOOP;
ViewerBLT.ChangeNumberOfLines[viewer, numLines.PRED];
RETURN --DONE
}; --ENDIF
ENDLOOP;
--If numLines will not be more within limit of Menus.MenuLine then move all controlled lines 1 step up and replace the lowest line by new request --
IF numLines.SUCC IN Menus.MenuLine
THEN {Menus.SetLine[menu, numLines, subMenu]; numLines ← numLines.SUCC}
ELSE {
FOR j: Menus.MenuLine IN (numFixedLine..numLines) DO
Menus.SetLine[menu, j-1, Menus.GetLine[menu, j]];
ENDLOOP;
Menus.SetLine[menu, numLines.PRED, subMenu];}; --ENDIF
ViewerBLT.ChangeNumberOfLines[viewer, numLines];
}; --ChangeMenu--
MenuProc: PROCEDURE [parent: REF ANY, clientData: REF ANY,
mouseButton: Menus.MouseButton,
shift, control: BOOL] = {
menuButton: ATOMNARROW[clientData];
MenuButtonMonitor[menuButton, mouseButton, shift, control];
}; --MenuProc--
MenuButtonMonitor: ENTRY PROCEDURE [event: ATOM, mouseButton: Menus.MouseButton, shift, control: BOOL] ={
numMenuButtonQd ← numMenuButtonQd.SUCC;
WHILE ~ okToProceed DO
WAIT prevEventActed;
ENDLOOP;
okToProceed ← FALSE;
BEGIN
ENABLE UNWIND =>GOTO aborted;
ButtProc[event, mouseButton, shift, control];
EXITS
aborted => {MessageWindow.Append[Rope.Concat[Atom.GetPName[event], " aborted"], TRUE]; MessageWindow.Blink[]}
END;
okToProceed ← TRUE;
NOTIFY prevEventActed;
numMenuButtonQd ← numMenuButtonQd.PRED;};--MenuButtonMonitor--
NotifyProc: ViewerClasses.NotifyProc =
BEGIN-- PROCEDURE [self: Viewer, input: LIST OF REF ANY]
IF ISTYPE[input.first, TIPUser.TIPScreenCoords] THEN -- If input is coords from mouse
{mousePlace: TIPUser.TIPScreenCoords ← NARROW[input.first]; --get mouse coordinates, store globally
controlPointX: REAL ← (mousePlace.mouseX - quickView.xTranslation - quickView.viewer.ww/2.0)/ quickView.xyScale;
controlPointY: REAL ← (mousePlace.mouseY - quickView.yTranslation - quickView.viewer.wh/2.0)/quickView.xyScale;
expand work area if clicked outside existing bounds
IF controlPointX > quickView.xRight THEN quickView.xRight ← controlPointX
ELSE IF controlPointX < quickView.xLeft THEN quickView.xLeft ← controlPointX;
IF controlPointY > quickView.yTop THEN quickView.yTop ← controlPointY
ELSE IF controlPointY < quickView.yBottom THEN quickView.yBottom ← controlPointY;
IF ISTYPE[input.rest.first, ATOM] THEN {
mouseButton: ATOMNARROW[input.rest.first];
IF numMenuButtonQd = 0
THEN ClickMonitor[mouseButton, controlPointX, controlPointY]
ELSE MessageWindow.Append[Rope.Concat[Atom.GetPName[mouseButton], " click ignored"], TRUE]};
};
END;
ClickMonitor: ENTRY PROCEDURE [mouseButton: ATOM, xCoord, yCoord: REAL] ={
WHILE ~ okToProceed DO
WAIT prevEventActed;
ENDLOOP;
okToProceed ← FALSE;
BEGIN
ENABLE UNWIND =>GOTO aborted;
ClickProc[mouseButton, xCoord, yCoord];
EXITS
aborted => {MessageWindow.Append[Rope.Concat[Atom.GetPName[mouseButton], " click aborted"], TRUE]; MessageWindow.Blink[]}
END;
okToProceed ← TRUE;
NOTIFY prevEventActed;};--ClickMonitor--
DestroyProc: ViewerClasses.DestroyProc ={ -- clean up on exit (viewer destroyed)
TRUSTED {Process.Detach[FORK ExitProc[]];};
quickView ← NIL; --So can create other instance
}; --DestroyProc--

ScrollProc: ViewerClasses.ScrollProc = -- act on scrollbar mouse hits
TYPE = PROC[self: Viewer, op: ScrollOp, amount: INTEGER]
-- RETURNS[top, bottom: INTEGER ← LAST[INTEGER]];
-- ScrollOp: TYPE = {query, up, down, thumb}
BEGIN
SELECT op FROM
up =>
{quickView.yTranslation ← quickView.yTranslation + amount;
EraseViewer[];
DrawInViewer[DrawProc]};
down =>
{quickView.yTranslation ← quickView.yTranslation - amount;
EraseViewer[];
DrawInViewer[DrawProc]};
thumb =>
{quickView.yTranslation ← - (quickView.yBottom + (1. - amount/100.) * (quickView.yTop - quickView.yBottom));
EraseViewer[];
DrawInViewer[DrawProc]};
query =>
{OPEN quickView;
RETURN[Real.FixI[100. - (-yTranslation + self.ch - yBottom) * 100. / (yTop - yBottom)],
Real.FixI[100. - (-yTranslation - yBottom) * 100. / (yTop - yBottom)]]};
ENDCASE;
END;
ViewerOps.RegisterViewerClass[$QuickViewer, NEW
[ViewerClasses.ViewerClassRec ←
[paint: PaintProc, -- procedure called when viewer contents must be repainted
notify: NotifyProc, -- procedure to respond to input events (from TIP table)
destroy: DestroyProc, -- procedure to clean up when done
scroll: ScrollProc, -- procedure to respond to scroll bar hits
-- Tip table (translates mouse events to commands)
tipTable: TIPUser.InstantiateNewTIPTable["QuickViewer.TIP"],
icon: document
]]]; -- Register with viewers
END.