GGWindowImpl.mesa
Copyright Ó 1985, 1986, 1987, 1988, 1989 by Xerox Corporation. All rights reserved.
Kurlander, September 1, 1987 11:46:26 am PDT
Bier, May 27, 1992 11:21 am PDT
Pier, April 9, 1992 5:09 pm PDT
Doug Wyatt, December 19, 1989 11:53:03 am 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.
DIRECTORY
AtomButtons, AtomButtonsTypes, BiScrollers, CodeTimer, Commander, CommandTool, Cursors, Feedback, FeedbackOps, FileNames, FS, Geom2D, GGAlign, GGBasicTypes, GGBoundBox, GGCaret, GGContainer, GGControlPanelTypes, GGCoreOps, GGDragTypes, GGEmbedTypes, GGEvent, GGHistory, GGHistoryTypes, GGInterfaceTypes, GGMeasure, GGMenu, GGModelTypes, GGMouseEvent, GGMultiGravity, GGOutline, GGRefresh, GGRefreshTypes, GGScene, GGSegmentTypes, GGSessionLog, GGSlice, GGState, GGStateExtras, GGStateTypes, GGUserInput, GGUserProfile, GGViewerOps, GGWindow, Icons, Imager, ImagerFont, ImagerTransformation, IO, MultiCursors, Process, Rope, SlackProcess, TiogaButtons, TIPPrivate, TIPUser, TIPUserExtras, UserProfile, Vectors2d, ViewerClasses, ViewerOps;
GGWindowImpl: CEDAR PROGRAM
IMPORTS AtomButtons, BiScrollers, CodeTimer, Commander, CommandTool, Cursors, Feedback, FeedbackOps, FileNames, FS, Geom2D, GGAlign, GGBoundBox, GGCaret, GGContainer, GGCoreOps, GGEvent, GGHistory, GGMeasure, GGMenu, GGMouseEvent, GGMultiGravity, GGOutline, GGRefresh, GGScene, GGSessionLog, GGSlice, GGState, GGStateExtras, GGUserInput, GGUserProfile, GGViewerOps, Icons, Imager, ImagerFont, ImagerTransformation, IO, MultiCursors, Process, Rope, SlackProcess, TIPPrivate, TIPUser, TIPUserExtras, UserProfile, Vectors2d, ViewerOps
EXPORTS GGWindow, GGHistoryTypes, GGInterfaceTypes = BEGIN
ControlsObj: PUBLIC TYPE = GGControlPanelTypes.ControlsObj; -- exported to GGInterfaceTypes
EmbedDataObj: PUBLIC TYPE = GGEmbedTypes.EmbedDataObj; -- exported to GGInterfaceTypes
StateDataObj: PUBLIC TYPE = GGStateTypes.StateDataObj; -- exported to GGInterfaceTypes
DragDataObj: PUBLIC TYPE = GGDragTypes.DragDataObj; -- exported to GGInterfaceTypes
CameraObj: TYPE = GGModelTypes.CameraObj;
Caret: TYPE = REF CaretObj;
CaretObj: TYPE = GGInterfaceTypes.CaretObj;
DebugDataObj: TYPE = GGInterfaceTypes.DebugDataObj;
Filters: TYPE = REF FiltersObj;
FiltersObj: TYPE = GGInterfaceTypes.FiltersObj;
ForegroundParts: TYPE = GGWindow.ForegroundParts;
GGDataObj: TYPE = GGInterfaceTypes.GGDataObj;
GGData: TYPE = GGInterfaceTypes.GGData;
HistoryTool: TYPE = REF HistoryToolObj;
HistoryToolObj: PUBLIC TYPE = GGHistory.HistoryToolObj; -- exported to GGHistoryTypes
ImagerProc: TYPE = GGInterfaceTypes.ImagerProc;
Point: TYPE = GGBasicTypes.Point;
RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj;
ROPE: TYPE = Rope.ROPE;
Scene: TYPE = REF SceneObj;
SceneObj: TYPE = GGModelTypes.SceneObj;
Segment: TYPE = GGSegmentTypes.Segment;
Sequence: TYPE = GGModelTypes.Sequence;
Slice: TYPE = GGModelTypes.Slice;
Traj: TYPE = GGModelTypes.Traj;
Transformation: TYPE = ImagerTransformation.Transformation;
Viewer: TYPE = ViewerClasses.Viewer;
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;
globalEditedProcList: LIST OF EditedProcItem;
Gargoyle Viewer State
RestoreScreenAndInvariants: PUBLIC PROC [paintAction: ATOM, ggData: GGData, remake: ForegroundParts ← triggerBag, edited: BOOLTRUE, okToSkipCapture: BOOL] = {
IF okToClearFeedback THEN Feedback.ClearHerald[ggData.feedback];
Fix the foreground Chain.
CodeTimer.StartInt[$RestoreScreenAndInvariants, $Gargoyle];
IF ggData.aborted[bags] THEN {
GGAlign.SetStaticBags[ggData];
GGRefresh.InvalidateForeground[ggData];
GGRefresh.InvalidateBackground[ggData];
ggData.aborted[bags] ← FALSE;
}
ELSE {
SELECT remake FROM
none => NULL;
triggerBag => {
GGAlign.SetStaticBags[ggData];
GGRefresh.InvalidateForeground[ggData];
};
triggerBagNotSceneBag => {
GGAlign.SetStaticTriggerAndAlignBags[ggData];
GGRefresh.InvalidateForeground[ggData];
};
alignBag => {
GGAlign.FlushAlignBag[ggData.hitTest.alignBag];
GGAlign.FillStaticAlignBag[ggData.hitTest.triggerBag, ggData.hitTest.sceneBag, ggData, NOT GGState.GetShowAlignments[ggData], ggData.hitTest.alignBag];
GGRefresh.InvalidateForeground[ggData];
};
bitMap => {
GGRefresh.InvalidateForeground[ggData];
};
sceneBag => {
GGAlign.FlushTriggerBag[ggData.hitTest.sceneBag];
GGAlign.FillStaticSceneBag[ggData.scene, ggData.hitTest.sceneBag];
};
ENDCASE => ERROR;
};
Show the viewer as edited, if appropriate.
IF edited THEN {
FOR list: LIST OF EditedProcItem ← globalEditedProcList, list.rest UNTIL list = NIL DO
IF ggData=list.first.ggData THEN list.first.editedProc[ggData, list.first.clientData];
ENDLOOP;
};
ViewerOps.PaintViewer[viewer: ggData.controls.actionArea, hint: client, whatChanged: ggData, clearClient: FALSE];
GGRefresh.PaintInParent[ggData, paintAction];
IF NOT okToSkipCapture THEN GGHistory.DoAdvanceCapture[ggData: ggData];
CodeTimer.StopInt[$RestoreScreenAndInvariants, $Gargoyle];
};
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.controls.caret2, caret2Pos];
GGState.SetSlopeValue[ggData, slope];
GGState.SetAngleValue[ggData, angle];
GGState.SetRadiusValue[ggData, distance];
GGState.SetLineDistanceValue[ggData, 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.controls.caret2, caret2Pos];
ggData.measure.caret0 ← ggData.measure.caret1;
ggData.measure.caret1 ← caret2Pos;
};
The Gargoyle Viewer
OpenViewerOnFile: PROC [fileName: Rope.ROPENIL, fancyPanel: BOOLFALSE] = {
ggData: GGData ← CreateWindowAux[GGScene.CreateScene[], TRUE, FALSE, FileNames.CurrentWorkingDirectory[], fancyPanel]; -- create brand new GG window
IF fileName#NIL AND ~Rope.Equal[fileName, ""] THEN GGEvent.Get[event: LIST[$Get, fileName], ggData: ggData]; -- tell GGEvent.Get to try for this file
GGHistory.CapTool[ggData.controls.topper.name, ggData];
ViewerOps.PaintViewer[ggData.controls.topper, caption]; -- just to get the icon to appear
ViewerOps.PaintViewer[ggData.controls.panel, caption]; -- just to get the icon to appear
};
CreateChildViewer: PUBLIC PROC [
scene: Scene,
wx, wy: INTEGER ← 0,
ww, wh: INTEGER ← 0,
cx, cy: INTEGER ← 0,
cw, ch: INTEGER ← 0,
parent: Viewer,
workingDirectory: Rope.ROPE,
clientData: REF ANYNIL,
paint: BOOLTRUE] RETURNS [viewer: Viewer, ggData: GGData] = {
ggData ← CreateGGDataForViewer[parent, scene, workingDirectory];
ggData.controls.active ← TRUE;
ggData.controls.biScroller ← BiScrollers.GetStyle[].CreateBiScroller[
class: actionAreaClass,
info: [
parent: parent,
name: "GargoyleChild",
wx: wx, wy: wy,
ww: ww, wh: wh,
cx: cx, cy: cy,
cw: cw, ch: ch,
data: ggData,
border: FALSE,
scrollable: FALSE],
paint: paint
];
ggData.controls.picture ← ggData.controls.topper ←
ggData.controls.biScroller.QuaViewer[inner: FALSE];
ggData.controls.actionArea ← ggData.controls.biScroller.QuaViewer[inner: TRUE];
ggData.controls.actionArea.cursor ← last; -- "last" means that Viewers should not do cursor handling for us
ggData.height ← 0;
ggData.parseInfo ← TIPPrivate.CreateParseInfo[ggTipTable];
ggData.controls.topper.newVersion ← FALSE;
ggData.controls.panel.newVersion ← FALSE;
ggData.controls.picture.newVersion ← FALSE;
SetCursorLooks[ggData.hitTest.gravityType, ggData];
[] ← SlackProcess.EnableAborts[handle: ggData.slackHandle];
GGEvent.OpenAutoScript[ggData, TRUE];
RegisterEditedProc[ggData, GGState.GGEdited, NIL];
RestoreScreenAndInvariants[$None, ggData, triggerBag, FALSE, FALSE];
viewer ← ggData.controls.picture;
Set up the feedback. If this window were not a child, the router would be created in GGMenuImplC.FeedbackLineInGGData.
ggData.router ← Feedback.CreateRouter[];
FeedbackOps.SetMultiMessageWindow[ggData.router, TRUE, LIST[$Error, $Complaint]]; -- blink
FeedbackOps.SetMultiMessageWindow[ggData.router, FALSE, LIST[$DuringMouse, $Feedback, $Warning, $Confirm, $Show, $Statistics]];
FeedbackOps.SetMultiTypescript[ggData.router, $Gargoyle, LIST[$Error, $Warning, $Show, $Typescript, $Complaint, $Statistics]];
ggData.embed.beingBorn ← FALSE;
};
PaintInViewer: PUBLIC GGRefresh.PaintProc = {
PROC [ggData: GGData, request: REFNIL, bounds: BoundBoxObj ← infiniteRect, clientData: REFNIL];
GGInMMMImpl.Paint can be used in place of this routine when Gargoyle is embedded in MMM.
ViewerOps.PaintViewer[viewer: ggData.controls.actionArea, hint: client, whatChanged: ggData, clearClient: FALSE];
};
CreateGGData: PUBLIC PROC [scene: Scene, workingDirectory: Rope.ROPE] RETURNS [ggData: GGData] = {
RETURN[CreateGGDataForViewer[NIL, scene, workingDirectory]];
};
CreateGGDataForViewer: PROC [outer: Viewer, scene: Scene, workingDirectory: Rope.ROPE] RETURNS [ggData: GGData] = {
ggData ← NEW[GGDataObj];
ggData.controls ← NEW[ControlsObj];
ggData.controlState ← NEW[StateDataObj];
ggData.controlState.doubleBuffer ← TRUE;
ggData.controlState.clientToViewer ← ImagerTransformation.Scale[1.0];
ggData.controlState.viewerToClient ← ImagerTransformation.Scale[1.0];
ggData.drag ← NEW[DragDataObj];
ggData.embed ← NEW[EmbedDataObj];
ggData.embed.scrollDue ← ImagerTransformation.Scale[1.0];
GGRefresh.RegisterPaintProc[ggData, PaintInViewer, NIL];
GGStateExtras.RegisterViewportProc[ggData, GGStateExtras.DefaultViewport, NIL];
ggData.embed.beingBorn ← TRUE;
Parent Viewers
IF outer#NIL THEN {
outer.newVersion ← FALSE;
ggData.controls.topper ← ggData.controls.panel ← outer;
};
Working Directory
GGStateExtras.SetWorkingDirectory[ggData, workingDirectory];
Scene and Interface Objects
ggData.scene ← scene;
ggData.caret ← GGCaret.Create[];
ggData.drag.savedCaret ← GGCaret.Create[];
ggData.anchor ← GGCaret.Create[];
ggData.camera ← NEW[CameraObj];
Input Handling
ggData.lastEvents ← GGCoreOps.NewEventListt[];
GGMouseEvent.InitializeFSM[ggData];
ggData.history ← [list: LIST[NIL],
maxSize: GGUserProfile.GetDefaultHistorySize[],
currentIndex: 0]; -- KAP July 7, 1988
Slack process must be created before call to GGMenu.BuildControlPanel
ggData.slackHandle ← SlackProcess.Create[queueSize: 50, logSize: 50, optimizeProc: OptimizeQueue, loggingProc: GGSessionLog.EnterAction, abortProc: GGAbortProc, abortData: ggData, abortViewer: ggData.controls.actionArea];
[] ← SlackProcess.EnableAborts[handle: ggData.slackHandle];
Selections
ggData.drag.selectState ← none;
ggData.drag.extendMode ← none;
GGState.SetSelectionCycler[ggData, GGMultiGravity.EmptyCycler[[0.0, 0.0]] ];
Hit Testing
ggData.hitTest ← NEW[FiltersObj];
ggData.hitTest.triggerBag ← GGAlign.CreateTriggerBag[];
ggData.hitTest.oldTriggerBag ← GGAlign.CreateTriggerBag[];
ggData.hitTest.sceneBag ← GGAlign.CreateTriggerBag[];
ggData.hitTest.oldSceneBag ← GGAlign.CreateTriggerBag[];
ggData.hitTest.alignBag ← GGAlign.CreateAlignBag[];
ggData.hitTest.oldAlignBag ← GGAlign.CreateAlignBag[];
ggData.multiGravityPool ← GGMultiGravity.NewMultiGravityPool[];
Refresh
ggData.refresh ← NEW[RefreshDataObj];
GGAlign.CreateLineTable[ggData];
ggData.refresh.startBoundBox ← GGBoundBox.NullBoundBox[];
ggData.refresh.beforeBox ← GGBoundBox.NullBoundBox[];
ggData.refresh.paintBox ← GGBoundBox.NullBoundBox[];
ggData.refresh.totalBox ← GGBoundBox.NullBoundBox[];
ggData.refresh.sandwich ← GGRefresh.CreateSandwich[];
ggData.refresh.oldTransform ← ImagerTransformation.Scale[1.0];
Behavior
ggData.behavior.activeDoc ← NIL; -- next time this field is needed it will be recomputed
ggData.rootSlice ← GGSlice.MakeCircleSlice[[200.0, 200.0], [300.0, 200.0]].slice;
The rootSlice is just any non-parent slice. It is used for storing properties. It is also created in GGEventImplD. Perhaps there should be a GGWindow.CreateRootSlice proc.
Debugging
ggData.debug ← NEW[DebugDataObj];
ggData.debug.autoScriptNames ← GGCoreOps.NewRopeListt[];
Defaults
ggData.defaults ← NEW[GGModelTypes.DefaultDataObj ← [
strokeColor: Imager.black,
fillColor: GGOutline.fillColor,
textColor: Imager.black,
font: GGUserProfile.GetDefaultDefaultFont[],
dropShadowColor: Imager.black
]];
};
CreateWindow: PUBLIC PROC [scene: Scene, iconic: BOOL, paint: BOOL, workingDirectory: Rope.ROPE] RETURNS [ggData: GGData] = {
fancyPanel: BOOL ← UserProfile.Boolean[key: "Gargoyle.FancyPanel", default: TRUE];
RETURN[CreateWindowAux[scene, iconic, paint, workingDirectory, fancyPanel]];
};
CreateWindowAux: PUBLIC PROC [scene: Scene, iconic: BOOL, paint: BOOL, workingDirectory: Rope.ROPE, fancyPanel: BOOLFALSE] RETURNS [ggData: GGData] = {
ggData.controls.panel is the container that holds the control panel.
ggData.controls.topper is the container that holds the picture.
If control panels are not separate, ggData.controls.panel = ggData.controls.topper.
ggData.controls.picture is the BiScroller that contains the picture.
If control panels are separate, ggData.controls.picture = ggData.controls.topper
ggData ← CreateGGData[scene, workingDirectory];
ggData.controls.panel ← GGContainer.GGContainerCreate[
info: [
name: "GGPanel: Gargoyle",
menu: NIL,
data: ggData,
iconic: iconic,
column: left,
scrollable: FALSE,
icon: panelIconG
],
paint: paint];
ggData.controls.biScroller ← BiScrollers.GetStyle[].CreateBiScroller[
class: actionAreaClass,
info: [
parent: IF GGUserProfile.GetSeparateControlPanel[] THEN NIL ELSE ggData.controls.panel,
name: "Gargoyle",
menu: NIL,
wx: 0, wy: 0,
ww: 10,
wh: 10, -- only dummy values for ww and wh for now
data: ggData,
iconic: TRUE,
column: left,
icon: noNameIconG,
border: FALSE,
scrollable: FALSE],
paint: FALSE
];
ggData.controls.picture ← ggData.controls.topper ← ggData.controls.biScroller.QuaViewer[inner: FALSE];
ggData.controls.actionArea ← ggData.controls.biScroller.QuaViewer[inner: TRUE];
ggData.controls.actionArea.cursor ← last; -- "last" means that Viewers should not do cursor handling for us
ggData.height ← 0;
ggData.parseInfo ← TIPPrivate.CreateParseInfo[ggTipTable];
GGMenu.BuildControlPanel[ggData, fancyPanel]; -- updates ggData.height
IF NOT GGUserProfile.GetSeparateControlPanel[] THEN { -- panel and picture together
GGContainer.ChildXBound[gargoyleContainer: ggData.controls.panel, child: ggData.controls.picture];
GGContainer.ChildYBound[gargoyleContainer: ggData.controls.panel, child: ggData.controls.picture];
ViewerOps.MoveViewer[ggData.controls.picture, 0, ggData.height, ggData.controls.panel.cw, ggData.controls.panel.ch-ggData.height, FALSE];
ggData.controls.topper ← ggData.controls.panel;
ggData.controls.topper.icon ← noNameIconG;
ggData.controls.topper.name ← "Gargoyle";
}
ELSE { -- panel by itself
thisViewer: AtomButtons.ButtonLineEntry ← [label[name: "Control", font: bigFont]];
noPicture: AtomButtons.ButtonLineEntry ← [label[name: "Panel", font: bigFont]];
ViewerOps.SetOpenHeight[ggData.controls.panel, ggData.height-entryVSpace];
[] ← AtomButtons.BuildButtonLine[container: ggData.controls.panel, x: 36, y: ggData.height,
clientData: ggData,
handleProc: GGUserInput.EventNotify,
entries: LIST[thisViewer],
horizontalSpace: entryHSpace, lineHeight: 144];
ggData.height ← ggData.height + 144;
[] ← AtomButtons.BuildButtonLine[container: ggData.controls.panel, x: 36, y: ggData.height,
clientData: ggData,
handleProc: GGUserInput.EventNotify,
entries: LIST[noPicture],
horizontalSpace: entryHSpace, lineHeight: 72];
ggData.height ← ggData.height + 72;
};
ggData.controls.topper.newVersion ← FALSE;
ggData.controls.panel.newVersion ← FALSE;
ggData.controls.picture.newVersion ← FALSE;
SetCursorLooks[ggData.hitTest.gravityType, ggData];
[] ← SlackProcess.EnableAborts[handle: ggData.slackHandle];
GGEvent.OpenAutoScript[ggData, TRUE];
RegisterEditedProc[ggData, GGState.GGEdited, NIL];
IF GGUserProfile.GetAutoOpenHistory[] THEN ggData.history.tool ← GGHistory.BuildTool["Gargoyle", ggData];
IF GGUserProfile.GetAutoOpenTypescript[] THEN
[] ← FeedbackOps.CreateNamedTypescript["Gargoyle Typescript", $Gargoyle, 120];
ggData.embed.beingBorn ← FALSE; -- enable input handling
};
ActiveInGGData: AtomButtons.InitTwoStateProc = {
ggData: GGData ← NARROW[clientData];
ggData.controls.activeButton ← twoState;
};
GGActionAreaPaint: PROC [self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOLFALSE] = {
ViewerClasses.PaintProc. whatChanged is a GGData. self is an ActionArea.
ggData: GGData ~ NARROW[BiScrollers.ClientDataOfViewer[self]];
sanityCheck: BOOL[TRUE..TRUE] ~ (ggData.controls.actionArea=self);
clientToViewer: Imager.Transformation ~ GGState.GetBiScrollersTransform[ggData];
IF whatChanged=NIL THEN { -- window scrolled or changed size or changed displays
action: ATOM ← $ViewersPaintEntireScene;
GGRefresh.SetScreen[ggData, self.cw, self.ch, context]; -- resize sandwich, if needed
If we are reopening the window (not scrolling) use the backed sandwich layers
IF ggData.refresh.clientToViewer#NIL AND ImagerTransformation.Equal[clientToViewer, ggData.refresh.clientToViewer] THEN action ← $ViewersPaintAllPlanes;
GGUserInput.EventNotify[ggData, LIST[action]]; -- queue the paint action
}
ELSE {
paintAction: ATOM ← ggData.refresh.paintAction;
GGRefresh.ActionAreaPaint[screen: context, whatHasChanged: paintAction, ggData: ggData, handleViewerAbort: (whatChanged=NIL)];
ggData.refresh.clientToViewer ← clientToViewer; -- for next time around
};
};
GGAbortProc: PROC [data: REF ANY] = { -- called by SlackProcess when user signals for abort
ggData: GGData ← NARROW[data];
Feedback.Append[ggData.router, oneLiner, $Feedback, "Queued operations Aborted"];
ggData.refresh.suppressRefresh ← FALSE; -- in case you killed FastPlayback
ggData.refresh.suppressScreen ← FALSE; -- in case you killed FastPlayback
ggData.aborted ← ALL[TRUE]; -- copies of aborted for all purposes
};
RegisterEditedProc: PUBLIC PROC [ggData: GGData, editedProc: EditedProc, clientData: REF ANY] = {
The caller (represented by ggData) wishes to be notified whenever his Gargoyle viewer is edited.
editedProcItem: EditedProcItem ← NEW[EditedProcItemObj ← [ggData, editedProc, clientData]];
globalEditedProcList ← CONS[editedProcItem, globalEditedProcList];
};
ViewerToWorld: PUBLIC PROC [viewerPoint: Point, ggData: GGData] RETURNS [worldPoint: Point] = {
vec: Imager.VEC;
viewerToClient: Imager.Transformation ← GGState.GetBiScrollersTransforms[ggData].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 ← GGState.GetBiScrollersTransform[ggData];
vec ← ImagerTransformation.Transform[clientToViewer, [x: worldPoint.x, y: worldPoint.y]];
viewerPoint ← [vec.x, vec.y];
};
Gargoyle Viewer Creation Utilities
OptimizeQueue: SlackProcess.OptimizeProc = {
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.
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← NARROW[SlackProcess.GetQueueEntry[qeGen, i].inputAction];
atom ← NARROW[action.first];
action ← NARROW[SlackProcess.GetQueueEntry[qeGen, i+1].inputAction];
nextAtom ← NARROW[action.first];
IF (atom = $During OR atom = $OneScale) AND (nextAtom = $During OR nextAtom = $OneScale) THEN skipActions ← skipActions + 1
ELSE IF (atom = $OneScroll OR atom = $OneZoom) AND (nextAtom = $OneScroll OR nextAtom = $OneZoom) THEN skipActions ← skipActions + 1
ELSE RETURN;
ENDLOOP;
};
PUChoiceList: PROC [r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15, r16, r17, r18, r19: AtomButtons.PopUpChoice ← [] ] RETURNS [list: AtomButtons.PopUpChoices] = {
OPEN AtomButtons;
InnerCons: PROC[r: PopUpChoice] = {rList ← CONS[r, rList]; };
rList: PopUpChoices;
IF r0.action#NIL THEN InnerCons[r0];
IF r1.action#NIL THEN InnerCons[r1];
IF r2.action#NIL THEN InnerCons[r2];
IF r3.action#NIL THEN InnerCons[r3];
IF r4.action#NIL THEN InnerCons[r4];
IF r5.action#NIL THEN InnerCons[r5];
IF r6.action#NIL THEN InnerCons[r6];
IF r7.action#NIL THEN InnerCons[r7];
IF r8.action#NIL THEN InnerCons[r8];
IF r9.action#NIL THEN InnerCons[r9];
IF r10.action#NIL THEN InnerCons[r10];
IF r11.action#NIL THEN InnerCons[r11];
IF r12.action#NIL THEN InnerCons[r12];
IF r13.action#NIL THEN InnerCons[r13];
IF r14.action#NIL THEN InnerCons[r14];
IF r15.action#NIL THEN InnerCons[r15];
IF r16.action#NIL THEN InnerCons[r16];
IF r17.action#NIL THEN InnerCons[r17];
IF r18.action#NIL THEN InnerCons[r18];
IF r19.action#NIL THEN InnerCons[r19];
FOR dummy: PopUpChoices ← rList, dummy.rest UNTIL dummy=NIL DO
list ← CONS[dummy.first, list];
ENDLOOP;
};
Choice: PROC [action: LIST OF REF ANY, actionImage: Rope.ROPE, doc: Rope.ROPE, font: Imager.Font ← NIL] RETURNS [AtomButtons.PopUpChoice]= {
RETURN[[action, actionImage, doc, font]];
};
List: PROC [ref1, ref2, ref3: REF ANYNIL] RETURNS [LIST OF REF ANY]= {
RETURN[
SELECT TRUE FROM
ref2=NIL => LIST[ref1],
ref3=NIL => LIST[ref1, ref2],
ENDCASE => LIST[ref1, ref2, ref3]
];
};
GGExtremaProc: 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
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];
};
GargoyleData Class Procedures
EditedProc: TYPE = GGWindow.EditedProc;
EditedProcItem: TYPE = REF EditedProcItemObj;
EditedProcItemObj: TYPE = RECORD [
ggData: GGData,
editedProc: EditedProc,
clientData: REF ANY
];
Cursors
SetCursorLooks: PUBLIC PROC [type: GGInterfaceTypes.GravityType, ggData: GGData, off: BOOLFALSE ] = {
newCursor: Cursors.CursorType ~ IF off THEN offCursor ELSE SELECT type FROM
pointsPreferred => pointsPreferredCursor,
linesPreferred, facesPreferred => linesPreferredCursor,
ENDCASE => ERROR;
ggData.controls.cursor ← newCursor;
IF newCursor#MultiCursors.GetACursor[NIL]
THEN MultiCursors.SetACursor[newCursor, NIL]; -- this code should make sure the cursor is in the Gargoyle action area before doing this.
ggData.controls.actionArea.cursor ← cursor;
};
The Gargoyle CommandTool Command
NewGGViewers: Commander.CommandProc = {
[cmd: Handle] RETURNS [result: REFNIL, msg: Rope.ROPENIL];
nameList, args: LIST OF Rope.ROPENIL;
argLength: NAT ← 0;
switchChar: CHAR = '-;
fancyPanel: BOOL ← UserProfile.Boolean[key: "Gargoyle.FancyPanel", default: TRUE];
[list: args, length: argLength] ← CommandTool.ParseToList[cmd: cmd, starExpand: TRUE, switchChar: switchChar ! CommandTool.Failed => CONTINUE; ];
IF args = NIL OR argLength < 1 THEN { OpenViewerOnFile[fileName: NIL, fancyPanel: fancyPanel]; RETURN};
IF argLength = 1 AND Rope.Equal[args.first, "-fancyPanel", FALSE] THEN {OpenViewerOnFile[fileName: NIL, fancyPanel: TRUE]; RETURN};
IF argLength = 1 AND Rope.Equal[args.first, "-~fancyPanel", FALSE] THEN {OpenViewerOnFile[fileName: NIL, fancyPanel: FALSE]; RETURN};
FOR rl: LIST OF Rope.ROPE ← args, rl.rest UNTIL rl = NIL DO --open a GGViewer on each file
IF Rope.Equal[rl.first, "-fancyPanel", FALSE] THEN fancyPanel ← TRUE
ELSE IF Rope.Equal[rl.first, "-~fancyPanel", FALSE] THEN fancyPanel ← FALSE
ELSE [] ← OpenViewerOnFile[fileName: FileNames.ResolveRelativePath[rl.first], fancyPanel: fancyPanel];
ENDLOOP;
};
FilenameMinusExtension: PUBLIC PROC [wholeName: ROPE] RETURNS [ROPE] ~ {
fullFName: ROPE; cp: FS.ComponentPositions;
[fullFName: fullFName, cp: cp] ← FS.ExpandName[wholeName];
RETURN[Rope.Substr[fullFName, 0, cp.base.start+cp.base.length]];
};
GGToIP: Commander.CommandProc = {
EachFile: FS.NameProc = {
[fullFName: ROPE] RETURNS [continue: BOOL]
ipName: Rope.ROPE;
Process.CheckForAbort[];
ipName ← FilenameMinusExtension[fullFName];
ipName ← Rope.Concat[ipName, ".ip"];
GGEvent.Get[ggData, LIST[$Get, fullFName]];
GGEvent.ToIP[ggData, LIST[$ToIP, ipName]];
continue ← TRUE;
};
TryPattern: PROC [pattern: Rope.ROPE] = {
ENABLE FS.Error => IF error.group # $bug THEN {
IO.PutRope[out, " -- "];
IO.PutRope[out, error.explanation];
GO TO err};
pattern ← FileNames.ResolveRelativePath[pattern];
pattern ← FS.ExpandName[pattern].fullFName;
IF NOT Rope.Match["*!*", pattern] THEN pattern ← Rope.Concat[pattern, "!h"];
FS.EnumerateForNames[pattern, EachFile];
EXITS
err => {IO.PutRope[out, "\n"]; RETURN};
};
out: IO.STREAM ← cmd.out;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
ggData: GGData ← CreateWindow[GGScene.CreateScene[], TRUE, FALSE, FileNames.CurrentWorkingDirectory[]];
ViewerOps.PaintViewer[ggData.controls.panel, caption]; -- just to get the icon to appear
ViewerOps.PaintViewer[ggData.controls.topper, caption]; -- just to get the icon to appear
GGUserInput.EventNotify[ggData, LIST[$Typescript]];
FOR i: NAT IN [1..argv.argc) DO
arg: Rope.ROPE = argv[i];
IF Rope.Length[arg] = 0 THEN LOOP;
TryPattern[arg];
ENDLOOP;
EXITS
failed => {result ← $Failure};
};
GGIPToIP: Commander.CommandProc = {
The first argument is the name of the script to apply to each file. Subsequent arguments are file name patterns describing a set of IP files to be merged editable. The named files will be overwritten by versions of themselves to which the script has been applied.
EachFile: FS.NameProc = {
[fullFName: ROPE] RETURNS [continue: BOOL]
newIPName: ROPE;
Process.CheckForAbort[];
GGUserInput.EventNotify[ggData, LIST[$Clear]];
GGUserInput.EventNotify[ggData, LIST[$MergeIPEditable, fullFName]];
GGSessionLog.PlaybackFromFile[scriptName, ggData];
newIPName ← FilenameMinusExtension[fullFName];
newIPName ← Rope.Concat[newIPName, "-mod.ip"];
GGUserInput.EventNotify[ggData, LIST[$ToIP, newIPName]];
continue ← TRUE;
};
TryPattern: PROC [pattern: Rope.ROPE] = {
ENABLE FS.Error => IF error.group # $bug THEN {
IO.PutRope[out, " -- "];
IO.PutRope[out, error.explanation];
GO TO err};
pattern ← FileNames.ResolveRelativePath[pattern];
pattern ← FS.ExpandName[pattern].fullFName;
IF NOT Rope.Match["*!*", pattern] THEN pattern ← Rope.Concat[pattern, "!h"];
FS.EnumerateForNames[pattern, EachFile];
EXITS
err => {IO.PutRope[out, "\n"]; RETURN};
};
out: IO.STREAM ← cmd.out;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd: cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
scriptName: Rope.ROPE ← argv[1];
ggData: GGData ← CreateWindow[GGScene.CreateScene[], TRUE, FALSE, FileNames.CurrentWorkingDirectory[]];
ViewerOps.PaintViewer[ggData.controls.panel, caption]; -- just to get the icon to appear
ViewerOps.PaintViewer[ggData.controls.topper, caption]; -- just to get the icon to appear
GGUserInput.EventNotify[ggData, LIST[$Typescript]];
ggData.refresh.suppressRefresh ← TRUE;
GGUserInput.EventNotify[ggData, LIST[$DisableRefresh]]; -- more kosher
FOR i: NAT IN [2..argv.argc) DO
arg: Rope.ROPE = argv[i];
IF Rope.Length[arg] = 0 THEN LOOP;
TryPattern[arg];
ENDLOOP;
GGUserInput.EventNotify[ggData, LIST[$EnableRefresh]];
GGUserInput.EventNotify[ggData, LIST[$Refresh]];
EXITS
failed => {result ← $Failure};
};
GGBasicTransformProc: PROC [bs: BiScrollers.BiScroller] RETURNS [Transformation] = {
Put the center of an 8.5x11 sheet of paper in the middle of the window.
height: REAL ← 11.0*72.0;
width: REAL ← 8.5*72.0;
actionArea: Viewer ← BiScrollers.QuaViewer[bs].child;
ww: REAL ← actionArea.ww;
wh: REAL ← actionArea.wh;
transform: Transformation;
transform ← ImagerTransformation.Translate[[(ww-width)/2.0, (wh-height)/2.0]];
RETURN[transform];
};
BSSaveProc: PRIVATE ViewerClasses.SaveProc = {
[self: ViewerClasses.Viewer, force: BOOLFALSE]
This SaveProc is called only by the emergency save mechanism when the BiScroller is a top level viewer. Normal saves call Store in GGEvent.
GGContainer.GargoyleContainerSave[self, force];
};
BSDestroyProc: PRIVATE ViewerClasses.DestroyProc = {
DestroyProc: TYPE = PROC [self: Viewer];
<<ggData: GGData ← NARROW[BiScrollers.ClientDataOfViewer[self]];
if there are two separate viewers and the panel viewer has already been destroyed, leaving only the picture viewer, when the picture viewer is destroyed then destroy the ggData structures and free the scene storage. If the panel viewer is not yet destroyed, don't free the scene storage so that the user can use the panel to save the scene.
IF ggData#NIL AND ggData.controls.topper=ggData.controls.picture AND ggData.controls.panel.destroyed THEN
GGUserInput.EventNotify[ggData, LIST[$Destroy]]; -- frees much garbage>>
Commented out because a race condition was causing crashes. -- Bier, May 21, 1992
};
Init: PROC = {
ggTipTable ← TIPUser.InstantiateNewTIPTable["Gargoyle.tip"]; -- used in ggData.parseInfo
actionAreaClass ← BiScrollers.GetStyle[].NewBiScrollerClass[[
flavor: $ActionArea,
save: BSSaveProc,
destroy: BSDestroyProc,
extrema: GGExtremaProc,
notify: GGUserInput.InputNotify,
bsUserAction: GGUserInput.BiScrollerInputNotify,
paint: GGActionAreaPaint,
tipTable: TIPUserExtras.TransparentTIPTable[], -- the real tipTable is in ggData.parseInfo
mayStretch: FALSE, -- NOT OK to scale X and Y differently
offsetsMustBeIntegers: TRUE,
preferIntegerCoefficients: FALSE,
vanilla: GGBasicTransformProc, --let the vanilla transform be the identity
preserve: [X: 0.5, Y: 0.5] --this point stays fixed during scaling (viewer size change)
]];
popUpFont ← ImagerFont.Find["xerox/tiogafonts/helvetica10I", substituteQuietly];
bigFont ← ImagerFont.Scale[ImagerFont.Find["xerox/pressfonts/helvetica-brr", substituteQuietly], 72.0];
iconsFrom ← "Gargoyle.icons"; -- KAP. October 16, 1990
InitIcons[];
BEGIN -- BuildCursors
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
linesPreferredArray: 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];
linesPreferredCursor ← Cursors.NewCursor[bits: linesPreferredArray, hotX: -8, hotY: -5];
offCursor ← Cursors.NewCursor[bits: offArray, hotX: -8, hotY: -6];
END;
Commander.Register[
key: "Gargoyle",
proc: NewGGViewers,
doc: "Create a Gargoyle Graphics Window",
clientData: NIL
];
Commander.Register[
key: "GGIPToIP",
proc: GGIPToIP,
doc: "GGIPToIP <scriptname> <ip file names, like ThesePics-*.ip> — apply the named script to each of the named interpress files (after performing MergeIPEditable). A new file with -mod.ip at the end will be created corresponding to each original file",
clientData: NIL
];
};
InitIcons: PUBLIC PROC = {
holdThatTiger: BOOL ← GGUserProfile.GetHoldThatTiger[];
panelIconG ← Icons.NewIconFromFile[iconsFrom, 5]; -- done here so file will come from working directory into which Gargoyle was brought over, e.g. ///Commands/
noNameIconG ← Icons.NewIconFromFile[iconsFrom, IF holdThatTiger THEN 8 ELSE 1]; -- done here so file will come from working directory into which Gargoyle was brought over, e.g. ///Commands/
dirtyNoNameIconG ← Icons.NewIconFromFile[iconsFrom, IF holdThatTiger THEN 7 ELSE 0]; -- done here so file will come from working directory into which Gargoyle was brought over, e.g. ///Commands/
cleanIconG ← Icons.NewIconFromFile[iconsFrom, IF holdThatTiger THEN 10 ELSE 3]; -- done here so file will come from working directory into which Gargoyle was brought over, e.g. ///Commands/
dirtyIconG ← Icons.NewIconFromFile[iconsFrom, IF holdThatTiger THEN 9 ELSE 2]; -- done here so file will come from working directory into which Gargoyle was brought over, e.g. ///Commands/
};
GetIcons: PUBLIC PROC RETURNS [panelIcon, noNameIcon, dirtyNoNameIcon, cleanIcon, dirtyIcon: Icons.IconFlavor] = {
RETURN[panelIconG, noNameIconG, dirtyNoNameIconG, cleanIconG, dirtyIconG];
};
popUpFont, bigFont: Imager.Font; -- initialized in Init
actionAreaClass: BiScrollers.BiScrollerClass ← NIL; -- filled in by Init
ggTipTable: TIPUser.TIPTable ← NIL; -- filled in by Init
panelIconG: Icons.IconFlavor ← unInit; -- filled in by InitIcons
noNameIconG: Icons.IconFlavor ← unInit; -- filled in by InitIcons
dirtyNoNameIconG: Icons.IconFlavor ← unInit; -- filled in by InitIcons
dirtyIconG: Icons.IconFlavor ← unInit; -- filled in by InitIcons
cleanIconG: Icons.IconFlavor ← unInit; -- filled in by InitIcons
iconsFrom: Rope.ROPENIL; -- filled in by Init
offCursor: Cursors.CursorType ← crossHairsCircle; -- proper value filled in by Init
pointsPreferredCursor: Cursors.CursorType ← crossHairsCircle; -- proper value filled in by Init
linesPreferredCursor: Cursors.CursorType ← crossHairsCircle; -- proper value filled in by Init
Init[];
END.