GGWindowImpl.mesa
Copyright Ó 1985, 1986, 1987, 1988, 1989, 1992 by Xerox Corporation. All rights reserved.
Kurlander, September 1, 1987 11:46:26 am PDT
Pier, June 23, 1993 5:01 pm PDT
Bier, October 22, 1993 4:01 pm PDT
Doug Wyatt, April 17, 1992 1:45 pm PDT
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, BasicTime, BiScrollers, CodeTimer, Commander, CommanderOps, CursorTypes, EBMesaLisp, Feedback, FeedbackOps, FileNames, FS, Geom2D, GGActive, 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, GGWindowExtras, Icons, Imager, ImagerFont, ImagerTransformation, IO, MultiCursors, Process, Rope, SlackProcess, TiogaButtons, TIPUser, UserProfile, Vectors2d, ViewerClasses, ViewerOps;
GGWindowImpl: CEDAR PROGRAM
IMPORTS AtomButtons, BasicTime, BiScrollers, CodeTimer, Commander, CommanderOps, EBMesaLisp, Feedback, FeedbackOps, FileNames, FS, Geom2D, GGActive, 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, TIPUser, UserProfile, Vectors2d, ViewerOps
EXPORTS GGWindow, GGWindowExtras, 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: BOOL ¬ TRUE, selectionChanged: BOOL ¬ FALSE, 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 OR selectionChanged THEN GGScene.SetLastEditedTime[ggData.scene, BasicTime.ExtendedNow[]];
IF edited THEN {
See registration of GGState.GGEdited in this module
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;
};
GGRefresh.PaintInParent[ggData, paintAction]; -- calls PaintInViewer for normal GG use
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.ROPE ¬ NIL, fancyPanel: BOOL ¬ FALSE] RETURNS [ggData: 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 ¬ NIL,
paint: BOOL ¬ TRUE]
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.tipTable ¬ 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[paintAction: $None, ggData: ggData, remake: triggerBag, edited: FALSE, okToSkipCapture: 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, withNullDoc: BOOLTRUE] RETURNS [ggData: GGData] = {
ggData ¬ NEW[GGDataObj];
ggData.controls ¬ NEW[ControlsObj];
IF withNullDoc THEN ggData.controls.controlPanel ¬ GGActive.NullDoc[ggData];
ggData.controlState ¬ NEW[StateDataObj];
ggData.controlState.doubleBuffer ¬ TRUE;
ggData.controlState.useBackingMap ¬ 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;
ggData.refresh ¬ NEW[RefreshDataObj];
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
GGAlign.CreateLineTable[ggData];
ggData.refresh.startBoundBox ¬ NIL; -- this field is OBSOLETE and should never be referenced. KAP. June 22, 1992.
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: PROC [scene: Scene, iconic: BOOL, paint: BOOL, workingDirectory: Rope.ROPE, fancyPanel: BOOL ¬ FALSE] 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 ¬ CreateGGDataForViewer[outer: NIL, scene: scene, workingDirectory: workingDirectory, withNullDoc: FALSE];
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.tipTable ¬ 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]; -- Bier, March 18, 1993
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, clear: BOOL] RETURNS [quit: BOOL ¬ FALSE] = {
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];
ipContext: BOOL ~ context.GetClass[]=$Interpress; -- special check. IP contexts should not be queued for painting even if whatChanged=NIL. KAP. August 14, 1992.
IF whatChanged=NIL AND NOT ipContext 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 ¬ IF ipContext THEN $PaintSceneNoBuffer ELSE 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] = { -- 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] = {
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;
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, actionImage: Rope.ROPE, doc: Rope.ROPE, font: Imager.Font ¬ NIL] RETURNS [AtomButtons.PopUpChoice]= {
RETURN[[action, actionImage, doc, font]];
};
List: PROC [ref1, ref2, ref3: REF ¬ NIL] RETURNS [LIST OF REF]= {
RETURN[
SELECT TRUE FROM
ref2=NIL => LIST[ref1],
ref3=NIL => LIST[ref1, ref2],
ENDCASE => LIST[ref1, ref2, ref3]
];
};
GGExtremaProc: PROC [clientData: REF, 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
];
Cursors
SetCursorLooks: PUBLIC PROC [type: GGInterfaceTypes.GravityType, ggData: GGData, off: BOOL ¬ FALSE ] = {
newCursor: ViewerClasses.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.ROPE ¬ NIL;
argLength: NAT ¬ 0;
command: BOOL ¬ FALSE;
commandRope: ROPE;
fileOpened: BOOL ¬ FALSE;
fancyPanel: BOOL ¬ UserProfile.Boolean[key: "Gargoyle.FancyPanel", default: TRUE];
[list: args, length: argLength] ¬ CommanderOps.ParseToList[cmd: cmd !
CommanderOps.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 IF Rope.Equal[rl.first, "-command", FALSE] THEN {
command ¬ TRUE;
commandRope ¬ NIL;
}
ELSE IF command THEN {
command ¬ FALSE;
commandRope ¬ Rope.Cat["(", rl.first, ")"];
cmd.out.PutF1["commandRope is [%g]\n", [rope[commandRope]] ];
}
ELSE {
ggData: GGData ¬ OpenViewerOnFile[fileName: FileNames.ResolveRelativePath[rl.first], fancyPanel: fancyPanel];
fileOpened ¬ TRUE;
IF commandRope # NIL AND NOT Rope.Equal[commandRope, "()", FALSE] THEN {
stream: IO.STREAM ¬ IO.RIS[commandRope];
val: REF ¬ EBMesaLisp.Parse[stream].val;
event: LIST OF REF ¬ NARROW[val];
GGUserInput.BiScrollerInputNotify[ggData, event];
};
};
ENDLOOP;
IF NOT fileOpened THEN {
ggData: GGData ¬ OpenViewerOnFile[fileName: NIL, fancyPanel: fancyPanel];
IF commandRope # NIL AND NOT Rope.Equal[commandRope, "()", FALSE] THEN {
stream: IO.STREAM ¬ IO.RIS[commandRope];
val: REF ¬ EBMesaLisp.Parse[stream].val;
event: LIST OF REF ¬ NARROW[val];
GGUserInput.BiScrollerInputNotify[ggData, event];
};
};
};
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: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd: cmd
! CommanderOps.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: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd: cmd
! CommanderOps.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]]; -- Bier, March 18, 1993
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 between the active Gargoyle control panel and the scene was causing crashes. -- Bier, May 21, 1992
};
BiScrollerInputNotify: BiScrollers.BSUserActionProc = {
PROC [bs: BiScroller, input: LORA];
An action has been received from BiScroller (e.g. from the ScrollBar).
ggData: GGData ¬ NARROW[BiScrollers.ClientDataOf[bs]];
GGUserInput.BiScrollerInputNotify[ggData, input];
};
SetBeingBorn: PUBLIC PROC [ggData: GGData, beingBorn: BOOL ¬ FALSE] = {
If ggData was created with GGWindow.CreateGGData, this routine must be called with beingBorn = FALSE before the GGData will handle input events.
ggData.embed.beingBorn ¬ beingBorn;
};
Init: PROC = {
ggTipTable ¬ TIPUser.InstantiateNewTIPTable["Gargoyle.tip"]; -- used in ggData.tipTable
actionAreaClass ¬ BiScrollers.GetStyle[].NewBiScrollerClass[[
flavor: $ActionArea,
save: BSSaveProc,
destroy: BSDestroyProc,
extrema: GGExtremaProc,
notify: GGUserInput.InputNotify,
bsUserAction: BiScrollerInputNotify,
paint: GGActionAreaPaint,
tipTable: TIPUser.TransparentTIPTable[], -- the real tipTable is in ggData.parseInfo
mayStretch: TRUE, -- 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
iconsFrom ¬ "/Cedar/Gargoyle/Gargoyle.icons"; -- KAP. July 20, 1992.
InitIcons[];
BEGIN -- BuildCursors
pointsPreferredArray: CursorTypes.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: CursorTypes.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: CursorTypes.CursorArray = [177777B, 177777B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 140003B, 177777B, 177777B]; -- square
pointsPreferredCursor ¬ MultiCursors.NewCursor[bits: pointsPreferredArray, hotX: -8, hotY: -6];
linesPreferredCursor ¬ MultiCursors.NewCursor[bits: linesPreferredArray, hotX: -8, hotY: -5];
offCursor ¬ MultiCursors.NewCursor[bits: offArray, hotX: -8, hotY: -6];
END;
Commander.Register[
key: "Gargoyle",
proc: NewGGViewers,
doc: "Create a graphical editor\n Gargoyle [-~fancyPanel] [-command (<some commands>) ] [file name pattern]\n See /R/GargoyleControlPanel.gargoyle for example commands, like\n-command ((SelectAll)(AreaColorBlack)(DeselectAll))",
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];
noNameIconG ¬ Icons.NewIconFromFile[iconsFrom, IF holdThatTiger THEN 8 ELSE 1];
dirtyNoNameIconG ¬ Icons.NewIconFromFile[iconsFrom, IF holdThatTiger THEN 7 ELSE 0];
cleanIconG ¬ Icons.NewIconFromFile[iconsFrom, IF holdThatTiger THEN 10 ELSE 3];
dirtyIconG ¬ Icons.NewIconFromFile[iconsFrom, IF holdThatTiger THEN 9 ELSE 2];
};
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.ROPE ¬ NIL; -- filled in by Init
offCursor: ViewerClasses.CursorType ¬ crossHairsCircle; -- proper value filled in by Init
pointsPreferredCursor: ViewerClasses.CursorType ¬ crossHairsCircle; -- proper value filled in by Init
linesPreferredCursor: ViewerClasses.CursorType ¬ crossHairsCircle; -- proper value filled in by Init
Init[];
END.