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; RestoreScreenAndInvariants: PUBLIC PROC [paintAction: ATOM, ggData: GGData, remake: ForegroundParts _ triggerBag, edited: BOOL _ TRUE, okToSkipCapture: BOOL] = { 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; }; 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; }; 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; }; OpenViewerOnFile: PROC [fileName: Rope.ROPE _ NIL, fancyPanel: BOOL _ FALSE] = { 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 ANY _ 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.parseInfo _ TIPPrivate.CreateParseInfo[ggTipTable]; ggData.controls.topper.newVersion _ FALSE; ggData.controls.panel.newVersion _ FALSE; ggData.controls.picture.newVersion _ FALSE; SetCursorLooks[ggData.hitTest.gravityType, ggData]; GGEvent.OpenAutoScript[ggData, TRUE]; RegisterEditedProc[ggData, GGState.GGEdited, NIL]; RestoreScreenAndInvariants[$None, ggData, triggerBag, FALSE, FALSE]; viewer _ ggData.controls.picture; 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 = { 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; IF outer#NIL THEN { outer.newVersion _ FALSE; ggData.controls.topper _ ggData.controls.panel _ outer; }; GGStateExtras.SetWorkingDirectory[ggData, workingDirectory]; ggData.scene _ scene; ggData.caret _ GGCaret.Create[]; ggData.drag.savedCaret _ GGCaret.Create[]; ggData.anchor _ GGCaret.Create[]; ggData.camera _ NEW[CameraObj]; ggData.lastEvents _ GGCoreOps.NewEventListt[]; GGMouseEvent.InitializeFSM[ggData]; ggData.history _ [list: LIST[NIL], maxSize: GGUserProfile.GetDefaultHistorySize[], currentIndex: 0]; -- KAP July 7, 1988 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]; ggData.drag.selectState _ none; ggData.drag.extendMode _ none; GGState.SetSelectionCycler[ggData, GGMultiGravity.EmptyCycler[[0.0, 0.0]] ]; 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[]; 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]; 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; ggData.debug _ NEW[DebugDataObj]; ggData.debug.autoScriptNames _ GGCoreOps.NewRopeListt[]; 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: BOOL _ FALSE] RETURNS [ggData: GGData] = { 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]; 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: BOOL _ FALSE] = { 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 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] = { 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]; }; 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_ 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 ANY _ NIL] 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-- = { 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]; }; EditedProc: TYPE = GGWindow.EditedProc; EditedProcItem: TYPE = REF EditedProcItemObj; EditedProcItemObj: TYPE = RECORD [ ggData: GGData, editedProc: EditedProc, clientData: REF ANY ]; SetCursorLooks: PUBLIC PROC [type: GGInterfaceTypes.GravityType, ggData: GGData, off: BOOL _ FALSE ] = { 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. }; NewGGViewers: Commander.CommandProc = { nameList, args: LIST OF Rope.ROPE _ NIL; 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 = { 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 = { EachFile: FS.NameProc = { 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]]; 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] = { 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 = { GGContainer.GargoyleContainerSave[self, force]; }; BSDestroyProc: PRIVATE ViewerClasses.DestroyProc = { <> }; 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  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.ROPE _ NIL; -- 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. NGGWindowImpl.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. Gargoyle Viewer State IF okToClearFeedback THEN Feedback.ClearHerald[ggData.feedback]; Fix the foreground Chain. Show the viewer as edited, if appropriate. ViewerOps.PaintViewer[viewer: ggData.controls.actionArea, hint: client, whatChanged: ggData, clearClient: FALSE]; The Gargoyle Viewer [] _ SlackProcess.EnableAborts[handle: ggData.slackHandle]; Set up the feedback. If this window were not a child, the router would be created in GGMenuImplC.FeedbackLineInGGData. PROC [ggData: GGData, request: REF _ NIL, bounds: BoundBoxObj _ infiniteRect, clientData: REF _ NIL]; GGInMMMImpl.Paint can be used in place of this routine when Gargoyle is embedded in MMM. Parent Viewers Working Directory Scene and Interface Objects Input Handling Slack process must be created before call to GGMenu.BuildControlPanel Selections Hit Testing Refresh Behavior 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 Defaults 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 [] _ SlackProcess.EnableAborts[handle: ggData.slackHandle]; ViewerClasses.PaintProc. whatChanged is a GGData. self is an ActionArea. If we are reopening the window (not scrolling) use the backed sandwich layers The caller (represented by ggData) wishes to be notified whenever his Gargoyle viewer is edited. Gargoyle Viewer Creation Utilities 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. This proc is required by BiScrollers to return the extremes of the displayed data GargoyleData Class Procedures Cursors ggData.controls.actionArea.cursor _ cursor; The Gargoyle CommandTool Command [cmd: Handle] RETURNS [result: REF _ NIL, msg: Rope.ROPE _ NIL]; [fullFName: ROPE] RETURNS [continue: BOOL] 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. [fullFName: ROPE] RETURNS [continue: BOOL] ggData.refresh.suppressRefresh _ TRUE; Put the center of an 8.5x11 sheet of paper in the middle of the window. [self: ViewerClasses.Viewer, force: BOOL _ FALSE] This SaveProc is called only by the emergency save mechanism when the BiScroller is a top level viewer. Normal saves call Store in GGEvent. DestroyProc: TYPE = PROC [self: Viewer]; 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. Commented out because a race condition was causing crashes. -- Bier, May 21, 1992 Ê•NewlineDelimiter – "cedar" style˜codešœ™KšœT™TK™,K™Kšœ™K™-K™—KšÏnœÿ™‡K˜šÏk ˜ JšœzžœÓžœ˜áK˜—š œžœž˜Jšžœ˜žœr˜“Kšžœ.ž˜:—˜Kšœ žœžœ'Ïc˜[KšœžœžœŸ˜VKšœžœžœŸ˜VKšœ žœžœŸ˜SK˜Kšœ žœ˜)Kšœžœžœ ˜Kšœ žœ˜+Kšœžœ!˜3Kšœ žœžœ ˜Kšœ žœ˜/Kšœžœ˜1Kšœ žœ˜-Kšœžœ˜'Kšœ žœžœ˜'KšœžœžœŸ˜UKšœ žœ˜/Kšœžœ˜!Kšœžœžœ!˜˜>Kš ™KšœžœŸ7˜XKšœQ˜QK™®Kš  ™ Kšœžœ˜!Kšœ8˜8Kš ™šœžœ ˜5Kšœ˜Kšœ˜Kšœ˜Kšœ,˜,Kšœ˜Kšœ˜—K˜K˜—š œžœžœžœ žœžœžœ˜}Kšœ žœ<žœ˜RKšžœF˜LK˜—K˜šœžœžœžœ žœžœžœžœžœ˜šKšœD™DKšœ?™?KšœS™SK™KšœD™DKšœP™PK˜Kšœ/˜/K˜šœ$ œ˜6šœ˜Kšœ˜Kšœžœ˜ Kšœ ˜ Kšœ˜Kšœ ˜ Kšœ žœ˜Kšœ˜Kšœ˜—Kšœ˜K˜—šœ4 œ˜EKšœ˜šœ˜Kš œ¡ )¡ ¡ ¡ œ˜WKšœ˜Kšœžœ˜ Kšœ ˜ Kšœ˜KšœŸ*˜2Kšœ ˜ Kšœžœ˜ Kšœ ˜ Kšœ˜Kšœžœ˜Kšœ žœ˜—Kšœž˜ Kšœ˜—Kšœ_¡œ˜fKšœI¡œ˜OKšœ*ŸA˜kK˜K˜Kšœ.¢ œ˜:Kšœ œŸ˜FK˜šžœžœ)žœŸ˜SKšœb˜bKšœb˜bKšœ‚žœ˜‰Kšœ/˜/Kšœ*˜*Kšœ)˜)Kšœ˜—šžœŸ˜Kš  œH˜RKš  œF˜OK–9[viewer: ViewerClasses.Viewer, clientHeight: INTEGER]šœJ˜Jšœ[˜[Kšœ˜Kšœ$˜$Kšœ žœ  œ˜Kšœ/˜/—Kšœ$˜$šœ[˜[Kšœ˜Kšœ$˜$Kšœ žœ  œ˜Kšœ.˜.—Kšœ#˜#K˜K˜—Kšœ$žœ˜*Kšœ#žœ˜)Kšœ%žœ˜+Kšœ3˜3K˜K–$[handle: SlackProcess.SlackHandle]šœ;™;Kšœžœ˜%Kšœ-žœ˜2Kšžœ$žœ?˜išžœ'ž˜-KšœN˜N—KšœžœŸ˜8Kšœ˜K˜—šœ"˜0Kšœžœ ˜$Kšœ(˜(K˜K˜—šœžœ6žœžœ žœžœžœžœ˜ƒKšœJ™JKšœžœ'˜>Kšœ žœžœžœ&˜BKšœP˜PK˜šžœ žœžœŸ6˜PKšœžœ˜(šœ8Ÿ˜UKšœM™M—KšžœžœžœKžœ!˜˜Kšœ žœ Ÿ˜HK˜—šžœ˜Kšœ žœ˜/Kšœxžœ˜~Kšœ0Ÿ˜GK˜—Kšœ˜K˜—K˜š œžœžœžœŸ5˜[Kšœžœ˜KšœQ˜QKšœ!žœŸ"˜JKšœ žœŸ"˜IKšœžœžœŸ%˜AK˜K˜—K™š œžœžœ6žœžœ˜aKšœ`™`Kšœ!žœ7˜[Kšœžœ'˜BK˜K˜—š œžœžœ&žœ˜_Kšœ žœ˜Kšœ`˜`Kšœ[˜[Kšœ˜Kšœ˜K˜—š œžœžœ%žœ˜_Kšœ žœ˜KšœQ˜QKšœY˜YKšœ˜Kšœ˜—K˜K™"š œ˜,Kšžœ.žœžœžœ™RKšœ§™§Kšœžœ˜Kš œžœžœžœžœ˜Kšžœžœžœ˜&K˜šžœžœžœž˜&Kšœžœ3˜AKšœžœ˜Kšœ žœ5˜DKšœ žœ˜ Kš žœžœžœžœžœ˜{Kš žœžœžœžœžœžœ˜„Kšžœžœ˜ Kšžœ˜—K˜K˜—š œžœ{žœ%˜¹Kšžœ ˜Kš œžœžœ˜=K˜Kšœ˜Kšžœ žœžœ˜$Kš žœ¢œžœžœ ¢œ˜$Kš žœ¢œžœžœ ¢œ˜$Kš žœ¢œžœžœ ¢œ˜$Kš žœ¢œžœžœ ¢œ˜$Kš žœ¢œžœžœ ¢œ˜$Kš žœ¢œžœžœ ¢œ˜$Kš žœ¢œžœžœ ¢œ˜$Kš žœ¢œžœžœ ¢œ˜$Kš žœ¢œžœžœ ¢œ˜$Kš žœ¢œžœžœ ¢œ˜&Kš žœ¢œžœžœ ¢œ˜&Kš žœ¢œžœžœ ¢œ˜&Kš žœ¢œžœžœ ¢œ˜&Kš žœ¢œžœžœ ¢œ˜&Kš žœ¢œžœžœ ¢œ˜&Kš žœ¢œžœžœ ¢œ˜&Kš žœ¢œžœžœ ¢œ˜&Kš žœ¢œžœžœ ¢œ˜&Kš žœ¢œžœžœ ¢œ˜&K˜šžœ)žœžœž˜>Kšœžœ˜Kšžœ˜—K˜K˜—šœžœ žœžœžœžœžœ žœžœžœ˜ŒKšžœ#˜)K˜K˜—šœžœžœžœžœžœžœžœžœžœ˜Išžœ˜šžœžœž˜Kšœžœžœ˜Kšœžœžœ ˜Kšžœžœ˜!—K˜—K˜K˜—K˜š Ðbn œžœžœžœžœŸœ˜K™QKšœ˜Kšœžœ ˜$Kšœg˜gKš œžœžœžœTžœ˜‡Kšœ9˜9K˜—K˜K™Kšœ žœ˜'Kšœžœžœ˜-šœžœžœ˜"K˜Kšœ˜Kšœ žœž˜K˜—K˜K™š œžœžœ;žœžœ˜hš œ žœžœ žœžœž˜KKšœ)˜)Kšœ7˜7Kšžœžœ˜—Kšœ#˜#šžœ#žœ˜)Kšžœ$žœŸZ˜ˆ—Kšœ+™+K˜—K˜K™ š£ œ˜'Kš œžœ žœžœ žœžœ™@Kš œžœžœžœžœ˜(Kšœ žœ˜Kšœ žœ˜Kšœ žœ<žœ˜RKšœPžœ1žœ˜‘Kš žœžœžœžœžœžœ˜gKšžœžœ'žœžœžœžœžœ˜ƒKšžœžœ(žœžœžœžœžœ˜…šžœžœžœžœžœžœžœŸ˜ZKšžœ%žœžœž˜DKš žœžœ&žœžœž˜KKšžœb˜fKšžœ˜—K˜K˜—š œžœžœ žœžœžœ˜HJšœ žœžœ˜+Jšœ!žœ˜:Jšžœ:˜@Jšœ˜J˜—šœ˜!šœžœ ˜Kšœ žœžœ žœ™*Kšœ žœ˜Kšœ˜Kšœ+˜+Kšœ$˜$Kšœžœ˜+Kšœžœ˜*Kšœ žœ˜K˜—š œžœ ž œ˜)šžœžœ žœžœ˜/Kšžœ˜Kšžœ!˜#Kšžœžœ˜ —Kšœ1˜1Kšœ žœ˜+Kšžœžœžœ&˜LKšžœ&˜(šž˜Kšœžœžœ˜'—K˜—Kšœžœžœ ˜šœ=˜=Kšœ)žœžœ ˜8—Kšœ5žœžœ'˜gKšœ7Ÿ!˜XKšœ8Ÿ!˜YKšœ žœ˜3šžœžœžœž˜Kšœ žœ ˜Kšžœžœžœ˜"Kšœ˜Kšžœ˜—šž˜Kšœ˜—K˜K˜—šœ˜#K™‰šœžœ ˜Kšœ žœžœ žœ™*Kšœ žœ˜Kšœ˜Kšœ žœ ˜.Kšœ žœ˜CKšœ2˜2Kšœ.˜.Kšœ.˜.Kšœ žœ˜8Kšœ žœ˜K˜—š œžœ ž œ˜)šžœžœ žœžœ˜/Kšžœ˜Kšžœ!˜#Kšžœžœ˜ —Kšœ1˜1Kšœ žœ˜+Kšžœžœžœ&˜LKšžœ&˜(šž˜Kšœžœžœ˜'—K˜—Kšœžœžœ ˜šœ=˜=Kšœ)žœžœ ˜8—Kšœžœ ˜ Kšœ5žœžœ'˜gKšœ7Ÿ!˜XKšœ8Ÿ!˜YKšœ žœ˜3K˜Kšœ!žœ™&Kšœ žœŸ˜Fšžœžœžœž˜Kšœ žœ ˜Kšžœžœžœ˜"Kšœ˜Kšžœ˜—Kšœ žœ˜6Kšœ žœ ˜0šž˜Kšœ˜—K˜K˜—K˜šœžœžœ˜TK™GKšœžœ ˜Kšœžœ ˜Kšœ5˜5Kšœžœ˜Kšœžœ˜Kšœ˜KšœN˜NKšžœ ˜K˜K˜—–5 -- [self: ViewerClasses.Viewer, force: BOOL _ FALSE]š œžœ˜/Kšœ$žœžœ™1K™ŒKšœ/˜/K˜K˜—š œžœ˜4Kšœ žœžœ™(šŸœžœ'˜@K™Õ—š žœžœžœ0žœ!žœ˜jKšœ žœ Ÿ˜H—K™QKšœ˜K˜—šœžœ˜Kšœ=Ÿ˜Xš œ.˜=Kšœ˜K˜K˜Kšœ˜Kšœ ˜ Kšœ0˜0Kšœ˜Kšœ/Ÿ+˜ZKšœ žœŸ&˜9Kšœžœ˜Kšœžœ˜!KšœŸ+˜JKšœŸ<˜WKšœ˜—K–O[name: ROPE, substitution: ImagerFont.Substitution _ substituteWithWarning]šœP˜PK–O[name: ROPE, substitution: ImagerFont.Substitution _ substituteWithWarning]šœg˜gKšœŸ˜6Kšœ ˜ šžœŸ˜KšœœŸ ˜¦KšœÙŸ˜êKšœ²Ÿ ˜»KšœZ˜ZKšœX˜XKšœB˜BKšžœ˜—šœ˜Kšœ˜Kšœ˜Kšœ)˜)Kšœ ž˜K˜—šœ˜Kšœ˜Kšœ˜Kšœý˜ýKšœ ž˜K˜—Kšœ˜K˜—š œžœžœ˜Kšœžœ$˜7Kšœ2Ÿm˜ŸKšœ/žœžœžœŸm˜½Kšœ4žœžœžœŸm˜ÂKšœ.žœžœžœŸm˜½Kšœ.žœžœžœŸm˜¼Kšœ˜K˜—šœžœU˜rKšžœD˜JK˜K˜—Kšœ!Ÿ˜8Kš œ žœŸ˜HKšœžœŸ˜8Kšœ'Ÿ˜@Kšœ(Ÿ˜AKšœ-Ÿ˜FKšœ'Ÿ˜@Kšœ'Ÿ˜@KšœžœžœŸ˜0Kšœ3Ÿ!˜TKšœ?Ÿ!˜`Kšœ>Ÿ!˜_K˜Kšœ˜K˜Kšžœ˜J˜—…—xX¦¹