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; RestoreScreenAndInvariants: PUBLIC PROC [paintAction: ATOM, ggData: GGData, remake: ForegroundParts ¬ triggerBag, edited: BOOL ¬ TRUE, selectionChanged: BOOL ¬ FALSE, 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 OR selectionChanged THEN GGScene.SetLastEditedTime[ggData.scene, BasicTime.ExtendedNow[]]; 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]; -- 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; }; 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]; GGEvent.OpenAutoScript[ggData, TRUE]; RegisterEditedProc[ggData, GGState.GGEdited, NIL]; RestoreScreenAndInvariants[paintAction: $None, ggData: ggData, remake: triggerBag, edited: FALSE, okToSkipCapture: 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, withNullDoc: BOOL _ TRUE] 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]; 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[]; 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]; 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: PROC [scene: Scene, iconic: BOOL, paint: BOOL, workingDirectory: Rope.ROPE, fancyPanel: BOOL ¬ FALSE] RETURNS [ggData: GGData] = { 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]; GGEvent.OpenAutoScript[ggData, TRUE]; RegisterEditedProc[ggData, GGState.GGEdited, NIL]; IF GGUserProfile.GetAutoOpenHistory[] THEN ggData.history.tool ¬ GGHistory.BuildTool["Gargoyle", ggData]; 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] = { 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 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] = { 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; 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-- = { 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 ]; 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. }; NewGGViewers: Commander.CommandProc = { 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, ")"]; } 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 = { 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 = { 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: 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[$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 = { <> }; BiScrollerInputNotify: BiScrollers.BSUserActionProc = { ggData: GGData ¬ NARROW[BiScrollers.ClientDataOf[bs]]; GGUserInput.BiScrollerInputNotify[ggData, input]; }; SetBeingBorn: PUBLIC PROC [ggData: GGData, beingBorn: BOOL ¬ FALSE] = { 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 ¬ "/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 () ] [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  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. Ì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. Gargoyle Viewer State IF okToClearFeedback THEN Feedback.ClearHerald[ggData.feedback]; Fix the foreground Chain. Show the viewer as edited, if appropriate. See registration of GGState.GGEdited in this module 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 ggData ¬ CreateGGData[scene, workingDirectory]; [] _ SlackProcess.EnableAborts[handle: ggData.slackHandle]; IF GGUserProfile.GetAutoOpenTypescript[] THEN [] ¬ FeedbackOps.CreateNamedTypescript["Gargoyle Typescript", $Gargoyle, 120]; -- Bier, March 18, 1993 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]; cmd.out.PutF1["commandRope is [%g]\n", [rope[commandRope]] ]; [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] GGUserInput.EventNotify[ggData, LIST[$Typescript]]; -- Bier, March 18, 1993 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 between the active Gargoyle control panel and the scene was causing crashes. -- Bier, May 21, 1992 PROC [bs: BiScroller, input: LORA]; An action has been received from BiScroller (e.g. from the ScrollBar). If ggData was created with GGWindow.CreateGGData, this routine must be called with beingBorn = FALSE before the GGData will handle input events. iconsFrom ¬ "Gargoyle.icons"; -- KAP. October 16, 1990 Ê D•NewlineDelimiter –(cedarcode) style˜codešœ™Kšœ ÏeœO™ZK™,K™K™"K™&K™KšÏnœÿ™‡K˜—šÏk ˜ Jšœ–ŸœíŸœu˜üK˜—šž œŸœŸ˜JšŸœ±ŸœW˜‘KšŸœ>Ÿ˜J—˜Kšœ ŸœŸœ'Ïc˜[KšœŸœŸœ ˜VKšœŸœŸœ ˜VKšœ ŸœŸœ ˜SK˜Kšœ Ÿœ˜)KšœŸœŸœ ˜Kšœ Ÿœ˜+KšœŸœ!˜3Kšœ ŸœŸœ ˜Kšœ Ÿœ˜/KšœŸœ˜1Kšœ Ÿœ˜-KšœŸœ˜'Kšœ ŸœŸœ˜'KšœŸœŸœ ˜UKšœ Ÿœ˜/KšœŸœ˜!KšœŸœŸœ!˜Kš¡™KšœŸœ 7˜XK˜QK™®Kš¡ ™ KšœŸœ˜!K˜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šœ ¡œŸœAŸœ˜qK˜šœ$¡œ˜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šœÏt œ˜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™f—KšœŸœ ˜8Kšœ˜K˜—šžœ"˜0KšœŸœ ˜$K˜(K˜K˜—šžœŸœ6Ÿœ ŸœŸœŸœŸœ˜KšœJ™JKšœŸœ'˜>Kšœ ŸœŸœŸœ&˜BKšœP˜PKšœ Ÿœ# q˜£K˜š Ÿœ ŸœŸœŸœ Ÿœ 6˜bKšœŸœ˜(šœ8 ˜UKšœM™M—KšŸœŸœŸœKŸœ!˜˜Kšœ Ÿœ  ˜HK˜—šŸœ˜Kš œ ŸœŸœ ŸœŸœ˜ZKšœxŸœ˜~Kšœ0 ˜GK˜—Kšœ˜K˜—K˜šž œŸœŸœ 5˜WKšœŸœ˜KšœQ˜QKšœ!Ÿœ "˜JKšœ Ÿœ "˜IKšœŸœŸœ %˜AK˜K˜—K™šžœŸœŸœ6Ÿœ˜]Kšœ`™`Kšœ!Ÿœ7˜[KšœŸœ'˜BK˜K˜—šž œŸœŸœ&Ÿœ˜_Kšœ Ÿœ˜K˜`K˜[K˜Kšœ˜K˜—šž œŸœŸœ%Ÿœ˜_Kšœ Ÿœ˜K˜QK˜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˜—šžœŸœŸœŸœŸœŸœŸœŸœ˜AšŸœ˜šŸœŸœŸ˜KšœŸœŸœ˜KšœŸœŸœ ˜KšŸœŸœ˜!—K˜—K˜K˜—K˜š Ðbn œŸœŸœŸœ œ˜{K™QKšœ˜KšœŸœ ˜$K˜gKš œŸœŸœŸœTŸœ˜‡K˜9K˜—K˜K™Kšœ Ÿœ˜'KšœŸœŸœ˜-šœŸœŸœ˜"K˜Kšœ˜Kšœ Ÿ˜K˜—K˜K™š žœŸœŸœ;ŸœŸœ˜hš œ&ŸœŸœ ŸœŸœŸ˜QKšœ)˜)Kšœ7˜7KšŸœŸœ˜—K˜#šŸœ#Ÿœ˜)KšŸœ$Ÿœ Z˜ˆ—Kšœ+™+K˜—K˜K™ š¥ œ˜'Kš œŸœ ŸœŸœ ŸœŸœ™@Kš œŸœŸœŸœŸœ˜(Kšœ Ÿœ˜Kšœ ŸœŸœ˜Kšœ Ÿœ˜Kšœ ŸœŸœ˜Kšœ Ÿœ<Ÿœ˜RKšœ]Ÿœ˜iKš ŸœŸœŸœŸœ#ŸœŸœ˜lKšŸœŸœ'ŸœŸœ"ŸœŸœŸœ˜ˆKšŸœŸœ(ŸœŸœ"ŸœŸœŸœ˜ŠšŸœŸœŸœŸœŸœŸœŸœ ˜ZKšŸœ%ŸœŸœŸ˜DKš ŸœŸœ&ŸœŸœŸ˜KšŸœŸœ"ŸœŸœ˜6Kšœ Ÿ˜KšœŸœ˜K˜—šŸœŸœ Ÿœ˜Kšœ Ÿœ˜K˜+K™=K˜—šŸœ˜K˜mKšœ Ÿœ˜š ŸœŸœŸœŸœŸœŸœ˜HKš œŸœŸœŸœŸœ˜(KšœŸœ ˜(Kš œŸœŸœŸœŸœ˜!K˜1K˜—K˜—KšŸœ˜—šŸœŸœ Ÿœ˜Kšœ,Ÿœ˜Iš ŸœŸœŸœŸœŸœŸœ˜HKš œŸœŸœŸœŸœ˜(KšœŸœ ˜(Kš œŸœŸœŸœŸœ˜!K˜1K˜—K˜—K˜K˜—š žœŸœŸœ ŸœŸœŸœ˜HKšœ ŸœŸœ˜+Kšœ!Ÿœ˜:KšŸœ:˜@Kšœ˜K˜—šžœ˜!šžœŸœ ˜Kšœ ŸœŸœ Ÿœ™*Kšœ Ÿœ˜Kšœ˜K˜+K˜$KšœŸœ˜+KšœŸœ˜*Kšœ Ÿœ˜K˜—šž œŸœ Ÿ œ˜)šŸœŸœ ŸœŸœ˜/KšŸœ˜KšŸœ!˜#KšŸœŸœ˜ —K˜1Kšœ Ÿœ˜+KšŸœŸœŸœ&˜LKšŸœ&˜(šŸ˜KšœŸœŸœ˜'—K˜—KšœŸœŸœ ˜˜?Kšœ*ŸœŸœ ˜9—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˜1Kšœ Ÿœ˜+KšŸœŸœŸœ&˜LKšŸœ&˜(šŸ˜KšœŸœŸœ˜'—K˜—KšœŸœŸœ ˜˜?Kšœ*ŸœŸœ ˜9—KšœŸœ ˜ Kšœ5ŸœŸœ'˜gKšœ7 !˜XKšœ8 !˜YKšœ Ÿœ'™KK˜Kšœ!Ÿœ™&Kšœ Ÿœ ˜FšŸœŸœŸœŸ˜Kšœ Ÿœ ˜KšŸœŸœŸœ˜"Kšœ˜KšŸœ˜—Kšœ Ÿœ˜6Kšœ Ÿœ ˜0šŸ˜K˜—K˜K˜—K˜šžœŸœŸœ˜TK™GKšœŸœ ˜KšœŸœ ˜K˜5KšœŸœ˜KšœŸœ˜Kšœ˜K˜NKšŸœ ˜K˜K˜—–5 -- [self: ViewerClasses.Viewer, force: BOOL _ FALSE]šž œŸœ˜/Kšœ$ŸœŸœ™1K™ŒKšœ/˜/K˜K˜—šž œŸœ˜4Kšœ ŸœŸœ™(š œŸœ'˜@K™Õ—š ŸœŸœŸœ0Ÿœ!Ÿœ˜jKšœ Ÿœ  ˜H—Kš¡‰™‰Kšœ˜K˜—šžœ"˜7KšŸœŸœ™#KšœF™FKšœŸœ˜6K˜1K˜K˜—š ž œŸœŸœŸœŸœ˜GK™K˜#K˜K™—• CharProps$Postfix0.83333 1 0.7 textColor šÐlnœŸœ˜Kšœ= ˜Wš¡œ.˜=Kšœ˜K˜K˜Kšœ˜Kšœ ˜ K˜$Kšœ˜Kšœ) +˜TKšœ Ÿœ "˜4KšœŸœ˜KšœŸœ˜!Kšœ +˜JKšœ <˜WKšœ˜—K–O[name: ROPE, substitution: ImagerFont.Substitution _ substituteWithWarning]˜PK–O[name: ROPE, substitution: ImagerFont.Substitution _ substituteWithWarning]˜gKšœ ™6Kšœ. ˜DKšœ ˜ šŸœ ˜Kšœ   ˜ªKšœÝ ˜îKšœ¶  ˜¿K˜_K˜]K˜GKšŸœ˜—šœ˜Kšœ˜Kšœ˜K˜äKšœ Ÿ˜K˜—šœ˜Kšœ˜Kšœ˜Kšœý˜ýKšœ Ÿ˜K˜—Kšœ˜K˜—šž œŸœŸœ˜KšœŸœ$˜7K˜1Kšœ/ŸœŸœŸœ˜OKšœ4ŸœŸœŸœ˜TKšœ.ŸœŸœŸœ˜OKšœ.ŸœŸœŸœ˜NKšœ˜K˜—šžœŸœU˜rKšŸœD˜JK˜K˜—Kšœ! ˜8Kš¡œ Ÿœ ˜HKšœŸœ ˜8Kšœ' ˜@Kšœ( ˜AKšœ- ˜FKšœ' ˜@Kšœ' ˜@KšœŸœŸœ ˜0Kšœ9 !˜ZKšœE !˜fKšœD !˜eK˜Kšœ˜K˜KšŸœ˜K˜—…—}T¯d