<<>> <> <> <> <> <> <> <> <> DIRECTORY AtomButtons, EBTypes, EmbeddedButtons, Feedback, FeedbackOps, FeedbackTypes, GGActive, GGBasicTypes, GGCircleCache, GGContainer, GGControlPanelTypes, GGEvent, GGInterfaceTypes, GGMenu, GGModelTypes, GGScene, GGSceneType, GGSegmentTypes, GGShapes, GGState, GGUserInput, GGUserProfile, GGWindow, Imager, ImagerFont, List, Menus, PFS, PFSNames, Rope, Rules, SimpleFeedback, TextNode, TiogaActive, TiogaIO, TiogaOps, UserProfile, ViewerClasses, ViewerTools; GGMenuImpl: CEDAR PROGRAM IMPORTS AtomButtons, EmbeddedButtons, Feedback, FeedbackOps, GGActive, GGCircleCache, GGContainer, GGEvent, GGScene, GGShapes, GGState, GGUserInput, GGUserProfile, GGWindow, Imager, ImagerFont, List, PFS, PFSNames, Rope, Rules, SimpleFeedback, TiogaActive, TiogaIO, TiogaOps, UserProfile, ViewerTools EXPORTS GGInterfaceTypes, GGModelTypes, GGMenu = BEGIN ControlsObj: PUBLIC TYPE = GGControlPanelTypes.ControlsObj; MsgRouter: TYPE = FeedbackTypes.MsgRouter; GGData: TYPE = GGInterfaceTypes.GGData; GravityExtentData: TYPE = REF GravityExtentDataObj; GravityExtentDataObj: TYPE = GGInterfaceTypes.GravityExtentDataObj; Point: TYPE = GGBasicTypes.Point; SceneObj: PUBLIC TYPE = GGSceneType.SceneObj; -- export of opaque type SceneRef: TYPE = REF SceneObj; Viewer: TYPE = ViewerClasses.Viewer; ROPE: TYPE = Rope.ROPE; popUpFont: Imager.Font; -- initialized in Init below; entryHeight: CARDINAL = 15; -- height of a line of items docEntryHeight: CARDINAL = 19; -- height of a line of items entryHSpace: CARDINAL = 2; -- horizontal space between items on a line entryVSpace: CARDINAL = 2; -- vertical leading between lines fullColumn: CARDINAL = 600; -- the width of the standard large left column. wideBody: CARDINAL = 1000; -- the feedback window is too narrow smallNumberSize: CARDINAL = 60; AddARule: PROC [ggData: GGData] = { rule: Rules.Rule ¬ Rules.Create[[ parent: ggData.controls.panel, wy: ggData.height, ww: ggData.controls.panel.cw, wh: 2 ]]; GGContainer.ChildXBound[ggData.controls.panel, rule]; ggData.height ¬ ggData.height + rule.wh + entryVSpace; }; <<>> BuildControlPanel: PUBLIC PROC [ggData: GGData, fancyPanel: BOOL ¬ FALSE] = { BuildActiveDocument[ggData, fancyPanel]; InitializeGravityLine[ggData]; BuildSlopeLine[ggData]; BuildAngleLine[ggData]; BuildRadiusLine[ggData]; BuildDistanceLine[ggData]; BuildMeasureLine[ggData]; BuildFeedbackLine[ggData]; AddARule[ggData]; }; BuildFeedbackLine: PROC [ggData: GGData] = { nextX: NAT ¬ AtomButtons.BuildButtonLine[ggData.controls.panel, 0, ggData.height, ggData, GGUserInput.EventNotify, LIST[ [label["Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Line", FeedbackLineInGGData, wideBody]] ]]; ggData.height ¬ ggData.height + entryHeight; }; FeedbackLineInGGData: AtomButtons.UpdateProc = { <> <> <<(BT) Complaint: Gargoyle informs the user of a user-initiated problem>> <<(BT) Error: internal error. One of Gargoyle's invariants has been broken>> <<(L) Confirm: Gargoyle asks the user to confirm a guarded operation>> <<(L) DuringMouse: feedback generated each time the mouse is moved.>> <<(L) Feedback: an acknowledgement that Gargoyle has done what you asked>> <<(LT) Show: info that was explicitly requested (e.g. what color is this?)>> <<(LT) Statistics: Gargoyle prints user requested statistics>> <<(LT) Warning: Problems during a batch operation, like MergeInterpress>> <<(T) Typescript: Output that only seems appropriate for the typescript, period.>> <<(L) = send to label. (B) = send to label and blink. (T) = send to typescript>> ggData: GGData ¬ NARROW[clientData]; router: MsgRouter ¬ Feedback.CreateRouter[]; FeedbackOps.SetMultiLabel[router, button, TRUE, LIST[$Error, $Complaint]]; -- blink FeedbackOps.SetMultiLabel[router, button, FALSE, LIST[$DuringMouse, $Feedback, $Warning, $Confirm, $Show, $Statistics]]; FeedbackOps.SetMultiTypescript[router, $SystemScript, LIST[$Error, $Warning, $Show, $Typescript, $Complaint, $Statistics]]; ggData.router ¬ router; ggData.controls.feedbackLine ¬ button; }; IsGargoyle: PROC [path: PFS.PATH] RETURNS [BOOL] = { short: ROPE ~ PFSNames.ShortNameRope[path]; RETURN[Rope.Match["*.gargoyle", short, FALSE] OR Rope.Match["*.gg", short, FALSE]]; }; defaultPanelName: ARRAY BOOL--fancy-- OF ROPE ~ [ FALSE: "[Cedar]GargoyleControlPanel.tioga", TRUE: "[Cedar]GargoyleControlPanel.gargoyle"]; FindControlPanel: PROC [fancy: BOOL ¬ FALSE] RETURNS [path: PFS.PATH ¬ NIL, isGargoyle: BOOL ¬ FALSE] ~ { <> <<(1) The file specified in Gargoyle.ControlPanelFile: in the user profile.>> <<(2) The file "[Cedar]GargoyleControlPanel.tioga" (useful during development)>> <> value: LIST OF ROPE ~ UserProfile.ListOfTokens["Gargoyle.ControlPanelFile", NIL]; FOR list: LIST OF ROPE ¬ value, list.rest UNTIL list=NIL DO { ENABLE PFS.Error => CONTINUE; rope: ROPE ~ list.first; fullFName: PFS.PATH ~ PFS.FileInfo[PFS.PathFromRope[rope]].fullFName; gargoyle: BOOL ~ IsGargoyle[fullFName]; IF ((NOT fancy) OR gargoyle) THEN RETURN[fullFName, gargoyle]; }; ENDLOOP; { ENABLE PFS.Error => CONTINUE; rope: ROPE ~ defaultPanelName[fancy]; fullFName: PFS.PATH ~ PFS.FileInfo[PFS.PathFromRope[rope]].fullFName; RETURN[fullFName, IsGargoyle[fullFName]]; }; RETURN[NIL, FALSE]; }; LinkToActionArea: GGUserInput.UserInputProc = { <> theirData: GGData ¬ GGState.GetGGInputFocus[]; IF theirData = NIL THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "LinkToActionArea failed: place input focus in target Gargoyle viewer"] ELSE { ConnectToGGControlPanel[ggData: theirData, panelData: ggData]; }; }; ConnectToGGControlPanel: PUBLIC PROC [ggData: GGData, panelData: GGData] = { <> panel: EBTypes.ActiveDoc; panel ¬ ggData.controls.controlPanel ¬ GGActive.LookupDoc[panelData]; ggData.controls.controlPanelViewer ¬ NIL; EmbeddedButtons.LinkDocToApplication[doc: panel, target: $Gargoyle, targetViewer: ggData.controls.actionArea, applicationData: ggData, notifyProc: ControlPanelNotify]; EmbeddedButtons.LinkDocToApplication[doc: panel, target: $UnQueuedGargoyle, targetViewer: ggData.controls.actionArea, applicationData: ggData, notifyProc: UnQueuedControlPanelNotify]; }; BuildActiveDocument: PROC [ggData: GGData, fancyPanel: BOOL] = { nextX: NAT ¬ 0; v: Viewer; lines: NAT ~ 6; height: NAT ¬ lines*docEntryHeight; filePath: PFS.PATH ¬ NIL; fileName: ROPE ¬ NIL; isGargoyle: BOOL ¬ FALSE; [filePath, isGargoyle] ¬ FindControlPanel[fancyPanel]; IF filePath = NIL THEN SimpleFeedback.Append[$System, oneLiner, $Error, "No file found for Gargoyle control panel in [Cedar] or in Gargoyle.ControlPanelFile user profile entry."] ELSE { fileName ¬ PFS.RopeFromPath[filePath]; SimpleFeedback.PutF[$System, oneLiner, $Feedback, "Loading %g as Gargoyle's control panel", [rope[fileName]]]; }; IF isGargoyle THEN { thisData: GGData; scene: GGModelTypes.Scene ¬ GGScene.CreateScene[]; height ¬ 140; [v, thisData] ¬ GGWindow.CreateChildViewer[scene: scene, wx: 0, wy: ggData.height, wh: height, parent: ggData.controls.panel, workingDirectory: ggData.currentWDir, paint: TRUE ]; ggData.controls.controlPanel ¬ GGActive.LookupDoc[thisData]; GGUserInput.EventNotify[thisData, LIST[$MergeAll, fileName]]; GGUserInput.EventNotify[thisData, LIST[$DeselectAll]]; GGUserInput.EventNotify[thisData, LIST[$SetActive, Rope.Literal["TRUE"]]]; GGUserInput.EventNotify[thisData, LIST[$SetScaleUnit, NEW[REAL ¬ 1.0], $Quiet]]; GGUserInput.EventNotify[thisData, LIST[$CleanVersion]]; GGState.SetReadOnly[thisData, TRUE]; } ELSE { v ¬ ViewerTools.MakeNewTextViewer[[ wx: 0, wy: ggData.height, wh: height, parent: ggData.controls.panel, data: TiogaIO.FromFile[filePath].root, scrollable: TRUE, border: TRUE]]; ggData.controls.controlPanel ¬ TiogaActive.LookupDoc[v]; TiogaOps.Interpret[v, LIST[$ActivityOn]]; }; GGContainer.ChildXBound[ggData.controls.panel, v]; ggData.controls.controlPanelViewer ¬ v; EmbeddedButtons.LinkDocToApplication[doc: ggData.controls.controlPanel, target: $Gargoyle, targetViewer: ggData.controls.actionArea, applicationData: ggData, notifyProc: ControlPanelNotify]; EmbeddedButtons.LinkDocToApplication[doc: ggData.controls.controlPanel, target: $UnQueuedGargoyle, targetViewer: ggData.controls.actionArea, applicationData: ggData, notifyProc: UnQueuedControlPanelNotify]; ggData.height ¬ ggData.height + height; }; ControlPanelNotify: EmbeddedButtons.NotifyProc = { <> newEvent: LIST OF REF ¬ List.Append[events]; -- make a copy of the eventList, because EmbeddedButtons resuses the original ggData: GGData ¬ NARROW[applicationData]; [] ¬ GGActive.ControlPanelButtonHandler[ggData, newEvent, buttonInfo]; }; UnQueuedControlPanelNotify: EmbeddedButtons.NotifyProc = { <> newEvent: LIST OF REF ANY ¬ List.Append[events]; -- make a copy of the eventList, because EmbeddedButtons resuses the original GGUserInput.UnQueuedEventNotify[applicationData, newEvent]; }; <<<> <> <> <> <> <> <> <> <> <> <> <<>> <> <> <> <> <<[button["GravExtent:", LIST[LIST[$ShowGravExtent]]]]>> <<]];>> <<>> <> <> <> <> <> <> <> <> <<[LIST[$GravityExtentChange, $ValueDown]],>> <<[LIST[$GravityExtentChange, $InitialValue]],>> <<[LIST[$GravityExtentChange, $ValueUp]]],>> <> <> <> <> <<];>> <<>> <> <> <> <<[twoState["Gravity", LIST[$ToggleGravity], TRUE, GravityInGGData]],>> <<[twoState["Midpts", LIST[$ToggleMidpoints], FALSE, MidpointsInGGData]],>> <<[twoState["Auto", LIST[$ToggleHeuristics], GGUserProfile.GetDefaultHeuristics[], HeuristicsInGGData]],>> <<[twoState["Alignments", LIST[$ToggleShowAlignments], TRUE, AlignmentsInGGData]]>> <<],>> <> <<>> <<>> <> <<};>> <<>>>> <<>> InitializeGravityLine: PROC [ggData: GGData] = { ggData.hitTest.gravityType ¬ pointsPreferred; GGState.SetGravityExtent[ggData, GGUserProfile.GetDefaultGravityExtent[]/72.0]; GGState.SetHeuristics[ggData, GGUserProfile.GetDefaultHeuristics[]]; }; BuildSlopeLine: PROC [ggData: GGData] = { buttonHandle: AtomButtons.SortedButtonHandle; nextX: NAT ¬ AtomButtons.BuildButtonLine[ggData.controls.panel, 0, ggData.height, ggData, GGUserInput.EventNotify,LIST[ [button["Slope:", LIST[LIST[$SlopePrompt]] ]], [button["Get!", LIST[LIST[$GetSlope]] ]], [button["Add!", LIST[LIST[$AddSlope]] ]], [button["Delete!", LIST[LIST[$DeleteSlope]] ]] ]]; buttonHandle ¬ AtomButtons.CreateSortedButtonViewer[ggData.controls.panel, nextX, ggData.height]; ggData.controls.slopeHandle ¬ buttonHandle; GGEvent.StandardSlopes[ggData, LIST[$InitStandardSlopes]]; ggData.height ¬ ggData.height + entryHeight; }; BuildAngleLine: PROC [ggData: GGData] = { buttonHandle: AtomButtons.SortedButtonHandle; nextX: NAT ¬ AtomButtons.BuildButtonLine[ggData.controls.panel, 0, ggData.height, ggData, GGUserInput.EventNotify,LIST[ [button["Angle:", LIST[LIST[$AnglePrompt]] ]], [button["Get!", LIST[LIST[$GetAngle]] ]], [button["Add!", LIST[LIST[$AddAngle]] ]], [button["Delete!", LIST[LIST[$DeleteAngle]] ]] ]]; buttonHandle ¬ AtomButtons.CreateSortedButtonViewer[ggData.controls.panel, nextX, ggData.height]; ggData.controls.angleHandle ¬ buttonHandle; GGEvent.StandardAngles[ggData, LIST[$InitStandardAngles]]; ggData.height ¬ ggData.height + entryHeight; }; BuildRadiusLine: PROC [ggData: GGData] = { buttonHandle: AtomButtons.SortedButtonHandle; nextX: NAT ¬ AtomButtons.BuildButtonLine[ggData.controls.panel, 0, ggData.height, ggData, GGUserInput.EventNotify, LIST[ [button["Radius:", LIST[LIST[$RadiusPrompt]] ]], [button["Get!", LIST[LIST[$GetRadius]] ]], [button["Add!", LIST[LIST[$AddRadius]] ]], [button["Delete!", LIST[LIST[$DeleteRadius]] ]] ]]; buttonHandle ¬ AtomButtons.CreateSortedButtonViewer[ggData.controls.panel, nextX, ggData.height]; ggData.controls.radiusHandle ¬ buttonHandle; GGEvent.StandardRadii[ggData, LIST[$InitStandardRadii]]; ggData.controls.radiusCircleCache ¬ GGCircleCache.Create[]; ggData.height ¬ ggData.height + entryHeight; }; BuildDistanceLine: PROC [ggData: GGData] = { buttonHandle: AtomButtons.SortedButtonHandle; nextX: NAT ¬ AtomButtons.BuildButtonLine[ggData.controls.panel, 0, ggData.height, ggData, GGUserInput.EventNotify, LIST[ [button["LineDist:", LIST[LIST[$DistancePrompt]] ]], [button["Get!", LIST[LIST[$GetDistance]] ]], [button["Add!", LIST[LIST[$AddDistance]] ]], [button["Delete!", LIST[LIST[$DeleteDistance]] ]] ]]; buttonHandle ¬ AtomButtons.CreateSortedButtonViewer[ggData.controls.panel, nextX, ggData.height]; ggData.controls.distanceHandle ¬ buttonHandle; GGEvent.StandardDistances[ggData, LIST[$InitStandardDistances]]; ggData.height ¬ ggData.height + entryHeight; }; BuildMeasureLine: PROC [ggData: GGData] = { nextX: NAT ¬ AtomButtons.BuildButtonLine[ggData.controls.panel, 0, ggData.height, ggData, GGUserInput.EventNotify, LIST[ <<[label["Measure- "]],>> [button["_", LIST[LIST[$MeasureSlopeFromSelection]], -1, TRUE ]], [button["Slope:", LIST[LIST[$MeasureSlopeHit]], -1, TRUE ]], [text["0.0", SlopeViewInGGData, smallNumberSize]], [button["_", LIST[LIST[$MeasureAngleFromSelection]], -1, TRUE ]], [button["Angle:", LIST[LIST[$MeasureAngleHit]], -1, TRUE ]], [text["0.0", AngleViewInGGData, smallNumberSize]], [button["_", LIST[LIST[$MeasureRadiusFromSelection]], -1, TRUE ]], [button["Radius:", LIST[LIST[$MeasureRadiusHit]], -1, TRUE ]], [text["0.0", DistanceViewInGGData, smallNumberSize]], [button["_", LIST[LIST[$MeasureLineDistFromSelection]], -1, TRUE ]], [button["LineDist:", LIST[LIST[$MeasureLineDistHit]], -1, TRUE ]], [text["0.0", LineDistViewInGGData, smallNumberSize]] ]]; ggData.height ¬ ggData.height + entryHeight; }; <