DIRECTORY

BiScrollers, Buttons, Commander, CommandTool, Cursors, FileNames, GGAlign, GGBasicTypes, GGBoundBox, AtomButtons, GGCaret, GGContainer, GGError, GGEvent, GGGraphicsButton, GGGravity, GGInterfaceTypes, GGMeasure, GGMenus, GGModelTypes, GGMultiGravity, GGObjects, GGRefresh, GGSegmentTypes, GGSessionLog, GGUserInput, GGVector, GGViewerOps, GGWindow, Geom2D, Icons, Imager, ImagerBackdoor, ImagerFastShow, ImagerPixelMap, ImagerTransformation, IO, Rope, SlackProcess, Terminal, TiogaButtons, TIPUser, ViewerClasses, ViewerOps;

GGWindowImpl: CEDAR PROGRAM
IMPORTS BiScrollers, Buttons, Commander, CommandTool, Cursors, FileNames, GGAlign, GGCaret, GGEvent, GGGravity, GGBoundBox, AtomButtons, GGContainer, GGError, GGGraphicsButton, GGMeasure, GGMenus, GGMultiGravity, GGObjects, GGRefresh, GGSessionLog, GGUserInput, GGVector, GGViewerOps, Geom2D, Icons, Imager, ImagerBackdoor, ImagerTransformation, ImagerFastShow, SlackProcess, Terminal, TIPUser, Rope, 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;

GargoyleData: 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] = TRUSTED {
PaintEntireViewer: PROC = CHECKED {
PlaceOrigin[self];
RestoreScreenAndInvariants[paintAction: $PaintEntireScene, gargoyleData: gargoyleData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE];
};
gargoyleData: GargoyleData;
IF whatChanged = NIL THEN { --we are being called by Window Manager
gargoyleData _ NARROW[BiScrollers.ClientDataOfViewer[self]];
PlaceOrigin[self];
RestoreScreenAndInvariants[paintAction: $ViewersPaintEntireScene, gargoyleData: gargoyleData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE];
}
ELSE {
gargoyleData _ NARROW[whatChanged];
IF gargoyleData.refresh.paintAction=$PaintEntireViewer THEN PaintEntireViewer[] ELSE
GGRefresh.ActionAreaPaint[context, gargoyleData.refresh.paintAction, gargoyleData];
};
};

RestoreScreenAndInvariants: PUBLIC PROC [paintAction: ATOM, gargoyleData: GargoyleData, remake: ForegroundParts _ triggerBag, backgndOK: BOOL _ FALSE, edited: BOOL _ TRUE, okToClearFeedback: BOOL] = {
IF okToClearFeedback THEN GGError.ClearHerald[gargoyleData.feedback];
IF gargoyleData.aborted[bags] THEN {
GGAlign.SetBagsForAction[gargoyleData, $CaretPos];
gargoyleData.refresh.foregndBitmapOK _ FALSE;
gargoyleData.refresh.backgndBitmapOK _ FALSE;
gargoyleData.aborted[bags] _ FALSE;
}
ELSE {
SELECT remake FROM
none => NULL;
triggerBag => {
GGAlign.SetBagsForAction[gargoyleData, $CaretPos];
gargoyleData.refresh.foregndBitmapOK _ FALSE;
};
objectBag => {
GGGravity.FlushObjectBag[gargoyleData.hitTest.currentObjectBag];
GGAlign.BuiltInFilters[gargoyleData.hitTest.currentTriggerBag, gargoyleData.hitTest.currentObjectBag, gargoyleData];
GGAlign.AddAllMidpoints[gargoyleData.hitTest.sceneTriggerBag, gargoyleData.hitTest.currentObjectBag, gargoyleData];
gargoyleData.refresh.foregndBitmapOK _ FALSE;
};
bitMap => {
gargoyleData.refresh.foregndBitmapOK _ FALSE;
};
sceneBag => {
GGAlign.SetSceneBagForAction[gargoyleData, $CaretPos];
GGAlign.AddAllMidpoints[gargoyleData.hitTest.sceneTriggerBag, gargoyleData.hitTest.currentObjectBag, gargoyleData];
};
ENDCASE => ERROR;
gargoyleData.refresh.backgndBitmapOK _ backgndOK;
};
IF edited THEN {
IF NOT gargoyleData.outer.newVersion THEN {
gargoyleData.outer.icon _ dirtyIcon;
gargoyleData.outer.newVersion _ TRUE;

ViewerOps.PaintViewer[gargoyleData.outer, caption];
};
};
gargoyleData.refresh.paintAction _ paintAction;
ViewerOps.PaintViewer[
viewer: gargoyleData.actionArea,
hint: client,
whatChanged: gargoyleData,
clearClient: FALSE]
};

MakeGGViewer: PROC [fileName: Rope.ROPE _ NIL] = {
gargoyleData: GargoyleData _ CreateWindow[GGObjects.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: gargoyleData]; -- tell GGEvent.Get to try for this file
ViewerOps.PaintViewer[gargoyleData.outer, caption]; -- just to get the icon to appear
};

CreateWindow: PUBLIC PROC [scene: Scene, iconic: BOOL, paint: BOOL, workingDirectory: Rope.ROPE] RETURNS [gargoyleData: GargoyleData] = {

tV: Viewer;
gargoyleData _ NEW[GargoyleDataObj];
gargoyleData.currentWDir _ workingDirectory;
gargoyleData.originalWDir _ originalWDir; --global
gargoyleData.hitTest _ NEW[FiltersObj];
gargoyleData.hitTest.currentTriggerBag _ GGAlign.CreateTriggerBag[];
gargoyleData.hitTest.sceneTriggerBag _ GGAlign.CreateTriggerBag[];
gargoyleData.hitTest.currentObjectBag _ GGGravity.CreateObjectBag[];
gargoyleData.scene _ scene;
gargoyleData.caret _ NEW[CaretObj];
gargoyleData.anchor _ NEW[CaretObj];
gargoyleData.camera _ NEW[CameraDataObj];
gargoyleData.state _ $None;
gargoyleData.mouseMode _ $None;
gargoyleData.drag.savedCaret _ NEW[CaretObj];
gargoyleData.drag.selectState _ none;
gargoyleData.drag.extendMode _ none;
gargoyleData.refresh.startBoundBox _ GGBoundBox.CopyBoundBox[GGBoundBox.emptyBoundBox];
gargoyleData.outer _ GGContainer.Create[
info: [
name: "Gargoyle",
menu: NIL,
data: gargoyleData,
iconic: TRUE,
column: left,
scrollable: FALSE,
icon: cleanIcon
],
paint: FALSE];
BuildActionArea[gargoyleData];

gargoyleData.height _ 0;
tV _ BiScrollers.CreateScale[ [parent: gargoyleData.outer, wx: 0, wy: buttonAlign, border: FALSE], gargoyleData.biScroller];
tV _ BiScrollers.CreateRotate[ [parent: gargoyleData.outer, wx: tV.wx+tV.ww+entryHSpace, wy: buttonAlign, border: FALSE], gargoyleData.biScroller];
tV _ BiScrollers.CreateFit[ [parent: gargoyleData.outer, wx: tV.wx+tV.ww+entryHSpace, wy: buttonAlign, border: FALSE], gargoyleData.biScroller];
tV _ BiScrollers.CreateReset[ [parent: gargoyleData.outer, wx: tV.wx+tV.ww+entryHSpace, wy: buttonAlign, border: FALSE], gargoyleData.biScroller];
tV _ BiScrollers.CreateEdge[ [parent: gargoyleData.outer, wx: tV.wx+tV.ww+entryHSpace, wy: buttonAlign, border: FALSE], gargoyleData.biScroller];
tV _ BiScrollers.CreatePrev[ [parent: gargoyleData.outer, wx: tV.wx+tV.ww+entryHSpace, wy: buttonAlign, border: FALSE], gargoyleData.biScroller];
tV _ Buttons.Create[info: [parent: gargoyleData.outer, name: "CenterSel", wx: tV.wx+tV.ww+entryHSpace, wy: 0, border: FALSE], proc: CenterSel, clientData: gargoyleData, paint: FALSE];
tV _ Buttons.Create[info: [parent: gargoyleData.outer, name: "FitSel", wx: tV.wx+tV.ww+entryHSpace, wy: 0, border: FALSE], proc: FitSel, clientData: gargoyleData, paint: FALSE];
gargoyleData.height _ entryHeight;

gargoyleData.slackHandle _ SlackProcess.Create[queueSize: 50, logSize: 50, optimizeProc: OptimizeQueue, loggingProc: GGSessionLog.EnterAction, abortProc: GGAbortProc, abortData: gargoyleData, abortViewer: gargoyleData.actionArea];

GGMenus.BuildControlPanel[gargoyleData, NIL];
tV _ BiScrollers.QuaViewer[gargoyleData.biScroller];
ViewerOps.MoveViewer[tV, 0, gargoyleData.height, gargoyleData.outer.cw, gargoyleData.outer.ch-gargoyleData.height, FALSE];
gargoyleData.outer.newVersion _ FALSE;
gargoyleData.gravityPool _ GGGravity.NewGravityPool[];
gargoyleData.multiGravityPool _ GGMultiGravity.NewMultiGravityPool[];
SetCursorLooks[gargoyleData.hitTest.gravityType, gargoyleData]; -- assumes gravity is turned ON
[] _ SlackProcess.EnableAborts[handle: gargoyleData.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
gargoyleData: GargoyleData _ NARROW[data];
gargoyleData.refresh.suppressRefresh _ FALSE; -- in case you killed FastPlayback
gargoyleData.aborted _ ALL[TRUE]; -- copies of aborted for all purposes
};

CenterSel: Buttons.ButtonProc = {
gargoyleData: GargoyleData _ NARROW[clientData];
caretPos: Point _ GGCaret.GetPoint[gargoyleData.caret];
bBox: GGBoundBox.BoundBox _ GGBoundBox.BoundBoxOfSelected[scene: gargoyleData.scene, selectClass: normal];
IF bBox.null THEN BiScrollers.Align[bs: gargoyleData.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: gargoyleData.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 = {
gargoyleData: GargoyleData _ NARROW[clientData];
tbBox, vBox, tvBox: Geom2D.Rect;
cToV: ImagerTransformation.Transformation;
bBox: GGBoundBox.BoundBox _ GGBoundBox.BoundBoxOfSelected[scene: gargoyleData.scene, selectClass: normal];
IF NOT bBox.null THEN {
cToV _ BiScrollers.GetStyle[].GetTransforms[gargoyleData.biScroller].clientToViewer;
tbBox _ ImagerTransformation.TransformRectangle[cToV, GGBoundBox.RectangleFromBoundBox[bBox] ];
vBox _ BiScrollers.ViewportBox[gargoyleData.biScroller];
tvBox _ ImagerTransformation.TransformRectangle[cToV, vBox];
BiScrollers.BoxScale[bs: gargoyleData.biScroller, from: tbBox, to: tvBox];
};
};

PlaceOrigin: PROC [viewer: Viewer] = TRUSTED {
clientToViewer, viewerToClient: Imager.Transformation;
rect: Imager.Rectangle;
pm: ImagerPixelMap.PixelMap;
pmRef: REF ImagerPixelMap.PixelMapRep;
bitmap: ImagerBackdoor.Bitmap;
gargoyleData: GargoyleData _ NARROW[BiScrollers.ClientDataOfViewer[viewer]];
[clientToViewer, viewerToClient]  _ BiScrollers.GetStyle[].GetTransforms[BiScrollers.QuaBiScroller[viewer]];

bitmap _ gargoyleData.refresh.foregroundBitmap _ ImagerBackdoor.NewBitmap[viewer.cw, viewer.ch];
pmRef _ NEW[ImagerPixelMap.PixelMapRep _ [
ref: bitmap.ref, pointer: bitmap.base, words: LONG[bitmap.wordsPerLine]*bitmap.height,
lgBitsPerPixel: 0, rast: bitmap.wordsPerLine, lines: bitmap.height
]];
pm _ [
sOrigin: 0, fOrigin: 0, sMin: 0, fMin: 0,
sSize: bitmap.height, fSize: bitmap.width, refRep: pmRef
];
gargoyleData.refresh.foregroundContext _ ImagerFastShow.Create[pm, 72, TRUE];
Imager.ConcatT[gargoyleData.refresh.foregroundContext, clientToViewer];

gargoyleData.refresh.backgroundPixelMap.refRep _ NIL;
bitmap _ gargoyleData.refresh.backgroundBitmap _ ImagerBackdoor.NewBitmap[viewer.cw, viewer.ch];
pmRef _ NEW[ImagerPixelMap.PixelMapRep _ [
ref: bitmap.ref, pointer: bitmap.base, words: LONG[bitmap.wordsPerLine]*bitmap.height,
lgBitsPerPixel: 0, rast: bitmap.wordsPerLine, lines: bitmap.height
]];
pm _ [
sOrigin: 0, fOrigin: 0, sMin: 0, fMin: 0,
sSize: bitmap.height, fSize: bitmap.width, refRep: pmRef
];
gargoyleData.refresh.backgroundContext _ ImagerFastShow.Create[pm, 72, TRUE];
Imager.ConcatT[gargoyleData.refresh.backgroundContext, clientToViewer];

bitmap _ gargoyleData.refresh.chunkingBitmap _ ImagerBackdoor.NewBitmap[viewer.cw, viewer.ch];
pmRef _ NEW[ImagerPixelMap.PixelMapRep _ [
ref: bitmap.ref, pointer: bitmap.base, words: LONG[bitmap.wordsPerLine]*bitmap.height,
lgBitsPerPixel: 0, rast: bitmap.wordsPerLine, lines: bitmap.height
]];
pm _ [
sOrigin: 0, fOrigin: 0, sMin: 0, fMin: 0,
sSize: bitmap.height, fSize: bitmap.width, refRep: pmRef
];
gargoyleData.refresh.chunkingContext _ ImagerFastShow.Create[pm, 72, TRUE];
Imager.ConcatT[gargoyleData.refresh.chunkingContext, clientToViewer];

rect _ ImagerBackdoor.GetBounds[gargoyleData.refresh.backgroundContext];
Imager.SetColor[gargoyleData.refresh.backgroundContext, Imager.white];
Imager.MaskRectangle[gargoyleData.refresh.backgroundContext, rect];
};

ViewerToWorld: PUBLIC PROC [viewerPoint: Point, gargoyleData: GargoyleData] RETURNS [worldPoint: Point] = {
vec: Imager.VEC;
viewerToClient: Imager.Transformation  _ BiScrollers.GetStyle[].GetTransforms[BiScrollers.QuaBiScroller[gargoyleData.actionArea]].viewerToClient;
vec _ ImagerTransformation.Transform[viewerToClient, [x: viewerPoint.x, y: viewerPoint.y] ];
worldPoint _ [ vec.x, vec.y];
};

WorldToViewer: PUBLIC PROC [worldPoint: Point, gargoyleData: GargoyleData] RETURNS [viewerPoint: Point] = {
vec: Imager.VEC;
clientToViewer: Imager.Transformation  _ BiScrollers.GetStyle[].GetTransforms[BiScrollers.QuaBiScroller[gargoyleData.actionArea]].clientToViewer;
vec _ ImagerTransformation.Transform[clientToViewer, [x: worldPoint.x, y: worldPoint.y] ];
viewerPoint _ [ vec.x, vec.y];
};

NewGGViewers: Commander.CommandProc = {
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 [gargoyleData: GargoyleData] = {
bs: BiScrollers.BiScroller _ BiScrollers.GetStyle[].CreateBiScroller[
class: actionAreaClass,
info: [
parent: gargoyleData.outer,
wx: 0, wy: gargoyleData.height,
ww: gargoyleData.outer.ww,
wh: gargoyleData.outer.wh, -- only initial values for ww and wh.  They are constrained below
data: gargoyleData,
border: FALSE,
scrollable: FALSE],
paint: FALSE
];
gargoyleData.actionArea _ bs.QuaViewer[inner: TRUE];
gargoyleData.biScroller _ bs;
GGContainer.ChildXBound[gargoyleData.outer, bs.QuaViewer[inner: FALSE]];
GGContainer.ChildYBound[gargoyleData.outer, bs.QuaViewer[inner: FALSE]];
gargoyleData.height _ gargoyleData.height + gargoyleData.actionArea.wh;
};

GGExtremaProc: PROC [clientData: REF ANY, direction: Geom2D.Vec] RETURNS [min, max: Geom2D.Vec] --BiScrollers.ExtremaProc-- = {
area: Geom2D.Rect;
gargoyleData: GargoyleData _ NARROW[clientData];
bigBox: GGBoundBox.BoundBox _ GGBoundBox.BoundBoxOfBoxes[GGObjects.BoundBoxesInScene[gargoyleData.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 [gargoyleData: GargoyleData] = {
caret0, caret1, caret2Pos: Point;
distance, angle, slope, lineDist: REAL;
caret0 _ gargoyleData.measure.caret0;
caret1 _ gargoyleData.measure.caret1;
caret2Pos _ GGVector.Scale[GGCaret.GetPoint[gargoyleData.caret], 1.0/gargoyleData.hitTest.scaleUnit];
distance _ GGMeasure.DistanceBetweenPoints[caret1, caret2Pos]; -- distance in scaleUnits
angle _ GGMeasure.SmallestAngleOfPoints[caret0, caret1, caret2Pos];
slope _ GGMeasure.SlopeOfPoints[caret1, caret2Pos];
IF slope<0.0 THEN slope _ slope+180.0;
IF slope=360.0 THEN slope _ 0.0;
lineDist _ GGMeasure.DistanceFromPointToLine[caret2Pos, caret0, caret1]; -- line distance in scaleUnits
gargoyleData.measure.caret2Value _ caret2Pos;
GGViewerOps.SetPoint[gargoyleData.measure.caret2, caret2Pos];
gargoyleData.measure.slopeViewValue _ slope;
GGViewerOps.SetReal[gargoyleData.measure.slopeView, slope];
gargoyleData.measure.angleViewValue _ angle;
GGViewerOps.SetReal[gargoyleData.measure.angleView, angle];
gargoyleData.measure.radiusViewValue _ distance;
GGViewerOps.SetReal[gargoyleData.measure.radiusView, distance];
gargoyleData.measure.lineDistViewValue _ lineDist;
GGViewerOps.SetReal[gargoyleData.measure.lineDistView, lineDist];
};

SaveCaretPos: PUBLIC PROC [gargoyleData: GargoyleData] = {
caret2Pos: Point _ GGVector.Scale[GGCaret.GetPoint[gargoyleData.caret], 1.0/gargoyleData.hitTest.scaleUnit];
gargoyleData.measure.caret2Value _ caret2Pos;
GGViewerOps.SetPoint[gargoyleData.measure.caret2, caret2Pos];
gargoyleData.measure.caret0 _ gargoyleData.measure.caret1;
gargoyleData.measure.caret1 _ caret2Pos;
};


SetCursorLooks: PUBLIC PROC [type: GGInterfaceTypes.GravityType, gargoyleData: GargoyleData] = {
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: gargoyleData.actionArea.column=color] ];
tipScreenCoords.mouseY _ IF gargoyleData.actionArea.column=color THEN terminal.colorHeight-mousePos.y ELSE terminal.bwHeight-mousePos.y; -- move mouse origin to lower left
[mouseViewer, client] _ ViewerOps.MouseInViewer[tipScreenCoords];
gargoyleData.actionArea.class.cursor _ newCursor _ SELECT type FROM
innerCircle => pointsPreferredCursor,
strictDistance => strictDistanceCursor,
off => offCursor,
ENDCASE => ERROR;
IF mouseViewer=gargoyleData.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 [gargoyleData: GargoyleData, on: BOOL] = {
stateInfo: AtomButtons.TwoState _ gargoyleData.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, gargoyleData: gargoyleData, remake: triggerBag, backgndOK: FALSE, edited: FALSE, okToClearFeedback: TRUE];
};

GetHeuristics: PUBLIC PROC [gargoyleData: GargoyleData] RETURNS [on: BOOL] = {
stateInfo: AtomButtons.TwoState _ gargoyleData.hitTest.heuristicsButton;
state: AtomButtons.StateType _ AtomButtons.GetButtonState[stateInfo];
on _ state = on;
};
SetGravityExtent: PUBLIC PROC [gargoyleData: GargoyleData, inches: REAL] = {
screenDots: REAL;
graphicsState: GGGraphicsButton.GraphicsState _ gargoyleData.hitTest.gravityExtentButton;
screenDots _ inches*72.0;
GGError.PutF[gargoyleData.feedback, oneLiner, "Gravity extent is now %g inches.", [real[inches]] ];
GGGraphicsButton.SetButtonValueAndPaint[graphicsState, screenDots];
gargoyleData.hitTest.tolerance _ screenDots;
gargoyleData.hitTest.criticalR _ screenDots;
gargoyleData.hitTest.innerR _ screenDots/2.0;
};

GetGravityExtent: PUBLIC PROC [gargoyleData: GargoyleData] RETURNS [inches: REAL] = {
screenDots: REAL;
graphicsState: GGGraphicsButton.GraphicsState _ gargoyleData.hitTest.gravityExtentButton;
screenDots _ GGGraphicsButton.GetValue[graphicsState];
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
]];
cleanIcon _ Icons.NewIconFromFile["Gargoyle.icons", 1]; -- done here so file will come from working directory into which Gargoyle was brought over, e.g. ///Commands/
dirtyIcon _ Icons.NewIconFromFile["Gargoyle.icons", 0]; -- 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
cleanIcon: 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. 
����File: GGWindowImpl.mesa
Author: Eric Bier on June 5, 1985 11:34:50 pm PDT
Copyright c 1985, 1986 by Xerox Corporation.  All rights reserved.
Last edited by Bier on January 28, 1987 3:56:19 pm PST
Contents: Code to create a Gargoyle window.  Calls GGContainer to make the container and adds menus at the top.  The graphics part of the Gargoyle window is an inner viewer of a BiScroller of class $ActionArea.  The paint proc for a $ActionArea is defined herein.
Pier, January 16, 1987 6:52:46 pm PST
ViewerClasses.PaintProc
whatChanged is a GargoyleData.  self is an ActionArea.
Fix the foreground Chain.
Fix the background invariant.
Show the viewer as edited, if appropriate.
Refresh the screen.
Construct the outer container.
slack process must be created before call to GGMenus.BuildControlPanel
PROC [qeGen: QueueEntryGenerator, actionsOnQueue: NAT] RETURNS [skipActions: NAT];
Notice that skipActions will be at most summary.count -1;  The most recent action on the queue will be done if nothing else is appropriate.  Always do the last During.
Change scale and offsets so that client data previously in `from' covers as much as possible of `to' (from and to in viewer coords).
viewer is the ActionArea. The created bitmap contexts have their transformations concatted with the analogous  BiScrollers transformation so that operations on the new bitmaps will match the viewer when they are finally displayed.
Construct the foreground bitmap.
Construct the background bitMap or pixelMap.
IF gargoyleData.refresh.showColors.state = off THEN { -- use 1 bit per pixel bitmap
};
ELSE { -- use 8 bit per pixel pixelMap for color display
gargoyleData.refresh.backgroundBitmap _ NIL;
pm _ gargoyleData.refresh.backgroundPixelMap _ ImagerPixelMap.Create[lgBitsPerPixel: 3, bounds: [0, 0, viewer.ch, viewer.cw] ];
gargoyleData.refresh.backgroundContext _ ImagerDitheredDevice.ContextFromPixelMap[frame: pm, displayHeight: viewer.ch, pixelUnits: TRUE];
};
Construct the chunking bitMap or pixelMap.
IF gargoyleData.refresh.showColors.state = off THEN { -- use 1 bit per pixel bitmap
};
ELSE { -- use 8 bit per pixel pixelMap for color display
gargoyleData.refresh.chunkingBitmap _ NIL;
pm _ gargoyleData.refresh.chunkingPixelMap _ ImagerPixelMap.Create[lgBitsPerPixel: 3, bounds: [0, 0, viewer.ch, viewer.cw] ];
gargoyleData.refresh.chunkingContext _ ImagerDitheredDevice.ContextFromPixelMap[frame: pm, displayHeight: viewer.ch, pixelUnits: TRUE];
};
Initialize the context to white.
[cmd: Handle] RETURNS [result: REF _ NIL, msg: Rope.ROPE _ NIL];
Create an ActionArea which fills the bottom of the Gargoyle Container
This proc is required by BiScrollers to return the extremes of the displayed data
The following is a hack and is NOT the right way to do business. Unfortunately, the cursor data lives in a viewer CLASS rec instead of a viewer INSTANCE rec so their is no easy way to keep an instance-specific cursor.
change the cursor immediately if the mouse is in the action area
Gargoyle Viewer State


vanilla: GGBasicTransformProc, --proc which provides the vanilla transform for BiScrollers
ÊŸ��˜�codešœ™Kšœ1™1KšœB™BKšœ6™6šœ‡™‡Kšœ%™%——K˜�šÏk	˜	K˜�KšœºœP˜ŒK˜�—šÏnœœ˜Kšœ“˜šKšœ˜—˜�Kšœœœ
˜Kšœ
œ˜+Kšœœ˜1Kšœ	œœ˜Kšœœ˜/Kšœœ˜!Kšœœ˜/Kšœ	œ˜%Kšœœ˜!Kšœœœ
˜Kšœ
œ˜'Kšœ	œ˜'Kšœ
œ˜'Kšœœ˜Kšœœ˜$Kšœœ˜1K˜�Kšœœœ˜)Kšœœ$˜9K˜�Kšœ
œÏc8˜RKšœ
œŸ˜8Kšœ
œŸ!˜<Kšœ
œŸ+˜FKšœ	œŸ0˜IKšœ	œŸ0˜IKšœ	œŸ0˜IKšœ
œ˜Kšœœ˜Kšœœ˜Kšœœ˜Kšœœ˜—K˜�šžœœ6œœ	œœœœœ˜‹Kšœ™Kšœ6™6šžœœœ˜#Kšœ˜Kšœpœ
œœ˜ŸK˜—Kšœ˜šœœœŸ'˜CKšœœ'˜<Kšœ˜Kšœwœ
œœ˜¦Kšœ˜—šœ˜Kšœœ˜#Kšœ5œÏbœ˜TKšœS˜SKšœ˜—Kšœ˜K˜�—šžœœœœOœœ
œœœ˜ÈKšœœ,˜EKšœ™šœœ˜$Kšœ2˜2Kšœ'œ˜-Kšœ'œ˜-Kšœœ˜#K˜—šœ˜šœ˜Kšœœ˜
šœ˜Kšœ2˜2Kšœ'œ˜-K˜—˜Kšœ@˜@Kšœt˜tKšœs˜sKšœ'œ˜-K˜—˜Kšœ'œ˜-K˜—šœ
˜
Kšœ6˜6Kšœs˜sK˜—Kšœœ˜—Kšœ™Kšœ1˜1K˜Kšœ*™*—šœœ˜šœœœ˜+Kšœ$˜$Kšœ œ˜%K˜�Kšœ3˜3K˜—K˜Kšœ™—Kšœ/˜/šœ˜Kšœ ˜ Kšœ
˜
Kšœ˜Kšœ
œ˜—Kšœ˜K˜�—šžœœœœ˜2KšœCœœ)Ÿ˜”Kšœ
œœœœ-Ÿ(˜ŸKšœ4Ÿ!˜UK˜K˜�—šžœœœœ	œœœ!˜‰K˜�K˜Kšœœ˜$Kšœ,˜,Kšœ*Ÿ˜2Kšœœ
˜'KšœD˜DKšœB˜BKšœD˜DKšœ˜Kšœœ˜#Kšœœ˜$Kšœœ˜)Kšœ˜Kšœ˜Kšœœ˜-Kšœ%˜%Kšœ$˜$KšœW˜WKšœ™šœ(˜(šœ˜Kšœ˜Kšœœ˜
Kšœ˜Kšœœ˜
Kšœ
˜
Kšœœ˜Kšœ˜Kšœ˜—Kšœœ˜—Kšœ˜K˜�K˜Kšœ[œ˜|Kšœrœ˜“Kšœoœ˜Kšœqœ˜’Kšœpœ˜‘Kšœpœ˜‘K•StartOfExpansionê[info: ViewerClasses.ViewerRec _ [class: NIL, wx: 0, wy: 0, ww: 0, wh: 0, cx: 0, cy: 0, cw: 0, ch: 0, lock: [process: PROCESS#0B, count: 0B (0)], tipTable: NIL, name: NIL, file: NIL, label: NIL, menu: NIL, icon: 177777B?, column: left, caption: FALSE, scrollable: TRUE, hscrollable: FALSE, iconic: TRUE, border: TRUE, newVersion: FALSE, newFile: FALSE, visible: TRUE, offDeskTop: FALSE, destroyed: FALSE, init: FALSE, saveInProgress: FALSE, inhibitDestroy: FALSE, guardDestroy: FALSE, paintingWedged: FALSE, spare0: FALSE, spare1: FALSE, spare2: FALSE, spare3: FALSE, spare4: FALSE, spare5: FALSE, spare6: FALSE, position: 0, openHeight: 0, link: NIL, parent: NIL, sibling: NIL, child: NIL, props: NIL, data: NIL], proc: Buttons.ButtonProc, clientData: REF ANY _ NIL, fork: BOOL _ TRUE, font: ImagerFont.Font _ NIL, documentation: REF ANY _ NIL, guarded: BOOL _ FALSE, paint: BOOL _ TRUE]šœvœ5œ˜·K–ê[info: ViewerClasses.ViewerRec _ [class: NIL, wx: 0, wy: 0, ww: 0, wh: 0, cx: 0, cy: 0, cw: 0, ch: 0, lock: [process: PROCESS#0B, count: 0B (0)], tipTable: NIL, name: NIL, file: NIL, label: NIL, menu: NIL, icon: 177777B?, column: left, caption: FALSE, scrollable: TRUE, hscrollable: FALSE, iconic: TRUE, border: TRUE, newVersion: FALSE, newFile: FALSE, visible: TRUE, offDeskTop: FALSE, destroyed: FALSE, init: FALSE, saveInProgress: FALSE, inhibitDestroy: FALSE, guardDestroy: FALSE, paintingWedged: FALSE, spare0: FALSE, spare1: FALSE, spare2: FALSE, spare3: FALSE, spare4: FALSE, spare5: FALSE, spare6: FALSE, position: 0, openHeight: 0, link: NIL, parent: NIL, sibling: NIL, child: NIL, props: NIL, data: NIL], proc: Buttons.ButtonProc, clientData: REF ANY _ NIL, fork: BOOL _ TRUE, font: ImagerFont.Font _ NIL, documentation: REF ANY _ NIL, guarded: BOOL _ FALSE, paint: BOOL _ TRUE]šœsœ2œ˜±Kšœ"˜"˜�KšœF™F—Kšœæ˜æK˜�Kšœ(œ˜-Kšœ4˜4Kšœsœ˜zKšœ œ˜&Kšœ6˜6KšœE˜EKšœ@Ÿ˜_K–$[handle: SlackProcess.SlackHandle]šœA˜AKšœ˜K˜�—šž
œ˜,Kšœ.œœœ™RKšœ§™§Kšœœ˜Kš	œœœœœ˜Kšœœœ˜&K˜šœœœ˜&KšœB˜BKšœœ˜KšœD˜DKšœœ˜ šœœœ˜/Kšœ˜K˜—Kšœœ˜Kšœ˜—K˜K˜�—š	žœœœœŸ5˜[Kšœœ˜*Kšœ'œŸ"˜PKšœœœŸ%˜GK˜K˜�—šž	œ˜!Kšœœ
˜0K–f[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGSegmentTypes.SelectionClass _ normal]šœ7˜7Kšœj˜jK–œ[bs: BiScrollers.BiScroller, client: BiScrollers.Location, viewer: BiScrollers.Location, doX: BOOL _ TRUE, doY: BOOL _ TRUE, paint: BOOL _ TRUE]šœ
œžœ˜´Kšœ¸œ˜ÂK˜K˜�—šžœ˜Kšœœ
˜0K–f[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGSegmentTypes.SelectionClass _ normal]šœ ˜ Kšœ*˜*Kšœj˜jšœœœ˜KšœT˜TK–O[m: ImagerTransformation.Transformation, r: ImagerTransformation.Rectangle]šœ_˜_Kšœ8˜8Kšœ<˜<šœJ˜JKšœdŸœŸ™„—K˜—K˜K˜�—šžœœœ˜.Kšœæ™æKšœ6˜6Kšœ˜Kšœ˜Kšœœ˜&Kšœ˜Kšœœ)˜LKšœl˜lK˜�K™ Kšœ`˜`šœœ˜*Kšœ.œ$˜VKšœB˜BKšœ˜—šœ˜Kšœ)˜)Kšœ8˜8Kšœ˜—KšœGœ˜MKšœG˜GK˜�K™,šœ-œŸ™SKšœ1œ˜5Kšœ`˜`šœœ˜*Kšœ.œ$˜VKšœB˜BKšœ˜—šœ˜Kšœ)˜)Kšœ8˜8Kšœ˜—KšœGœ˜MK™—šœŸ1™8Kšœ(œ™,Kšœ™Kšœƒœ™‰K™—KšœG˜GK˜�K™*šœ-œŸ™SKšœ^˜^šœœ˜*Kšœ.œ$˜VKšœB˜BKšœ˜—šœ˜Kšœ)˜)Kšœ8˜8Kšœ˜—KšœEœ˜KK™—šœŸ1™8Kšœ&œ™*Kšœ}™}Kšœœ™‡K™—KšœE˜E˜�K™ —KšœH˜HKšœF˜FKšœC˜CKšœ˜K˜�—šž
œœœ2œ˜kKšœœ˜Kšœ‘˜‘Kšœ\˜\Kšœ˜Kšœ˜K˜�—šž
œœœ1œ˜kKšœœ˜Kšœ‘˜‘KšœZ˜ZKšœ˜Kšœ˜K˜�—šÐbnœ˜'Kšœœ
œœœœ™@Kš	œœœœœ˜(Kšœœ˜Kšœœ˜KšœPœ1œ˜‘Kšœœœœœœ˜LšœœœœœœœŸ˜ZKšœE˜EKšœ˜—K˜K˜�—šžœœœ!˜>KšœE™EšœE˜EKšœ˜šœ˜Kšœ˜Kšœ˜Kšœ˜KšœŸA˜\Kšœ˜Kšœœ˜Kšœœ˜—Kšœ˜Kšœ˜—Kšœ.œ˜4Kšœ˜Kšœ@œ˜HKšœ@œ˜HKšœG˜GKšœ˜K˜�—š¡
œœœœœŸœ˜K™QKšœ˜Kšœœ
˜0Kšœo˜oKš	œœœœTœ˜‡Kšœ9˜9K˜K˜�—K˜�šžœœœ!˜9Kšœ!˜!Kšœ"œ˜'Kšœ%˜%Kšœ%˜%KšœE œ˜eKšœ?Ÿ˜XKšœC˜CKšœ3˜3Kšœœ˜&Kšœ
œ
˜ KšœIŸ˜gKšœ-˜-Kšœ=˜=Kšœ,˜,Kšœ;˜;Kšœ,˜,Kšœ;˜;Kšœ0˜0Kšœ?˜?Kšœ2˜2KšœA˜AK˜K˜�—šžœœœ!˜:Kšœl˜lKšœ-˜-Kšœ=˜=Kšœ:˜:Kšœ(˜(K˜K˜�—K˜�šžœœœE˜`K™ÙK˜Kšœ"˜"Kšœœ˜
K˜0KšœCŸ˜`Kšœ+œv˜¤Kšœœ&œ!œŸ"˜«KšœA˜Ašœ3œ˜CKšœ%˜%Kšœ'˜'Kšœ˜Kšœœ˜K–[Cursors.CursorType]š @™@—Kšœ%œœ˜TK˜K˜�—šžœœ˜KšœœŸ
˜¦KšœÙŸ˜êKšœ²Ÿ	˜»KšœZ˜ZKšœX˜XKšœB˜BK˜K˜�—Kšœ™K™�šž
œœœ"œ˜EKšœH˜HKšœE˜EKšœœœ
œœœœ˜;Kšœ#˜#Kšœwœ
œœ˜¦K˜K˜�—š
ž
œœœœœ˜NKšœH˜HKšœE˜EKšœ˜K˜K™�—šžœœœ&œ˜LKšœœ˜KšœY˜YKšœ˜Kšœc˜cKšœC˜CKšœ,˜,Kšœ,˜,Kšœ-˜-K˜K˜�—š
žœœœœ
œ˜UKšœœ˜KšœY˜YKšœ6˜6Kšœ˜K˜—K˜�šžœœ˜šœ=˜=Kšœ˜Kšœ˜Kšœ ˜ Kšœ˜Kšœ9˜9KšœœŸ&˜9Kšœœ˜Kšœœ˜!KšœŸ;™ZKšœŸ@˜[Kšœ˜—Kšœ8Ÿm˜¥Kšœ8Ÿm˜¥Kšœ3˜3K˜šœ˜Kšœ˜Kšœ˜Kšœ)˜)Kšœ˜K˜—Kšœ˜—K˜�Kšœ/œŸ˜HKšœ&Ÿ˜:Kšœ&Ÿ˜:KšœœœŸ˜3Kšœ Ÿ˜<Kšœ,Ÿ˜HKšœ+Ÿ˜GK˜�Kšœ˜K˜�Kšœ˜—�…—����Vº��~s��