<<>> <> <> <> <> <> DIRECTORY BasicTime, BiScrollers, FunctionCache, FS, Geom2D, Imager, ImagerArtwork, ImagerBackdoor, ImagerColor, ImagerInterpress, ImagerMemory, ImagerPixel, ImagerPixelArray, ImagerTransformation, InputFocus, InterpressInterpreter, IO, MessageWindow, PPreView, PPreViewClient, RasterEncodingStandardIO, Real, Rope, SF, Vector2, ViewerAbort, ViewerClasses, ViewerGroupLocks, ViewerLocks, ViewerPrivate, ViewerTools; PPreViewImpl: CEDAR MONITOR IMPORTS BiScrollers, FunctionCache, FS, Geom2D, Imager, ImagerArtwork, ImagerBackdoor, ImagerColor, ImagerInterpress, ImagerMemory, ImagerPixel, ImagerPixelArray, ImagerTransformation, InputFocus, InterpressInterpreter, IO, MessageWindow, PPreView, PPreViewClient, Real, Rope, SF, Vector2, ViewerAbort, ViewerGroupLocks, ViewerLocks, ViewerPrivate, ViewerTools EXPORTS PPreView ~ BEGIN Data: TYPE ~ PPreView.Data; IPData: TYPE ~ PPreView.IPData; AISData: TYPE ~ PPreView.AISData; RESData: TYPE ~ PPreView.RESData; PaintOp: TYPE ~ PPreView.PaintOp; Context: TYPE ~ Imager.Context; Rectangle: TYPE ~ Imager.Rectangle; Transformation: TYPE ~ ImagerTransformation.Transformation; Viewer: TYPE ~ ViewerClasses.Viewer; Vec: TYPE ~ Geom2D.Vec; PProc: TYPE ~ PROC [Context]; 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; oneOverPointsPerInch: REAL ~ 1.0/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; useMessageWindow: BOOL ¬ FALSE; Message: PROC [message: Rope.ROPE, clearFirst, blink: BOOL ¬ TRUE] ~ { MessageWindow.Append[Rope.Concat["PreView: ", message], clearFirst]; IF blink THEN MessageWindow.Blink[]; }; RectangleFromXYs: PUBLIC PROC [x0, y0, x1, y1: REAL] RETURNS [r: Imager.Rectangle] ~ { IF x0 > x1 THEN {t: REAL ¬ x0; x0 ¬ x1; x1 ¬ t}; IF y0 > y1 THEN {t: REAL ¬ y0; y0 ¬ y1; y1 ¬ t}; r ¬ [x0, y0, x1-x0, y1-y0]; }; PVNotify: PUBLIC ViewerClasses.NotifyProc ~ {-- [self: Viewer, input: LIST OF REF ANY] ENABLE UNWIND => NULL; NewMouse: PROC ~ { IF InputFocus.GetInputFocus[].owner # self THEN InputFocus.SetInputFocus[self]; IF b.all THEN { dif: Imager.VEC ¬ Vector2.Sub[[mouseX, mouseY], b.origin]; b.p0 ¬ Vector2.Add[b.p0, dif]; b.p1 ¬ Vector2.Add[b.p1, dif]; b.origin ¬ [mouseX, mouseY]; } ELSE { IF b.x0 THEN b.p0.x ¬ mouseX ELSE IF b.x1 THEN b.p1.x ¬ mouseX; IF b.y0 THEN b.p0.y ¬ mouseY ELSE IF b.y1 THEN b.p1.y ¬ mouseY; }; b.rect ¬ RectangleFromXYs[b.p0.x, b.p0.y, b.p1.x, b.p1.y]; PVFeedback[data: data, v: self, op: change]; --paint grey box }; mouseX, mouseY: REAL ¬ 0.0; data: Data ¬ NARROW[BiScrollers.ClientDataOfViewer[self]]; b: PPreView.BBoxState ¬ data.bBox; IF data.painting THEN RETURN; -- ignore input while painting 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 b.active THEN PVFeedback[data: data, v: self, op: remove]; -- kill grey bbox b­ ¬ []; -- initialization default values cancel current clipping rectangle }; $Down => { -- determine user interaction (corner, edge, or center?) once: BOOL ¬ FALSE; wOver4, hOver4: REAL; b.x0 ¬ b.x1 ¬ b.y0 ¬ b.y1 ¬ b.all ¬ FALSE; IF NOT b.active THEN { b.x1 ¬ b.y1 ¬ TRUE; b.p0 ¬ b.p1 ¬ [mouseX, mouseY]; b.active ¬ TRUE; -- turn it on and leave it on RETURN; }; IF b.p0.x > b.p1.x THEN {t: REAL ¬ b.p0.x; b.p0.x ¬ b.p1.x; b.p1.x ¬ t}; IF b.p0.y > b.p1.y THEN {t: REAL ¬ b.p0.y; b.p0.y ¬ b.p1.y; b.p1.y ¬ t}; wOver4 ¬ (b.p1.x-b.p0.x)/4; hOver4 ¬ (b.p1.y-b.p0.y)/4; SELECT mouseX FROM < b.p0.x+wOver4 => b.x0 ¬ TRUE; > b.p1.x-wOver4 => b.x1 ¬ TRUE; ENDCASE => once ¬ TRUE; SELECT mouseY FROM < b.p0.y+hOver4 => b.y0 ¬ TRUE; > b.p1.y-hOver4 => b.y1 ¬ TRUE; ENDCASE => IF once THEN { b.all ¬ TRUE; b.origin ¬ [mouseX, mouseY]; }; NewMouse[]; }; $Move => NewMouse[]; $EndMove, $ShowSelection => PPreViewClient.ShowSelection[data, self]; ENDCASE => NULL; z: BiScrollers.ClientCoords => { -- TYPE = REF Vec mouseX ¬ z.x; -- mouseX in BiScroller coords mouseY ¬ z.y; -- mouseY in BiScroller coords IF useMessageWindow THEN Message[IO.PutFR["%g, %g", IO.real[z.x], IO.real[z.y]], TRUE, FALSE]; }; ENDCASE => Message["unknown input Notify operation"]; ENDLOOP; }; PVFeedback: PUBLIC PROC [data: Data, v: Viewer, op: PPreView.PaintOp ¬ paint] ~ { ENABLE UNWIND => data.bBox­ ¬ []; -- kill selection in desperation Action: PROC [context: Context] ~ { -- this context in viewer coords Show: PROC [r: Rectangle, updateDimensions: BOOL ¬ FALSE] ~ { ctv: Transformation ¬ PPreViewClient.GetTransformation[v]; r ¬ ImagerTransformation.TransformRectangle[m: ctv, r: r]; Imager.MaskRectangle[context, [r.x-5.0, r.y-5.0, 5.0, r.h+10.0]]; -- left side Imager.MaskRectangle[context, [r.x+r.w, r.y-5.0, 5.0, r.h+10.0]]; -- right side Imager.MaskRectangle[context, [r.x, r.y-5.0, r.w, 5.0]]; -- top side Imager.MaskRectangle[context, [r.x, r.y+r.h, r.w, 5.0]]; -- bottom side IF updateDimensions THEN { ViewerTools.SetContents[data.selectionWViewer, IO.PutFR1["%6.3f", IO.real[oneOverPointsPerInch*r.w]]]; ViewerTools.SetContents[data.selectionHViewer, IO.PutFR1["%6.3f", IO.real[oneOverPointsPerInch*r.h]]]; }; }; IF data.bBox.prev = data.bBox.rect AND op = change THEN RETURN; -- no change Imager.SetColor[context, xorStipple]; SELECT op FROM paint => Show[data.bBox.rect]; remove => { IF data.abort THEN data.abort ¬ FALSE ELSE IF data.bBox.prev # [0., 0., 0., 0.] THEN Show[data.bBox.prev]; -- remove old box data.bBox.prev ¬ [0.0, 0.0, 0.0, 0.0]; }; change => { IF data.abort THEN data.abort ¬ FALSE ELSE IF data.bBox.prev # [0., 0., 0., 0.] THEN Show[data.bBox.prev]; -- remove old box Show[data.bBox.rect, TRUE]; -- show new box }; ENDCASE => Message["unknown feedback operation"]; data.bBox.prev ¬ data.bBox.rect; }; DirectPaint[viewer: v, action: Action]; }; DoFileOps: PUBLIC PROC [ op: PPreView.BBoxOp, viewer: Viewer, data: Data, fileName: Rope.ROPE, clip: BOOL ¬ TRUE] ~ { ctv: Transformation ¬ PPreViewClient.GetTransformation[viewer]; r: Rectangle ¬ ImagerTransformation.TransformRectangle[m: ctv, r: data.bBox.rect]; clipRect: REF Rectangle ¬ IF NOT clip THEN NIL ELSE NEW[Rectangle ¬ ImagerTransformation.InverseTransformRectangle[ctv, r]]; SELECT op FROM stuff => { -- stuff requires clip to be TRUE and bBox valid DoStuff: PROC [context: Context] ~ { DoIt: PROC ~ { Imager.ConcatT[context, ctv]; DoPainting[context: context, data: data, viewer: viewer, clipRect: clipRect]; }; Imager.TranslateT[context, [-r.x, -r.y]]; Imager.DoSaveAll[context, DoIt]; IF data.stuffWithBorders THEN { Imager.SetStrokeWidth[context, borderWidth]; Imager.SetGray[context, 1]; Imager.MaskVector[context, [r.x, r.y], [r.x, r.y+r.h]]; -- left side Imager.MaskVector[context, [r.x, r.y], [r.x+r.w, r.y]]; -- bottom side Imager.MaskVector[context, [r.x+r.w, r.y], [r.x+r.w, r.y+r.h]]; -- right side Imager.MaskVector[context, [r.x, r.y+r.h], [r.x+r.w, r.y+r.h]]; -- top side }; }; m: Transformation ¬ ImagerArtwork.Points[]; ImagerArtwork.PasteArtwork[DoStuff, [0, 0, r.w, r.h], m, TRUE, data.stuffWithFit]; }; ipMaster => { DoIPMaster: PROC [context: Context] ~ { Imager.ScaleT[context, metersPerPoint]; IF clip THEN { Imager.TranslateT[context, [-r.x, -r.y]]; Imager.ClipRectangle[context, r]; }; Imager.ConcatT[context, ctv]; DoPainting[context: context, data: data, viewer: viewer, clipRect: clipRect]; }; ref: ImagerInterpress.Ref ¬ ImagerInterpress.Create[fileName: fileName ! FS.Error => { Message[Rope.Concat[error.explanation, " creating IPMaster"]]; GOTO Quit; }]; ImagerInterpress.DoPage[self: ref, action: DoIPMaster ! Imager.Error => { Message[Rope.Concat[error.explanation, " creating IPMaster"]]; ImagerInterpress.Close[ref]; GOTO Quit; }]; ImagerInterpress.Close[ref]; EXITS Quit => data.abort ¬ TRUE; }; ENDCASE; }; DirectPaint: PROC [viewer: 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: Context, data: Data, viewer: Viewer, clipRect: REF Rectangle ¬ NIL] ~ { IF data.clientPaint # NIL THEN [] ¬ data.clientPaint[viewer, context, data.clientData, TRUE] ELSE WITH data SELECT FROM ipData: IPData => PaintIP[context: context, ipData: ipData]; --kills context variables, but ok resData: RESData => PaintRES[context: context, resData: resData, clipRect: clipRect]; aisData: AISData => PaintAIS[context: context, aisData: aisData, clipRect: clipRect]; < PaintPress[context: context, pressData: pressData];>> < PaintPD[context: context, pdData: pdData, viewer: viewer];>> < PaintGriffin[context: context, gData: gData];>> < PaintGG[context: context, pvData: pvData];>> ENDCASE => Message["unknown paint operation"]; }; PaintIP: PROC [context: Context, ipData: IPData] ~ { ENABLE { Imager.Error => { Message[Rope.Concat[error.explanation, ". Use -M switch to preview this file."]]; GOTO Null; -- return cleanly from painting }; Imager.Warning => RESUME; }; IF ipData.ipMaster # NIL THEN SELECT TRUE FROM ipData.switches['M] => { -- M switch means use no ImagerMemory selected: BOOL ¬ FALSE; Imager.SetGray[context, 1]; Imager.ScaleT[context, pointsPerMeter]; -- establish IP coord system InterpressInterpreter.DoPage[ipData.ipMaster, ipData.pageNumber, context, PPreView.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 ~ { <> 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[]; Imager.SetGray[ipData.iMemContext, 1]; Imager.ScaleT[ipData.iMemContext, pointsPerMeter]; -- establish IP coord system InterpressInterpreter.DoPage[ ipData.ipMaster, ipData.pageNumber, ipData.iMemContext, PPreView.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; EXITS Null => NULL; }; < { Message[Atom.GetPName[code]]; pressData.abort ¬ TRUE; CONTINUE; }]; }; >> <> pdData: PDData ¬ NARROW[BiScrollers.ClientDataOfViewer[self]]; WITH whatChanged SELECT FROM handle: PDImageReader.Handle => Imager.DrawBitmap[ context: context, bitmap: handle.bitmap, position: [0,pdData.pdhandle.herald.imageSSize]]; ENDCASE => ERROR; }; IF pdData.scalePD THEN Imager.Scale2T[context, pdData.scaleFactors];-- do only once (outside of paintAction) <> IF PDImageReader.ResetToImage[pdData: pdData]#NIL THEN [] ¬ PDImageReader.InterpretImage[handle: pdData.pdhandle, viewer: viewer, paintAction: PaintIt]; --this guy eventually calls PaintIt. <> }; >> ClipPixelArray: PROC [pa: ImagerPixelArray.PixelArray, clipRectangle: Rectangle] RETURNS [new: ImagerPixelArray.PixelArray] ~ { MaxSample: PROC [i: NAT] RETURNS [ImagerPixel.Sample] ~ { RETURN [ImagerPixelArray.MaxSampleValue[pa, i]] }; paRect: Rectangle ¬ ImagerTransformation.InverseTransformRectangle[m: pa.m, r: clipRectangle]; paBox: SF.Box = SF.Intersect[[max: [pa.sSize, pa.fSize]], [min: [s: Real.Floor[paRect.x], f: Real.Floor[paRect.y]], max: [s: Real.Ceiling[paRect.x+paRect.w], f: Real.Ceiling[paRect.y+paRect.h]]]]; pixelMap: ImagerPixel.PixelMap = ImagerPixel.NewPixelMap[samplesPerPixel: pa.samplesPerPixel, box: paBox, maxSample: MaxSample]; FOR i: NAT IN [0..pixelMap.samplesPerPixel) DO ImagerPixelArray.Transfer[pa: pa, i: i, s: paBox.min.s, f: paBox.min.f, dst: pixelMap[i], dstMin: paBox.min, size: SF.Size[paBox]]; ENDLOOP; new ¬ ImagerPixelArray.FromPixelMap[pixelMap: pixelMap, box: paBox, scanMode: [slow: right, fast: up], immutable: TRUE]; new.m ¬ ImagerTransformation.PreTranslate[pa.m, [paBox.min.s, paBox.min.f]]; }; PaintAIS: PROC [context: Context, aisData: AISData, clipRect: REF Rectangle] ~ { state: PPreView.AISState ¬ aisData.state; pa: ImagerPixelArray.PixelArray ¬ state.pa; IF state.active THEN { IF clipRect#NIL THEN { pa ¬ ClipPixelArray[pa, clipRect­] }; IF state.op = NIL THEN { Imager.SetGray[context, 1]; Imager.MaskPixel[context: context, pa: pa] } ELSE Imager.DrawSampledColor[context: context, pa: pa, colorOperator: state.op]; }; }; PaintRES: PROC [context: Context, resData: RESData, clipRect: REF Rectangle] ~ { res: RasterEncodingStandardIO.RES = resData.image; maskImage: ImagerPixelArray.PixelArray ¬ res.maskImage; colorImage: ImagerPixelArray.PixelArray ¬ res.colorImage; MaskPABounds: PROC [pa: ImagerPixelArray.PixelArray] ~ { Inner: PROC ~ { Imager.ConcatT[context, pa.m]; Imager.MaskRectangle[context, [0, 0, pa.sSize, pa.fSize]]; }; Imager.DoSave[context, Inner]; }; IF clipRect#NIL THEN { IF maskImage # NIL THEN maskImage ¬ ClipPixelArray[maskImage, clipRect­]; IF colorImage # NIL THEN colorImage ¬ ClipPixelArray[colorImage, clipRect­]; }; SELECT TRUE FROM colorImage # NIL AND maskImage=NIL AND res.colorOperator # NIL AND res.colorOperator.samplesPerPixelIn = 1 AND ImagerPixelArray.MaxSampleValue[colorImage, 0] = 1 => { sample: CARDINAL ¬ 0; Pixel: PROC [NAT] RETURNS [REAL] ~ { RETURN [sample] }; maskImage ¬ colorImage; colorImage ¬ NIL; sample ¬ 0; Imager.SetColor[context, ImagerColor.ColorFromPixel[res.colorOperator, Pixel]]; MaskPABounds[maskImage]; sample ¬ 1; Imager.SetColor[context, ImagerColor.ColorFromPixel[res.colorOperator, Pixel]]; }; colorImage = NIL OR res.colorOperator = NIL => { Imager.SetGray[context, 1]; }; ENDCASE => { Imager.SetSampledColor[context, colorImage, NIL, res.colorOperator] }; IF maskImage=NIL THEN Imager.MaskRectangle[context, ImagerTransformation.TransformRectangle[m: colorImage.m, r: [0, 0, colorImage.sSize, colorImage.fSize]]] ELSE Imager.MaskPixel[context, maskImage]; }; < { -- M switch means use no ImagerMemory Imager.ScaleT[context, pointsPerMica]; -- establish Griffin coord. system Imager.SetStrokeJoint[context, 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 ~ { <> 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, Context])#NIL THEN { --hit ImagerMemory.Replay[c: gData.iMemContext, into: context]; gData.pageInMem ¬ gData.pageNumber; } ELSE { -- miss gData.iMemContext ¬ ImagerMemory.NewMemoryContext[]; Imager.ScaleT[gData.iMemContext, pointsPerMica]; -- establish Griffin coord. system Imager.SetStrokeJoint[gData.iMemContext, 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; }; >> < { -- M switch means use no ImagerMemory Imager.ScaleT[context, pointsPerInch]; -- establish GG coord. system GGForPV.DrawData[context, pvData.data]; }; pvData.pageInMem=pvData.pageNumber => { -- iMemContext is current ImagerMemory.Replay[c: pvData.iMemContext, into: context]; }; pvData.pageInMem#pvData.pageNumber => { -- memory context may be cached PageCompareProc: FunctionCache.CompareProc ~ { <> RETURN[NARROW[argument, CacheKey]­ = cacheKey­]; }; cacheKey: CacheKey ¬ NEW[CacheKeyRep ¬ [pvData.fileInfo.fullFName, pvData.fileInfo.created, pvData.pageNumber]]; IF (pvData.iMemContext ¬ NARROW[FunctionCache.Lookup[x: globalCache, compare: PageCompareProc, clientID: $PVPage].value, Context])#NIL THEN { --hit ImagerMemory.Replay[c: pvData.iMemContext, into: context]; pvData.pageInMem ¬ pvData.pageNumber; } ELSE { -- miss pvData.iMemContext ¬ ImagerMemory.NewMemoryContext[]; Imager.ScaleT[pvData.iMemContext, pointsPerInch]; -- establish GG coord. system GGForPV.DrawData[pvData.iMemContext, pvData.data]; FunctionCache.Insert[x: globalCache, argument: cacheKey, clientID: $PVPage, size: ImagerMemory.GetContextSize[pvData.iMemContext], value: pvData.iMemContext]; pvData.pageInMem ¬ pvData.pageNumber; ImagerMemory.Replay[c: pvData.iMemContext, into: context]; }; }; ENDCASE => NULL; }; >> PVPaint: PUBLIC ENTRY ViewerClasses.PaintProc ~ TRUSTED { <<[self: Viewer, context: Context, whatChanged: REF ANY, clear: BOOL];>> ENABLE ABORTED => GOTO Finished; -- return and unlock viewers data: Data ¬ NARROW[BiScrollers.ClientDataOfViewer[self]]; BEGIN ENABLE UNWIND => data.painting ¬ FALSE; -- abort is set, painting gets cleared PVPaintWithAbort: PROC ~ TRUSTED { data.abort ¬ data.painting ¬ TRUE; DoPainting[context: context, data: data, viewer: self]; data.abort ¬ data.painting ¬ FALSE; IF data.bBox.active THEN PVFeedback[data: data, v: self, op: paint]; }; ViewerAbort.CallWithAbortEnabled[self, PVPaintWithAbort]; END; EXITS Finished => NULL; }; PVDestroy: PUBLIC ENTRY ViewerClasses.DestroyProc ~ { -- [self: Viewer] ENABLE UNWIND => NULL; data: Data ¬ NARROW[BiScrollers.ClientDataOfViewer[self]]; WITH data SELECT FROM ipData: IPData => { IF ipData.ipMaster # NIL THEN InterpressInterpreter.Close[master: ipData.ipMaster]; ipData.ipMaster ¬ NIL; }; resData: RESData => { resData.image ¬ NIL; }; aisData: AISData => { <> <> aisData.state.pa ¬ NIL; aisData.state.op ¬ NIL; }; < {>> <> <> <<};>> < {>> <> <> <> <<};>> < {>> <> <<};>> < {>> <> <<};>> ENDCASE => Message["unknown destroy operation"]; <> }; PVGetName: PUBLIC ViewerClasses.GetProc ~ { <<[self: Viewer, op: ATOM] RETURNS [data: REF ANY]>> <> RETURN[NARROW[BiScrollers.ClientDataOfViewer[self], Data].container.file]; }; PVBasicTransformProc: PUBLIC PROC [bs: BiScrollers.BiScroller] RETURNS [Transformation] ~ { <> actionArea: Viewer ¬ bs.style.QuaViewer[bs: bs, inner: TRUE]; data: Data ¬ NARROW[BiScrollers.ClientDataOfViewer[actionArea]]; <> height: REAL ¬ data.pageHeight; width: REAL ¬ data.pageWidth; ww: REAL ¬ actionArea.ww; wh: REAL ¬ actionArea.wh; RETURN[ImagerTransformation.Translate[[(ww-width)/2.0, (wh-height)/2.0]]]; }; PVExtremaProc: PUBLIC PROC [clientData: REF ANY, direction: Vec] RETURNS [min, max: Vec] ~ { --BiScrollers.ExtremaProc <> 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. .. Old Preview.tip SELECT TRIGGER FROM Mouse WHILE Red Down => Coords, Move; Red Down => SELECT ENABLE FROM Yellow Down => Abort; ENDCASE => Coords, Move; Yellow Down => Abort; Red Up => EndMove; Blue Up => ShowSelection; ENDCASE.