<> <> <> <> <> <> DIRECTORY AtomButtons, AtomButtonsTypes, BiScrollers, BufferedRefresh, Buttons, Commander, CommandTool, Cursors, Feedback, FileNames, FunctionCache, Geom2D, GGAlign, GGBasicTypes, GGBoundBox, GGCaret, GGContainer, GGEvent, GGFont, GGGravity, GGInterfaceTypes, GGMeasure, GGMenus, GGModelTypes, GGMouseEvent, GGMultiGravity, GGOutline, GGRefresh, GGScene, GGSegmentTypes, GGSessionLog, GGState, GGUserInput, GGViewerOps, GGWindow, GraphicsButton, Icons, Imager, ImagerTransformation, IO, Rope, SlackProcess, Terminal, TiogaButtons, TIPUser, Vectors2d, ViewerClasses, ViewerOps; GGWindowImpl: CEDAR PROGRAM IMPORTS AtomButtons, BiScrollers, BufferedRefresh, Buttons, Commander, CommandTool, Cursors, Feedback, FileNames, FunctionCache, Geom2D, GGAlign, GGBoundBox, GGCaret, GGContainer, GGEvent, GGFont, GGGravity, GGMeasure, GGMenus, GGMouseEvent, GGMultiGravity, GGOutline, GGRefresh, GGScene, GGSessionLog, GGState, GGUserInput, GGViewerOps, GraphicsButton, Icons, Imager, ImagerTransformation, Rope, SlackProcess, Terminal, TIPUser, Vectors2d, ViewerOps EXPORTS GGWindow = BEGIN Caret: TYPE = REF CaretObj; CaretObj: TYPE = GGInterfaceTypes.CaretObj; CameraDataObj: TYPE = GGModelTypes.CameraDataObj; Filters: TYPE = REF FiltersObj; FiltersObj: TYPE = GGInterfaceTypes.FiltersObj; Slice: TYPE = GGModelTypes.Slice; ImagerProc: TYPE = GGInterfaceTypes.ImagerProc; Outline: TYPE = GGModelTypes.Outline; Point: TYPE = GGBasicTypes.Point; Scene: TYPE = REF SceneObj; SceneObj: TYPE = GGModelTypes.SceneObj; Segment: TYPE = GGSegmentTypes.Segment; Sequence: TYPE = GGModelTypes.Sequence; Traj: TYPE = GGModelTypes.Traj; Viewer: TYPE = ViewerClasses.Viewer; ForegroundParts: TYPE = GGWindow.ForegroundParts; GGData: TYPE = REF GargoyleDataObj; GargoyleDataObj: TYPE = GGInterfaceTypes.GargoyleDataObj; buttonAlign: INTEGER _ 2; -- align popUp and standard buttons in first button line entryHeight: CARDINAL = 15; -- height of a line of items entryVSpace: CARDINAL = 2; -- vertical leading between lines entryHSpace: CARDINAL = 2; -- horizontal space between items on a line column1: CARDINAL = 200; -- horizontal space between margin and column 1; column2: CARDINAL = 250; -- horizontal space between margin and column 2. column3: CARDINAL = 500; -- horizontal space between margin and column 3; nameSize: CARDINAL = 140; smallNumberSize: CARDINAL = 45; numberSize: CARDINAL = 80; bigNumberSize: CARDINAL = 160; pointSize: CARDINAL = 160; GGActionAreaPaint: PROC [self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE] = { <> PaintEntireViewer: PROC = CHECKED { BufferedRefresh.FitSandwichToScreen[ggData.refresh.sandwich, self.cw, self.ch]; RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE]; }; clientToViewer: Imager.Transformation _ BiScrollers.GetStyle[].GetTransforms[BiScrollers.QuaBiScroller[self]].clientToViewer; ggData: GGData; IF whatChanged = NIL THEN { --we are being called by Window Manager <> ggData _ NARROW[BiScrollers.ClientDataOfViewer[self]]; BufferedRefresh.FitSandwichToScreen[ggData.refresh.sandwich, self.cw, self.ch]; IF ggData.refresh.clientToViewer#NIL AND ImagerTransformation.Equal[clientToViewer, ggData.refresh.clientToViewer] THEN { -- Window Motion RestoreScreenAndInvariants[paintAction: $ViewersPaintAllPlanes, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE]; } ELSE { -- Scrolling RestoreScreenAndInvariants[paintAction: $ViewersPaintEntireScene, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE]; }; } ELSE { ggData _ NARROW[whatChanged]; IF ggData.refresh.paintAction=$PaintEntireViewer THEN PaintEntireViewer[] ELSE GGRefresh.ActionAreaPaint[context, ggData.refresh.paintAction, ggData]; }; ggData.refresh.clientToViewer _ clientToViewer; -- for next time around }; RestoreScreenAndInvariants: PUBLIC PROC [paintAction: ATOM, ggData: GGData, remake: ForegroundParts _ triggerBag, backgndOK: BOOL _ FALSE, edited: BOOL _ TRUE, okToClearFeedback: BOOL] = { IF okToClearFeedback THEN Feedback.ClearHerald[ggData.feedback]; <> IF ggData.aborted[bags] THEN { GGAlign.SetStaticBags[ggData]; BufferedRefresh.SetLayerOK[ggData.refresh.sandwich, $Foreground, FALSE]; BufferedRefresh.SetLayerOK[ggData.refresh.sandwich, $Background, FALSE]; ggData.aborted[bags] _ FALSE; } ELSE { SELECT remake FROM none => NULL; triggerBag => { GGAlign.SetStaticBags[ggData]; BufferedRefresh.SetLayerOK[ggData.refresh.sandwich, $Foreground, FALSE]; }; objectBag => { GGAlign.FlushAlignBag[ggData.hitTest.alignBag]; GGAlign.FillStaticAlignBag[ggData.hitTest.triggerBag, ggData.hitTest.sceneBag, ggData.hitTest, NOT GGState.ShowAlignments[ggData], GGState.Midpoints[ggData], ggData.hitTest.alignBag]; BufferedRefresh.SetLayerOK[ggData.refresh.sandwich, $Foreground, FALSE]; }; bitMap => { BufferedRefresh.SetLayerOK[ggData.refresh.sandwich, $Foreground, FALSE]; }; sceneBag => { GGAlign.FlushTriggerBag[ggData.hitTest.sceneBag]; GGAlign.FillStaticSceneBag[ggData.scene, ggData.hitTest.sceneBag]; GGAlign.AddAllMidpoints[ggData.hitTest.sceneBag, GGState.Midpoints[ggData], ggData.hitTest.alignBag]; }; ENDCASE => ERROR; <> IF NOT backgndOK THEN BufferedRefresh.SetLayerOK[ggData.refresh.sandwich, $Background, FALSE]; -- don't set it TRUE if it is FALSE. It knows better than you do }; <> IF edited THEN { IF NOT ggData.outer.newVersion THEN { ggData.outer.icon _ IF ggData.outer.file=NIL THEN dirtyNoNameIcon ELSE dirtyIcon; ggData.outer.newVersion _ TRUE; ViewerOps.PaintViewer[ggData.outer, caption]; }; }; ggData.refresh.paintAction _ paintAction; ViewerOps.PaintViewer[ viewer: ggData.actionArea, hint: client, whatChanged: ggData, clearClient: FALSE] }; MakeGGViewer: PROC [fileName: Rope.ROPE _ NIL] = { ggData: GGData _ CreateWindow[GGScene.CreateScene[], TRUE, FALSE, FileNames.CurrentWorkingDirectory[] ]; -- create brand new GG window IF fileName#NIL AND ~Rope.Equal[fileName, ""] THEN GGEvent.Get[event: LIST[$Get, fileName], clientData: ggData]; -- tell GGEvent.Get to try for this file ViewerOps.PaintViewer[ggData.outer, caption]; -- just to get the icon to appear }; CreateWindow: PUBLIC PROC [scene: Scene, iconic: BOOL, paint: BOOL, workingDirectory: Rope.ROPE] RETURNS [ggData: GGData] = { tV: Viewer; ggData _ NEW[GargoyleDataObj]; ggData.currentWDir _ workingDirectory; ggData.originalWDir _ originalWDir; --global ggData.hitTest _ NEW[FiltersObj]; ggData.hitTest.triggerBag _ GGAlign.CreateTriggerBag[]; ggData.hitTest.sceneBag _ GGAlign.CreateTriggerBag[]; ggData.hitTest.alignBag _ GGAlign.CreateAlignBag[]; ggData.refresh.lineCache _ FunctionCache.Create[maxEntries: 200]; ggData.scene _ scene; ggData.caret _ NEW[CaretObj]; ggData.anchor _ NEW[CaretObj]; ggData.camera _ NEW[CameraDataObj]; GGMouseEvent.InitializeFSM[ggData]; ggData.drag.savedCaret _ NEW[CaretObj]; ggData.drag.selectState _ none; ggData.drag.extendMode _ none; ggData.refresh.startBoundBox _ GGBoundBox.NullBoundBox[]; ggData.refresh.sandwich _ GGRefresh.CreateSandwich[]; ggData.defaults _ NEW[GGInterfaceTypes.DefaultDataObj _ [strokeColor: Imager.black, fillColor: GGOutline.fillColor, font: GGFont.DefaultDefaultFontData[] ] ]; <> ggData.outer _ GGContainer.Create[ info: [ name: "Gargoyle", menu: NIL, data: ggData, iconic: TRUE, column: left, scrollable: FALSE, icon: noNameIcon ], paint: FALSE]; BuildActionArea[ggData]; ggData.height _ 0; tV _ BiScrollers.CreateScale[ [parent: ggData.outer, wx: 0, wy: buttonAlign, border: FALSE], ggData.biScroller]; tV _ BiScrollers.CreateRotate[ [parent: ggData.outer, wx: tV.wx+tV.ww+entryHSpace, wy: buttonAlign, border: FALSE], ggData.biScroller]; tV _ BiScrollers.CreateFit[ [parent: ggData.outer, wx: tV.wx+tV.ww+entryHSpace, wy: buttonAlign, border: FALSE], ggData.biScroller]; tV _ BiScrollers.CreateReset[ [parent: ggData.outer, wx: tV.wx+tV.ww+entryHSpace, wy: buttonAlign, border: FALSE], ggData.biScroller]; tV _ BiScrollers.CreateEdge[ [parent: ggData.outer, wx: tV.wx+tV.ww+entryHSpace, wy: buttonAlign, border: FALSE], ggData.biScroller]; tV _ BiScrollers.CreatePrev[ [parent: ggData.outer, wx: tV.wx+tV.ww+entryHSpace, wy: buttonAlign, border: FALSE], ggData.biScroller]; tV _ Buttons.Create[info: [parent: ggData.outer, name: "CenterSel", wx: tV.wx+tV.ww+entryHSpace, wy: 0, border: FALSE], proc: CenterSel, clientData: ggData, paint: FALSE]; tV _ Buttons.Create[info: [parent: ggData.outer, name: "FitSel", wx: tV.wx+tV.ww+entryHSpace, wy: 0, border: FALSE], proc: FitSel, clientData: ggData, paint: FALSE]; ggData.height _ entryHeight; <> ggData.slackHandle _ SlackProcess.Create[queueSize: 50, logSize: 50, optimizeProc: OptimizeQueue, loggingProc: GGSessionLog.EnterAction, abortProc: GGAbortProc, abortData: ggData, abortViewer: ggData.actionArea]; GGMenus.BuildControlPanel[ggData, NIL]; tV _ BiScrollers.QuaViewer[ggData.biScroller]; ViewerOps.MoveViewer[tV, 0, ggData.height, ggData.outer.cw, ggData.outer.ch-ggData.height, FALSE]; ggData.outer.newVersion _ FALSE; ggData.gravityPool _ GGGravity.NewGravityPool[]; ggData.multiGravityPool _ GGMultiGravity.NewMultiGravityPool[]; SetCursorLooks[ggData.hitTest.gravityType, ggData]; -- assumes gravity is turned ON [] _ SlackProcess.EnableAborts[handle: ggData.slackHandle]; }; OptimizeQueue: SlackProcess.OptimizeProc = { <> <> atom, nextAtom: ATOM; action: LIST OF REF ANY; IF actionsOnQueue < 2 THEN RETURN [0]; skipActions _ 0; FOR i: NAT IN [0..actionsOnQueue-2] DO [----, ----, action] _ SlackProcess.GetQueueEntry[qeGen, i]; atom _ NARROW[action.first]; [----, ----, action] _ SlackProcess.GetQueueEntry[qeGen, i+1]; nextAtom _ NARROW[action.first]; IF atom = $During AND nextAtom = $During THEN { skipActions _ skipActions + 1; } ELSE RETURN; ENDLOOP; }; GGAbortProc: PROC [data: REF ANY] = { -- called by SlackProcess when user signals for abort ggData: GGData _ NARROW[data]; Feedback.Append[ggData.feedback, "Queued operations Aborted", oneLiner]; ggData.refresh.suppressRefresh _ FALSE; -- in case you killed FastPlayback ggData.aborted _ ALL[TRUE]; -- copies of aborted for all purposes }; CenterSel: Buttons.ButtonProc = { ggData: GGData _ NARROW[clientData]; caretPos: Point _ GGCaret.GetPoint[ggData.caret]; bBox: GGBoundBox.BoundBox _ GGBoundBox.BoundBoxOfSelected[scene: ggData.scene, selectClass: normal]; IF bBox.null THEN BiScrollers.Align[bs: ggData.biScroller, client: [variant: coord[x: caretPos.x, y: caretPos.y]], viewer: [variant: fraction[fx: 0.5, fy: 0.5]], paint: TRUE] ELSE BiScrollers.Align[bs: ggData.biScroller, client: [variant: coord[x: (bBox.loX+bBox.hiX)/2.0, y: (bBox.loY+bBox.hiY)/2.0]], viewer: [variant: fraction[fx: 0.5, fy: 0.5]], paint: TRUE]; }; FitSel: Buttons.ButtonProc = { ggData: GGData _ NARROW[clientData]; tbBox, vBox, tvBox: Geom2D.Rect; cToV: ImagerTransformation.Transformation; bBox: GGBoundBox.BoundBox _ GGBoundBox.BoundBoxOfSelected[scene: ggData.scene, selectClass: normal]; IF NOT bBox.null THEN { cToV _ BiScrollers.GetStyle[].GetTransforms[ggData.biScroller].clientToViewer; tbBox _ ImagerTransformation.TransformRectangle[cToV, GGBoundBox.RectangleFromBoundBox[bBox] ]; vBox _ BiScrollers.ViewportBox[ggData.biScroller]; tvBox _ ImagerTransformation.TransformRectangle[cToV, vBox]; BiScrollers.BoxScale[bs: ggData.biScroller, from: tbBox, to: tvBox]; <> }; }; <> <> <> <> <<[clientToViewer, ----] _ BiScrollers.GetStyle[].GetTransforms[BiScrollers.QuaBiScroller[viewer]];>> <<>> <> <<};>> <<>> ViewerToWorld: PUBLIC PROC [viewerPoint: Point, ggData: GGData] RETURNS [worldPoint: Point] = { vec: Imager.VEC; viewerToClient: Imager.Transformation _ BiScrollers.GetStyle[].GetTransforms[BiScrollers.QuaBiScroller[ggData.actionArea]].viewerToClient; vec _ ImagerTransformation.Transform[viewerToClient, [x: viewerPoint.x, y: viewerPoint.y] ]; worldPoint _ [ vec.x, vec.y]; }; WorldToViewer: PUBLIC PROC [worldPoint: Point, ggData: GGData] RETURNS [viewerPoint: Point] = { vec: Imager.VEC; clientToViewer: Imager.Transformation _ BiScrollers.GetStyle[].GetTransforms[BiScrollers.QuaBiScroller[ggData.actionArea]].clientToViewer; vec _ ImagerTransformation.Transform[clientToViewer, [x: worldPoint.x, y: worldPoint.y] ]; viewerPoint _ [ vec.x, vec.y]; }; NewGGViewers: Commander.CommandProc = { <<[cmd: Handle] RETURNS [result: REF _ NIL, msg: Rope.ROPE _ NIL];>> nameList, args: LIST OF Rope.ROPE _ NIL; argLength: NAT _ 0; switchChar: CHAR = '-; [list: args, length: argLength] _ CommandTool.ParseToList[cmd: cmd, starExpand: TRUE, switchChar: switchChar ! CommandTool.Failed => CONTINUE; ]; IF args = NIL OR argLength < 1 THEN { MakeGGViewer[fileName: NIL]; RETURN;}; FOR rl: LIST OF Rope.ROPE _ args, rl.rest UNTIL rl = NIL DO --open a GGViewer on each file [] _ MakeGGViewer[fileName: FileNames.ResolveRelativePath[rl.first]]; ENDLOOP; }; BuildActionArea: PRIVATE PROC [ggData: GGData] = { <> bs: BiScrollers.BiScroller _ BiScrollers.GetStyle[].CreateBiScroller[ class: actionAreaClass, info: [ parent: ggData.outer, wx: 0, wy: ggData.height, ww: ggData.outer.ww, wh: ggData.outer.wh, -- only initial values for ww and wh. They are constrained below data: ggData, border: FALSE, scrollable: FALSE], paint: FALSE ]; ggData.actionArea _ bs.QuaViewer[inner: TRUE]; ggData.biScroller _ bs; GGContainer.ChildXBound[ggData.outer, bs.QuaViewer[inner: FALSE]]; GGContainer.ChildYBound[ggData.outer, bs.QuaViewer[inner: FALSE]]; ggData.height _ ggData.height + ggData.actionArea.wh; }; GGExtremaProc: PROC [clientData: REF ANY, direction: Geom2D.Vec] RETURNS [min, max: Geom2D.Vec] --BiScrollers.ExtremaProc-- = { <> area: Geom2D.Rect; ggData: GGData _ NARROW[clientData]; bigBox: GGBoundBox.BoundBox _ GGBoundBox.BoundBoxOfBoxes[GGScene.BoundBoxesInScene[ggData.scene].list]; area _ IF bigBox#NIL THEN [x: bigBox.loX, y: bigBox.loY, w: bigBox.hiX-bigBox.loX, h: bigBox.hiY-bigBox.loY] ELSE [0.0, 0.0, 1.0, 1.0]; [min, max] _ Geom2D.ExtremaOfRect[r: area, n: direction]; }; NewCaretPos: PUBLIC PROC [ggData: GGData] = { caret0, caret1, caret2Pos: Point; distance, angle, slope, lineDist: REAL; caret0 _ ggData.measure.caret0; caret1 _ ggData.measure.caret1; caret2Pos _ Vectors2d.Scale[GGCaret.GetPoint[ggData.caret], 1.0/ggData.hitTest.scaleUnit]; distance _ GGMeasure.DistanceBetweenPoints[caret1, caret2Pos]; -- distance in scaleUnits angle _ GGMeasure.SmallestAngleOfPoints[caret0, caret1, caret2Pos]; slope _ GGMeasure.SlopeOfPoints[caret1, caret2Pos]; lineDist _ GGMeasure.DistanceFromPointToLine[caret2Pos, caret0, caret1]; -- line distance in scaleUnits ggData.measure.caret2Value _ caret2Pos; GGViewerOps.SetPoint[ggData.measure.caret2, caret2Pos]; ggData.measure.slopeViewValue _ slope; GGViewerOps.SetReal[ggData.measure.slopeView, slope]; ggData.measure.angleViewValue _ angle; GGViewerOps.SetReal[ggData.measure.angleView, angle]; ggData.measure.radiusViewValue _ distance; GGViewerOps.SetReal[ggData.measure.radiusView, distance]; ggData.measure.lineDistViewValue _ lineDist; GGViewerOps.SetReal[ggData.measure.lineDistView, lineDist]; }; SaveCaretPos: PUBLIC PROC [ggData: GGData] = { caret2Pos: Point _ Vectors2d.Scale[GGCaret.GetPoint[ggData.caret], 1.0/ggData.hitTest.scaleUnit]; ggData.measure.caret2Value _ caret2Pos; GGViewerOps.SetPoint[ggData.measure.caret2, caret2Pos]; ggData.measure.caret0 _ ggData.measure.caret1; ggData.measure.caret1 _ caret2Pos; }; SetCursorLooks: PUBLIC PROC [type: GGInterfaceTypes.GravityType, ggData: GGData] = { <> newCursor: Cursors.CursorType; mouseViewer: ViewerClasses.Viewer; client: BOOL; terminal: Terminal.Virtual _ Terminal.Current[]; mousePos: Terminal.Position _ Terminal.GetMousePosition[terminal]; -- mouse origin in upper left tipScreenCoords: TIPUser.TIPScreenCoords _ NEW[TIPUser.TIPScreenCoordsRec _ [mouseX: mousePos.x, mouseY: mousePos.y, color: ggData.actionArea.column=color] ]; tipScreenCoords.mouseY _ IF ggData.actionArea.column=color THEN terminal.colorHeight-mousePos.y ELSE terminal.bwHeight-mousePos.y; -- move mouse origin to lower left [mouseViewer, client] _ ViewerOps.MouseInViewer[tipScreenCoords]; ggData.actionArea.class.cursor _ newCursor _ SELECT type FROM innerCircle => pointsPreferredCursor, strictDistance => strictDistanceCursor, off => offCursor, ENDCASE => ERROR; <> IF mouseViewer=ggData.actionArea AND client THEN Cursors.SetCursor[newCursor]; }; BuildCursors: PROC [] = { pointsPreferredArray: Cursors.CursorArray = [600B+1100B, 600B+1100B+2040B, 2040B+4020B, 4020B+10010B, 10010B+20004B, 20004B+40002B, 40002B+100001B, 40002B+100001B, 40002B+100001B, 40002B+100001B, 20004B+40002B, 10010B+20004B, 4020B+10010B, 2040B+4020B, 600B+1100B+2040B, 600B+1100B]; -- diamond strictDistanceArray: Cursors.CursorArray = [100001B+40002B, 40002B+20004B, 20004B+10010B, 10010B+4020B, 4020B+2040B, 2040B, 0, 0, 0, 0, 2040B, 4020B+2040B, 10010B+4020B, 20004B+10010B, 40002B+20004B, 100001B+40002B]; -- folded diamond offArray: Cursors.CursorArray = [177777B, 177777B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 177777B, 177777B]; -- square pointsPreferredCursor _ Cursors.NewCursor[bits: pointsPreferredArray, hotX: -8, hotY: -6]; strictDistanceCursor _ Cursors.NewCursor[bits: strictDistanceArray, hotX: -8, hotY: -5]; offCursor _ Cursors.NewCursor[bits: offArray, hotX: -8, hotY: -6]; }; <> <<>> SetHeuristics: PUBLIC PROC [ggData: GGData, on: BOOL] = { stateInfo: AtomButtons.TwoState _ ggData.hitTest.heuristicsButton; state: AtomButtons.StateType _ AtomButtons.GetButtonState[stateInfo]; IF state = on AND on OR state = off AND NOT on THEN RETURN; AtomButtons.SwitchState[stateInfo]; RestoreScreenAndInvariants[paintAction: $PaintEntireViewer, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: FALSE, okToClearFeedback: TRUE]; }; GetHeuristics: PUBLIC PROC [ggData: GGData] RETURNS [on: BOOL] = { stateInfo: AtomButtons.TwoState _ ggData.hitTest.heuristicsButton; state: AtomButtons.StateType _ AtomButtons.GetButtonState[stateInfo]; on _ state = on; }; <<>> SetGravityExtent: PUBLIC PROC [ggData: GGData, inches: REAL] = { screenDots: REAL; graphicsState: AtomButtonsTypes.GraphicsState _ ggData.hitTest.gravityExtentButton; ged: GGInterfaceTypes.GravityExtentData; screenDots _ inches*72.0; Feedback.PutF[ggData.feedback, oneLiner, "Gravity extent is now %g inches.", [real[inches]] ]; ged _ NEW[GGInterfaceTypes.GravityExtentDataObj _ [extent: screenDots]]; GraphicsButton.SetButtonValueAndPaint[graphicsState, ggData, ged]; ggData.hitTest.tolerance _ screenDots; ggData.hitTest.criticalR _ screenDots; ggData.hitTest.innerR _ screenDots/2.0; }; GetGravityExtent: PUBLIC PROC [ggData: GGData] RETURNS [inches: REAL] = { screenDots: REAL; graphicsState: AtomButtonsTypes.GraphicsState _ ggData.hitTest.gravityExtentButton; ged: GGInterfaceTypes.GravityExtentData; ged _ NARROW[GraphicsButton.GetValue[graphicsState].buttonData]; screenDots _ ged.extent; inches _ screenDots/72.0; }; Init: PROC = { actionAreaClass _ BiScrollers.GetStyle[].NewBiScrollerClass[[ flavor: $ActionArea, extrema: GGExtremaProc, notify: GGUserInput.InputNotify, paint: GGActionAreaPaint, tipTable: TIPUser.InstantiateNewTIPTable["Gargoyle.TIP"], mayStretch: FALSE, -- NOT OK to scale X and Y differently offsetsMustBeIntegers: TRUE, preferIntegerCoefficients: FALSE, <> preserve: [X: 0.5, Y: 0.5] --this specifies point that stays fixed when viewer size changes ]]; noNameIcon _ Icons.NewIconFromFile["Gargoyle.icons", 1]; -- done here so file will come from working directory into which Gargoyle was brought over, e.g. ///Commands/ dirtyNoNameIcon _ Icons.NewIconFromFile["Gargoyle.icons", 0]; -- done here so file will come from working directory into which Gargoyle was brought over, e.g. ///Commands/ dirtyIcon _ Icons.NewIconFromFile["Gargoyle.icons", 2]; -- done here so file will come from working directory into which Gargoyle was brought over, e.g. ///Commands/ originalWDir _ FileNames.CurrentWorkingDirectory[]; BuildCursors[]; Commander.Register[ key: "Gargoyle", proc: NewGGViewers, doc: "Create a Gargoyle Graphics Window", clientData: NIL ]; }; actionAreaClass: BiScrollers.BiScrollerClass _ NIL; -- filled in by Init noNameIcon: Icons.IconFlavor _ unInit; -- filled in by Init dirtyNoNameIcon: Icons.IconFlavor _ unInit; -- filled in by Init dirtyIcon: Icons.IconFlavor _ unInit; -- filled in by Init originalWDir: Rope.ROPE _ NIL; -- filled in by Init offCursor: Cursors.CursorType; -- filled in by BuildCursors pointsPreferredCursor: Cursors.CursorType; -- filled in by BuildCursors strictDistanceCursor: Cursors.CursorType; -- filled in by BuildCursors Init[]; END.