PreViewImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Last edited by Ken Pier; May 15, 1986 9:55:26 am PDT
Doug Wyatt, June 7, 1986 1:51:02 pm PDT
Bland, August 28, 1986 4:30:07 pm PDT
Mike Spreitzer September 4, 1986 5:56:24 pm PDT
DIRECTORY
AIS USING [CloseFile],
Atom USING [GetPName],
BasicTime USING [GMT],
BiScrollers USING [BiScroller, ClientCoords, ClientDataOfViewer, QuaBiScroller],
Convert USING [RopeFromReal],
FS USING [Error],
FunctionCache USING [Cache, CompareProc, GlobalCache, Insert, Lookup],
Geom2D USING [ExtremaOfRect, id, Transform, Rect, Vec],
GriffinImageUtils USING [GriffinToImagerCalls],
Imager USING [black, ClipRectangle, Color, ConcatT, Context, Error, MaskBox, MaskRectangle, MaskVector, Scale2T, ScaleT, SetColor, SetSampledColor, SetStrokeJoint, SetStrokeWidth, TranslateT, DoSaveAll],
ImagerArtwork USING [PasteArtwork, Points],
ImagerBackdoor USING [DrawBits, MakeStipple],
ImagerInterpress USING [Close, Create, DoPage, Ref],
ImagerMemory USING [GetContextSize, NewMemoryContext, Replay],
ImagerPixelMap USING [DeviceRectangle, PixelMap, Window],
ImagerTransformation USING [PostTranslate, Rectangle, Scale, Transformation, TransformRectangle],
InputFocus USING [GetInputFocus, SetInputFocus],
Interpress USING [DoPage],
IO USING [PutFR],
MessageWindow USING [Append, Blink],
PDFileReader USING [Close, Handle],
PDImageReader USING [Handle, InterpretImage, ResetToImage],
PreView USING [AISData, AISState, BBoxOp, bsStyle, Data, GData, IPData, IPLogError, PaintOp, PDData, PressData, PVFeedback, PVListRemove, ResetProcess],
PrincOpsUtils USING [],
Process USING [GetCurrent],
Real USING [LargestNumber],
Rope USING [Concat, ROPE],
SafeStorage USING [ReclaimCollectibleObjects],
ShowPress USING [Close, DrawPressPage, Handle, ShowPressError],
ViewerClasses USING [DestroyProc, GetProc, NotifyProc, PaintProc, Viewer],
ViewerGroupLocks USING [CallRootUnderWriteLock],
ViewerLocks USING [Wedged],
ViewerPrivate USING [PaintClient];
PreViewImpl: CEDAR MONITOR
IMPORTS AIS, Atom, BiScrollers, Convert, FS, FunctionCache, Geom2D, GriffinImageUtils, Imager, ImagerArtwork, ImagerBackdoor, ImagerInterpress, ImagerMemory, ImagerPixelMap, ImagerTransformation, Interpress, InputFocus, IO, MessageWindow, PDFileReader, PDImageReader, PreView, Process, Rope, SafeStorage, ShowPress, ViewerGroupLocks, ViewerLocks, ViewerPrivate
EXPORTS PreView = BEGIN
PProc: TYPE = PROC [Imager.Context];
Data: TYPE = PreView.Data;
PressData: TYPE = PreView.PressData;
PDData: TYPE = PreView.PDData;
IPData: TYPE = PreView.IPData;
AISData: TYPE = PreView.AISData;
GData: TYPE = PreView.GData;
CacheKey: TYPE = REF CacheKeyRep;
CacheKeyRep: TYPE = RECORD [
fileName: Rope.ROPE,
created: BasicTime.GMT,
pageNumber: INTEGER
];
globalCache: FunctionCache.Cache ← FunctionCache.GlobalCache[];
screenResolution: REAL = 72.0; -- points per inch
pointsPerInch: REAL = 72.0;
pointsPerMica: REAL = 72.0/2540.0;
micasPerPoint: REAL = 2540.0/72.0;
pointsPerMeter: REAL = 72.0/.0254;
metersPerPoint: REAL = .0254/72.0;
grey: CARDINAL ← 122645B;
xorStipple: Imager.Color = ImagerBackdoor.MakeStipple[stipple: grey, xor: TRUE];
borderWidth: REAL ← 2.0;
fudge: REAL ← 2.0;
messageWindowFeedback: BOOLEANFALSE;
Message: PROC [x, y: REAL] = {
MessageWindow.Append[Convert.RopeFromReal[x], TRUE];
MessageWindow.Append[", ", FALSE];
MessageWindow.Append[Convert.RopeFromReal[y], FALSE];
};
PVNotify: PUBLIC ViewerClasses.NotifyProc = {-- [self: Viewer, input: LIST OF REF ANY]
ENABLE UNWIND => NULL;
mouseX, mouseY: REAL ← 0.0;
data: Data ← NARROW[BiScrollers.ClientDataOfViewer[self]];
FOR list: LIST OF REF ANY ← input, list.rest UNTIL list = NIL DO
WITH list.first SELECT FROM
x: ATOM => SELECT x FROM
$Abort => {
IF data.bBox.active THEN PVFeedback[data: data, v: self, op: remove]; -- remove grey bbox.
data.bBox^ ← []; -- initialization default values cancel current clipping rectangle
};
$Move => {
IF InputFocus.GetInputFocus[].owner#self THEN InputFocus.SetInputFocus[self];
IF data.bBox.mode = waitingForFirstPoint THEN {
data.bBox.mode ← waitingForSecondPoint;
data.bBox.x0 ← mouseX;
data.bBox.y0 ← mouseY;
data.bBox.active ← TRUE;
};
data.bBox.rect ← [x: MIN[mouseX, data.bBox.x0], y: MIN[mouseY, data.bBox.y0], w: ABS[mouseX-data.bBox.x0], h: ABS[mouseY-data.bBox.y0]];
PVFeedback[data: data, v: self, op: change, x: mouseX, y: mouseY]; -- repaint grey bbox.
};
$EndMove => IF data.bBox.active
THEN MessageWindow.Append[
message: IO.PutFR["Selection = [x: %g, y: %g, w: %g, h: %g] inches", [real[data.bBox.rect.x/pointsPerInch]], [real[data.bBox.rect.y/pointsPerInch]], [real[data.bBox.rect.w/pointsPerInch]], [real[data.bBox.rect.h/pointsPerInch]]],
clearFirst: TRUE]
ELSE MessageWindow.Append["No selection", TRUE];
ENDCASE => NULL;
z: BiScrollers.ClientCoords => { --TYPE = REF Vec
mouseX ← z.x; -- mouseX in BiScroller coords
mouseY ← z.y; -- mouseY in BiScroller coords
IF messageWindowFeedback THEN Message[mouseX, mouseY];
};
ENDCASE => ERROR;
ENDLOOP;
};
PVFeedback: PUBLIC PROC [data: Data, v: ViewerClasses.Viewer, x, y: REAL ← 0.0, op: PreView.PaintOp ← paint] = {
Action: PROC [context: Imager.Context] = { -- this context in viewer coords
Show: PROC [x1, y1, x2, y2: REAL] = {
rect: ImagerTransformation.Rectangle;
IF x1 > x2 THEN {t: REAL ← x2; x2 ← x1; x1 ← t};
IF y1 > y2 THEN {t: REAL ← y2; y2 ← y1; y1 ← t};
rect ← ImagerTransformation.TransformRectangle[m: ctv, r: [x: x1, y: y1, w: x2-x1, h: y2-y1]];
x1 ← rect.x; y1 ← rect.y; x2 ← rect.x+rect.w; y2 ← rect.y+rect.h;
Imager.MaskRectangle[context, [x1-5.0, y1-5.0, 5.0, y2-y1+10.0]]; -- left side
Imager.MaskRectangle[context, [x2, y1-5.0, 5.0, y2-y1+10.0]]; -- right side
Imager.MaskRectangle[context, [x1, y1-5.0, x2-x1, 5.0]]; -- top side
Imager.MaskRectangle[context, [x1, y2, x2-x1, 5.0]]; -- bottom side
};
IF data.bBox.lastX=x AND data.bBox.lastY=y AND op#paint THEN RETURN; -- no change
Imager.SetColor[context, xorStipple];
SELECT op FROM
paint => {
Show[data.bBox.rect.x, data.bBox.rect.y, data.bBox.rect.x+data.bBox.rect.w, data.bBox.rect.y+data.bBox.rect.h];
};
remove => {
Show[data.bBox.lastX, data.bBox.lastY, data.bBox.x0, data.bBox.y0]; --remove the old box
data.bBox.lastX ← data.bBox.lastY ← Real.LargestNumber;
};
change => {
IF data.bBox.lastX#Real.LargestNumber THEN Show[data.bBox.lastX, data.bBox.lastY, data.bBox.x0, data.bBox.y0]; --remove the old box
Show[x, y, data.bBox.x0, data.bBox.y0]; -- to show the new box
data.bBox.lastX ← x; data.bBox.lastY ← y;
};
ENDCASE => ERROR;
};
ctv: ImagerTransformation.Transformation ← PreView.bsStyle.GetTransforms[BiScrollers.QuaBiScroller[v]].clientToViewer;
DirectPaint[viewer: v, action: Action];
};
DoFileOps: PUBLIC PROC [op: PreView.BBoxOp, viewer: ViewerClasses.Viewer, data: Data, fileName: Rope.ROPE, clip: BOOLTRUE] = {
ctv: ImagerTransformation.Transformation ← PreView.bsStyle.GetTransforms[BiScrollers.QuaBiScroller[viewer]].clientToViewer;
rectV: ImagerTransformation.Rectangle ← ImagerTransformation.TransformRectangle[m: ctv, r: [x: data.bBox.rect.x, y: data.bBox.rect.y, w: data.bBox.rect.w, h: data.bBox.rect.h]];
SELECT op FROM
stuff => { -- stuff requires clip to be TRUE and bBox valid
DoStuff: PROC [context: Imager.Context] = {
DoIt: PROC = {
context.ConcatT[ctv];
DoPainting[context: context, data: data, viewer: viewer];
};
context.TranslateT[t: [-rectV.x, -rectV.y]];
Imager.DoSaveAll[context, DoIt];
IF data.stuffWithBorders THEN {
context.SetStrokeWidth[borderWidth];
context.SetColor[Imager.black];
context.MaskVector [ [rectV.x, rectV.y], [rectV.x, rectV.y+rectV.h] ]; -- left side
context.MaskVector [ [rectV.x, rectV.y], [rectV.x+rectV.w, rectV.y] ]; -- bottom
context.MaskVector [ [rectV.x+rectV.w, rectV.y], [rectV.x+rectV.w, rectV.y+rectV.h] ]; -- right side
context.MaskVector [ [rectV.x, rectV.y+rectV.h], [rectV.x+rectV.w, rectV.y+rectV.h] ]; -- top
};
};
ImagerArtwork.PasteArtwork[action: DoStuff, bounds: [0, 0, rectV.w, rectV.h], m: ImagerArtwork.Points[], clip: TRUE, fit: data.stuffWithFit];
};
ipMaster => {
DoIPMaster: PROC [context: Imager.Context] = {
context.ScaleT[metersPerPoint];
IF clip THEN {
context.TranslateT[t: [-rectV.x, -rectV.y]];
context.ClipRectangle[rectV];
};
context.ConcatT[ctv];
DoPainting[context: context, data: data, viewer: viewer];
};
r: ImagerInterpress.Ref ← ImagerInterpress.Create[fileName: fileName ! FS.Error => {
MessageWindow.Append[message: error.explanation, clearFirst: TRUE];
GOTO Quit;
};];
ImagerInterpress.DoPage[self: r, action: DoIPMaster ! Imager.Error => {
MessageWindow.Append[message: Rope.Concat[error.explanation, " creating IPMaster"], clearFirst: TRUE]; CONTINUE;};];
ImagerInterpress.Close[r];
EXITS
Quit => NULL;
};
ENDCASE;
data.bBox^ ← []; -- causes initialization of data.bBox values to inactive state
};
DirectPaint: PROC [viewer: ViewerClasses.Viewer, action: PProc] = {
CallLocked: PROC = { ViewerPrivate.PaintClient[viewer, action ! ABORTED => CONTINUE] };
IF viewer = NIL OR viewer.destroyed OR viewer.paintingWedged THEN RETURN;
ViewerGroupLocks.CallRootUnderWriteLock[CallLocked, viewer
! ViewerLocks.Wedged => {viewer.paintingWedged ← TRUE; CONTINUE}];
};
DoPainting: PROC [context: Imager.Context, data: Data, viewer: ViewerClasses.Viewer] = {
data.abort ← FALSE; -- need this to clean up any PD abort bits that were set before opening the viewer
WITH data SELECT FROM
ipData: IPData => PaintIP[context: context, ipData: ipData]; --clobbers context variables, but that is OK
pressData: PressData => PaintPress[context: context, pressData: pressData];
pdData: PDData => PaintPD[context: context, pdData: pdData, viewer: viewer];
aisData: AISData => PaintAIS[context: context, aisData: aisData];
gData: GData => PaintGriffin[context: context, gData: gData];
ENDCASE => ERROR;
data.abort ← FALSE; --just in case you returned here due to data.abort set while PD painting
};
PaintIP: PROC [context: Imager.Context, ipData: IPData] = {
SELECT TRUE FROM
ipData.switches['M] => { -- M switch means use no ImagerMemory
context.SetColor[Imager.black];
context.ScaleT[pointsPerMeter]; -- establish IP coord system
Interpress.DoPage[master: ipData.ipMaster, page: ipData.pageNumber, context: context, log: PreView.IPLogError];
};
ipData.pageInMem=ipData.pageNumber => { -- iMemContext is current
ImagerMemory.Replay[c: ipData.iMemContext, into: context];
};
ipData.pageInMem#ipData.pageNumber => { -- memory context may be cached
PageCompareProc: FunctionCache.CompareProc = {
PROC [argument: Domain] RETURNS [good: BOOL];
RETURN[NARROW[argument, CacheKey]^=cacheKey^];
};
cacheKey: CacheKey ← NEW[CacheKeyRep ← [ipData.fileInfo.fullFName, ipData.fileInfo.created, ipData.pageNumber]];
IF (ipData.iMemContext ← NARROW[FunctionCache.Lookup[x: globalCache, compare: PageCompareProc, clientID: $PVPage].value, Imager.Context])#NIL THEN { --hit
ImagerMemory.Replay[c: ipData.iMemContext, into: context];
ipData.pageInMem ← ipData.pageNumber;
}
ELSE { -- miss
ipData.iMemContext ← ImagerMemory.NewMemoryContext[];
ipData.iMemContext.SetColor[Imager.black];
ipData.iMemContext.ScaleT[pointsPerMeter]; -- establish IP coord system
Interpress.DoPage[master: ipData.ipMaster, page: ipData.pageNumber, context: ipData.iMemContext, log: PreView.IPLogError];
FunctionCache.Insert[x: globalCache, argument: cacheKey, value: ipData.iMemContext, size: ImagerMemory.GetContextSize[ipData.iMemContext], clientID: $PVPage];
ipData.pageInMem ← ipData.pageNumber;
ImagerMemory.Replay[c: ipData.iMemContext, into: context];
};
};
ENDCASE => NULL;
};
PaintPress: PROC [context: Imager.Context, pressData: PressData] = {
context.SetColor[Imager.black];
context.ScaleT[pointsPerMica]; -- establish Press coord system
ShowPress.DrawPressPage[context: context, show: pressData.presshandle, pageNumber: pressData.pageNumber
! ShowPress.ShowPressError => {
SELECT code FROM
$NoSuchPage => {IF pressData.pageNumber > 0 THEN {pressData.pageNumber ← pressData.pageNumber-1; RETRY};};
ENDCASE => {
MessageWindow.Append[Atom.GetPName[code], TRUE];
MessageWindow.Blink[];
CONTINUE;
};
}
];
};
PaintPD: PROC [context: Imager.Context, pdData: PDData, viewer: ViewerClasses.Viewer] = {
PaintIt: PROC [self: ViewerClasses.Viewer, whatChanged: REF ANY] = {
remember that this routine may be called many times and should not do destructive operations on the context state
pdData: PDData ← NARROW[BiScrollers.ClientDataOfViewer[self]];
WITH whatChanged SELECT FROM
handle: PDImageReader.Handle => {
t: ImagerPixelMap.PixelMap = handle.pixelMap;
w: ImagerPixelMap.DeviceRectangle = ImagerPixelMap.Window[t];
IF w.sSize > 0 THEN {
context.SetColor[Imager.black];
ImagerBackdoor.DrawBits[context: context, base: handle.pixelMap.refRep.pointer, wordsPerLine: handle.pixelMap.refRep.rast, sMin: t.sMin, fMin: t.fMin, fSize: t.fSize, sSize: t.sSize, tx: w.fMin, ty: pdData.pdhandle.herald.imageSSize-w.sMin+w.sSize];
};
};
ENDCASE => ERROR;
};
IF pdData.scalePD THEN context.Scale2T[pdData.scaleFactors]; -- do this only once (outside of paintAction)
Reset the handle in pdData to the image in pdData.pageNumber, then interpret the image
IF PDImageReader.ResetToImage[pdData: pdData]#NIL THEN IF NOT pdData.abort THEN [] ← PDImageReader.InterpretImage[handle: pdData.pdhandle, viewer: viewer, paintAction: PaintIt]; --this guy eventually calls PaintIt.
};
PaintAIS: PROC [context: Imager.Context, aisData: AISData] = {
state: PreView.AISState ← aisData.state;
IF state.active THEN {
Imager.SetSampledColor[context: context, m: ImagerTransformation.Scale[1.0], pa: state.pa, colorOperator: state.op];
Imager.MaskBox[context, [0,0, state.pixels, state.scans]];
};
};
PaintGriffin: PROC [context: Imager.Context, gData: GData] = {
SELECT TRUE FROM
gData.switches['M] => { -- M switch means use no ImagerMemory
context.ScaleT[pointsPerMica]; -- establish Griffin coord. system
context.SetStrokeJoint[round]; -- avoid miter problems
GriffinImageUtils.GriffinToImagerCalls[context, gData.image];
};
gData.pageInMem=gData.pageNumber => { -- iMemContext is current
ImagerMemory.Replay[c: gData.iMemContext, into: context];
};
gData.pageInMem#gData.pageNumber => { -- memory context may be cached
PageCompareProc: FunctionCache.CompareProc = {
PROC [argument: Domain] RETURNS [good: BOOL];
RETURN[NARROW[argument, CacheKey]^=cacheKey^];
};
cacheKey: CacheKey ← NEW[CacheKeyRep ← [gData.fileInfo.fullFName, gData.fileInfo.created, gData.pageNumber]];
IF (gData.iMemContext ← NARROW[FunctionCache.Lookup[x: globalCache, compare: PageCompareProc, clientID: $PVPage].value, Imager.Context])#NIL THEN { --hit
ImagerMemory.Replay[c: gData.iMemContext, into: context];
gData.pageInMem ← gData.pageNumber;
}
ELSE { -- miss
gData.iMemContext ← ImagerMemory.NewMemoryContext[];
gData.iMemContext.ScaleT[pointsPerMica]; -- establish Griffin coord. system
gData.iMemContext.SetStrokeJoint[round]; -- avoid miter problems
GriffinImageUtils.GriffinToImagerCalls[gData.iMemContext, gData.image];
FunctionCache.Insert[x: globalCache, argument: cacheKey, value: gData.iMemContext, size: ImagerMemory.GetContextSize[gData.iMemContext], clientID: $PVPage];
gData.pageInMem ← gData.pageNumber;
ImagerMemory.Replay[c: gData.iMemContext, into: context];
};
};
ENDCASE => NULL;
};
PVPaint: PUBLIC ENTRY ViewerClasses.PaintProc = TRUSTED {
[self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL];
ENABLE UNWIND => NULL;
data: Data ← NARROW[BiScrollers.ClientDataOfViewer[self]];
data.process ← Process.GetCurrent[];
DoPainting[context: context, data: data, viewer: self ! UNWIND => PreView.ResetProcess[data]];
IF data.bBox.active THEN PreView.PVFeedback[data: data, v: self, op: paint ! UNWIND => PreView.ResetProcess[data]];
PreView.ResetProcess[data]; -- this call is to a PreViewTool ENTRY PROC so it is synchronized with PreViewTool.StopIfYouCan
};
PVDestroy: PUBLIC ENTRY ViewerClasses.DestroyProc = {
[self: Viewer]
ENABLE UNWIND => NULL;
data: Data ← NARROW[BiScrollers.ClientDataOfViewer[self]];
PreView.PVListRemove[ref: data];
WITH data SELECT FROM
ipData: IPData => {
ipData.ipMaster ← NIL; -- Interpress.Close doesn't exist
};
pressData: PressData => {
ShowPress.Close[pressData.presshandle];
pressData.presshandle ← NIL;
};
pdData: PDData => {
PDFileReader.Close[pdData.pdhandle];
pdData.pdhandle ← NIL;
pdData.image ← NIL;
};
aisData: AISData => {
AIS.CloseFile[aisData.state.openFile];
aisData.state.openFile ← NIL;
aisData.state.pa ← NIL;
aisData.state.op ← NIL;
};
gData: GData => {
gData.image ← NIL;
};
ENDCASE => ERROR; -- can't happen
SafeStorage.ReclaimCollectibleObjects[suspendMe: FALSE]; -- good citizenship
};
This next PROC is for the benefit of TSetter, which will call it when the InputFocus is in the previewer and the TSetter GET button is clicked
PVGetName: PUBLIC ViewerClasses.GetProc = { --[self: Viewer, op: ATOM] RETURNS [data: REF ANY]
d: Data ← NARROW[BiScrollers.ClientDataOfViewer[self]];
RETURN[d.container.name];
};
PVBasicTransformProc: PUBLIC PROC [bs: BiScrollers.BiScroller] RETURNS [t: Geom2D.Transform] = {
BiScrollers.TransformGenerator
iv: ViewerClasses.Viewer ← bs.style.QuaViewer[bs: bs, inner: TRUE];
data: Data ← NARROW[BiScrollers.ClientDataOfViewer[iv]];
t ← Geom2D.id.PostTranslate[[0, iv.ch - MAX[data.pageHeight, data.pageWidth]]];
t ← Geom2D.id.PostTranslate[[0, iv.ch - data.pageHeight]];
Make client <0, max> appear at the upper left corner of viewer.
};
PVExtremaProc: PUBLIC PROC [clientData: REF ANY, direction: Geom2D.Vec] RETURNS [min, max: Geom2D.Vec] --BiScrollers.ExtremaProc-- = {
This proc is required by BiScrollers to return the extremes of the displayed data
data: Data ← NARROW[clientData];
area: Geom2D.Rect ← [x: 0.0, y: 0.0, w: data.pageWidth, h: data.pageHeight];
[min, max] ← Geom2D.ExtremaOfRect[r: area, n: direction];
};
END.