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: BOOL ← TRUE;
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 ATOM ← NIL] =
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: ATOM ← NARROW[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: ATOM ← NARROW[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;