<> <> <> <> DIRECTORY Buttons, CedarProcess, Containers, Feedback, GGCaret, GGEvent, GGHistory, GGHistoryTypes, GGHistoryTypesOpaque, GGInterfaceTypes, GGModelTypes, GGParent, GGScene, GGSelect, GGSlice, GGSliceOps, GGState, GGUtility, GGWindow, IO, MBQueue, Process, RefTab, Rope, Rules, SlackProcess, TEditDocument, TEditOps, TextEdit, TextNode, ViewerClasses, ViewerOps, ViewerSpecs, ViewerTools; GGHistoryImpl: CEDAR MONITOR IMPORTS GGParent, CedarProcess, Containers, Feedback, GGCaret, GGEvent, GGHistory, GGScene, GGSelect, GGSlice, GGSliceOps, GGState, GGUtility, GGWindow, IO, MBQueue, Process, RefTab, Rope, Rules, SlackProcess, TEditOps, TextEdit, TextNode, ViewerOps, ViewerSpecs, ViewerTools EXPORTS GGHistory, GGHistoryTypes = BEGIN Change: PUBLIC TYPE = GGHistoryTypesOpaque.Change; -- exported to GGHistoryTypes GGData: TYPE = GGInterfaceTypes.GGData; HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent; HistoryEventObj: TYPE = GGHistoryTypes.HistoryEventObj; HistoryTool: TYPE = REF HistoryToolObj; HistoryToolObj: PUBLIC TYPE = GGHistory.HistoryToolObj; -- exported to GGHistoryTypes HistoryProc: TYPE = GGHistoryTypes.HistoryProc; SubEvent: TYPE = GGHistoryTypes.SubEvent; SubEventObj: TYPE = GGHistoryTypes.SubEventObj; Layout: TYPE = GGHistory.Layout; LayoutRec: TYPE = GGHistory.LayoutRec; SlackHandle: TYPE = SlackProcess.SlackHandle; Slice: TYPE = GGInterfaceTypes.Slice; SliceWalkProc: TYPE = GGModelTypes.SliceWalkProc; DebugHalt: SIGNAL = CODE; KillError: SIGNAL = CODE; noHistory: BOOL _ FALSE; -- for performance testing. Eliminates all history activity tempCapture: BOOL _ TRUE; -- temporarily substitutes captures for currents. Required until slice property Get procs can return multiple values. <<>> <> doSelections: BOOL _ TRUE; doUnlink: BOOL _ TRUE; debugSlice: Slice _ NIL; NewCurrent: PUBLIC PROC [name: Rope.ROPE, ggData: GGData] RETURNS [HistoryEvent _ NIL] = { -- equivalent to SetCurrent[ggData, Create[name] ] IF noHistory THEN RETURN ELSE { count: INT _ GGScene.CountSelectedSlices[ggData.scene, first, normal]; nameAndCount: Rope.ROPE _ IF count=0 THEN IO.PutFR["%g no object", [rope[name]]] ELSE IO.PutFR["%g %g object%g", [rope[name]], [integer[count]], [rope[IF count>1 THEN "s" ELSE NIL]] ]; IF tempCapture THEN { <> <> SELECT TRUE FROM Rope.Equal["Transform", name, FALSE], -- from GGEventImplC Rope.Equal["Motion:", Rope.Substr[base: name, start: 0, len: 7], FALSE], -- from GGMouseEventImplA Rope.Equal["Undo", Rope.Substr[base: name, start: 0, len: 4], FALSE], -- from UndoN Rope.Equal["Set scale unit", name, FALSE] => { -- from GGEventImplB KillAdvanceCapture[ggData];-- KillAdvanceCapture synchronizes with already launched AdvanceCapture SetCurrent[ggData, Create[nameAndCount]]; RETURN[GetCurrent[ggData].event]; }; ENDCASE => { <> <> <> NewCapture[name, ggData]; -- NewCapture synchronizes with already launched AdvanceCapture RETURN[NIL]; }; } ELSE { KillAdvanceCapture[ggData];-- KillAdvanceCapture synchronizes with already launched AdvanceCapture SetCurrent[ggData, Create[nameAndCount]]; RETURN[GetCurrent[ggData].event]; }; }; }; PushCurrent: PUBLIC PROC [ggData: GGData] = { -- pushes current event onto history list IF noHistory THEN RETURN ELSE { event: HistoryEvent _ GetCurrent[ggData].event; IF GGSliceOps.debugHalt THEN SIGNAL DebugHalt; IF event#NIL THEN { SetSliceIndex: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { slice.historyTop _ newIndex; }; oldTail, tail: LIST OF HistoryEvent; -- will be list of stale events to dismantle newIndex: INT _ ggData.history.currentIndex+1; -- get ready for new push WalkSlicesInEvent[event, SetSliceIndex]; -- record the newIndex in slice.historyTop in each slice. slice.historyTop is the index of the latest history event a given slice is a member of event.index _ newIndex; Push[event, GetHistory[ggData] ]; ggData.history.currentIndex _ newIndex; -- record new index after Push succeeded <> tail _ GetHistory[ggData]; -- get expanded history list THROUGH [0..ggData.history.maxSize) DO IF tail=NIL THEN EXIT ELSE { oldTail _ tail; tail _ tail.rest; }; ENDLOOP; IF tail#NIL AND tail.first#NIL THEN { -- tail may be LIST[NIL] MarkSlices: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { TopHistory: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { slice.historyTop _ newIndex; }; DescendASlice[slice, TopHistory]; }; sliceList, ptr: LIST OF Slice; [sliceList, ptr] _ GGUtility.StartSliceList[]; <> [] _ GGScene.WalkSlices[ggData.scene, first, MarkSlices]; FOR tailEvents: LIST OF HistoryEvent _ tail, tailEvents.rest UNTIL tailEvents=NIL DO ResetEvent[tailEvents.first, sliceList, ptr]; ENDLOOP; oldTail.rest _ NIL; -- this truncates the actual history list UnlinkListedSlices[sliceList]; -- do your dirty work }; }; }; }; KillAdvanceCapture: PUBLIC ENTRY PROC [ggData: GGData] = { ENABLE UNWIND => NULL; KillAdvanceCaptureInternal[ggData]; }; KillAdvanceCaptureInternal: INTERNAL PROC [ggData: GGData] = { advanceProcess: CedarProcess.Process _ ggData.history.advanceProcess; advanceEvent: HistoryEvent _ ggData.history.advanceEvent; IF GGSliceOps.debugHalt THEN SIGNAL DebugHalt; IF ggData.history.advanceProcess#NIL THEN { ggData.history.advanceProcess _ NIL; ggData.history.advanceEvent _ NIL; IF CedarProcess.GetStatus[advanceProcess]=busy THEN CedarProcess.Abort[advanceProcess]; [] _ CedarProcess.Join[process: advanceProcess, wait: TRUE]; IF advanceEvent#NIL AND NOT Rope.Equal[advanceEvent.name, "advance", FALSE] THEN SIGNAL KillError; -- debugging check IF advanceEvent#NIL THEN [] _ CedarProcess.Fork[action: DiscardAction, data: advanceEvent, options: [priority: tryPriority, usePriority: TRUE]]; -- detach process by dropping return value. Start it in the foreground because of the way CedarProcess works. DiscardAction can drop to the background. }; }; DiscardAction: CedarProcess.ForkableProc = { <> advanceEvent: HistoryEvent _ NARROW[data]; IF advanceEvent#NIL AND NOT Rope.Equal[advanceEvent.name, "advance", FALSE] THEN SIGNAL KillError; -- debugging check IF advanceEvent#NIL THEN { CedarProcess.SetPriority[priority: background]; Process.Yield[]; GGUtility.UnlinkCapturedScene[advanceEvent]; }; }; tryPriority: CedarProcess.Priority _ normal; DoAdvanceCapture: PUBLIC ENTRY PROC [ggData: GGData] = { ENABLE UNWIND => NULL; IF noHistory THEN RETURN ELSE { IF GGSliceOps.debugHalt THEN SIGNAL DebugHalt; <> IF ggData.history.advanceProcess#NIL THEN KillAdvanceCaptureInternal[ggData]; -- important for synchronization ggData.history.advanceEvent _ Create["advance"]; ggData.history.advanceProcess _ CedarProcess.Fork[action: CaptureAction, data: ggData, options: [priority: tryPriority, usePriority: TRUE] ]; -- remember the advance process. Start it in the foreground because of the way CedarProcess works. CaptureAction can drop to the background. }; }; CaptureAction: CedarProcess.ForkableProc = { <> ggData: GGData _ NARROW[data]; IF GGSliceOps.debugHalt THEN SIGNAL DebugHalt; CedarProcess.SetPriority[priority: background]; Process.Yield[]; Capture[ggData, ggData.history.advanceEvent, FALSE]; }; NewCapture: PUBLIC ENTRY PROC [name: Rope.ROPE, ggData: GGData] = { <> ENABLE UNWIND => NULL; IF noHistory THEN RETURN ELSE { count: INT _ GGScene.CountSelectedSlices[ggData.scene, first, normal]; nameAndCount: Rope.ROPE _ IF count=0 THEN IO.PutFR["%g no object", [rope[name]]] ELSE IO.PutFR["%g %g object%g", [rope[name]], [integer[count]], [rope[IF count>1 THEN "s" ELSE NIL]] ]; IF GGSliceOps.debugHalt THEN SIGNAL DebugHalt; IF ggData.history.advanceProcess#NIL THEN { <> advanceStatus: CedarProcess.Status _ CedarProcess.Join[process: ggData.history.advanceProcess, wait: TRUE].status; SELECT advanceStatus FROM done => { <> ggData.history.advanceEvent.name _ nameAndCount; SetCurrent[ggData, ggData.history.advanceEvent]; -- advanceEvent already has capture subevent on it. PushCurrent will eventually push it onto history list. }; aborted => { <> <> <> SetCurrent[ggData, Create[nameAndCount]]; Capture[ggData, GetCurrent[ggData].event, FALSE]; }; ENDCASE => ERROR; ggData.history.advanceProcess _ NIL; ggData.history.advanceEvent _ NIL; } ELSE { -- no advance <> <> SetCurrent[ggData, Create[nameAndCount]]; Capture[ggData, GetCurrent[ggData].event, FALSE]; }; }; }; Capture: PROC [ggData: GGData, currentEvent: HistoryEvent, copyAll: BOOL _ FALSE] = { <> IF currentEvent#NIL THEN { captureRef: REF Change.capture; virginList: LIST OF Slice; virginMap: RefTab.Ref; [virginList, virginMap] _ GGUtility.CopySceneClean[ggData.scene, copyAll]; captureRef _ NEW[Change.capture _ [capture[ggData.scene, virginList, virginMap] ] ]; Note[currentEvent, Uncapture, captureRef]; }; }; Uncapture: PROC [historyData: REF Change, currentEvent: HistoryEvent] = { <> <> IF noHistory THEN RETURN ELSE { captureData: REF Change.capture; captureData _ NARROW[historyData]; GGUtility.RestoreSceneClean[captureData.scene, captureData.virginData, captureData.virginMap]; }; }; Create: PUBLIC PROC [name: Rope.ROPE] RETURNS [HistoryEvent] = { <> RETURN [IF noHistory THEN NIL ELSE NEW[HistoryEventObj _ [name, -1, LIST[NIL]] ]]; }; SetCurrent: PUBLIC PROC [ggData: GGData, event: HistoryEvent] = { -- sets current event to event IF GGSliceOps.debugHalt THEN SIGNAL DebugHalt; ggData.history.currentEvent _ event; }; GetCurrent: PUBLIC PROC [ggData: GGData] RETURNS [event: HistoryEvent, index: INT] = { RETURN[ggData.history.currentEvent, ggData.history.currentIndex]; }; DescendASlice: PROC [slice: Slice, walkProc: SliceWalkProc] = { [] _ walkProc[slice]; [] _ GGParent.WalkChildren[slice, all, walkProc]; }; WalkSlicesInEvent: PROC [event: HistoryEvent, walkProc: SliceWalkProc] = { <> IF event#NIL THEN FOR subevents: LIST OF SubEvent _ event.subevents, subevents.rest UNTIL subevents=NIL DO IF subevents.first#NIL THEN { subevent: SubEvent _ subevents.first; historyData: REF Change _ subevent.historyData; WITH historyData SELECT FROM captureRef: REF Change.capture => { FOR capturedSlices: LIST OF Slice _ captureRef.virginData, capturedSlices.rest UNTIL capturedSlices=NIL DO found: BOOL _ FALSE; nextVal: REF; nextSlice: Slice _ capturedSlices.first; [found, nextVal] _ RefTab.Fetch[captureRef.virginMap, nextSlice]; IF nextSlice.class#NIL THEN DescendASlice[nextSlice, walkProc]; IF found THEN { nextOriginal: Slice _ NARROW[nextVal]; IF nextOriginal.class#NIL THEN DescendASlice[nextOriginal, walkProc]; }; ENDLOOP; }; propsRef: REF Change.changingprops => { IF propsRef.slice.class#NIL THEN DescendASlice[propsRef.slice, walkProc]; }; stateRef: REF Change.changingstate => NULL; ENDCASE => ERROR; }; ENDLOOP; }; SetHistory: PUBLIC PROC [ggData: GGData, list: LIST OF HistoryEvent] = { IF GGSliceOps.debugHalt THEN SIGNAL DebugHalt; ggData.history.list _ list; }; GetHistory: PUBLIC PROC [ggData: GGData] RETURNS [LIST OF HistoryEvent] = { RETURN[ggData.history.list]; }; Push: PUBLIC PROC [event: HistoryEvent, list: LIST OF HistoryEvent] = { IF GGSliceOps.debugHalt THEN SIGNAL DebugHalt; IF event#NIL AND event.subevents#NIL THEN { <<>> <> tempEvent: HistoryEvent; newList: LIST OF HistoryEvent _ LIST[event]; <<>> <> temp: LIST OF HistoryEvent _ list.rest; list.rest _ newList; newList.rest _ temp; <> tempEvent _ list.first; list.first _ list.rest.first; list.rest.first _ tempEvent; }; }; Note: PUBLIC PROC [event: HistoryEvent, historyProc: HistoryProc, historyData: REF Change] = { IF event = NIL THEN RETURN; IF GGSliceOps.debugHalt THEN SIGNAL DebugHalt; <> IF GGSliceOps.noteNoMore AND event.subevents#NIL AND event.subevents.first#NIL AND event.subevents.first.historyData.kind=capture THEN RETURN; event.subevents _ CONS[NEW[SubEventObj _ [historyProc, historyData]], event.subevents]; }; Undo: PUBLIC PROC [historyEvent: HistoryEvent, currentEvent: HistoryEvent] = { <> <> IF historyEvent=NIL THEN RETURN; IF GGSliceOps.debugHalt THEN SIGNAL DebugHalt; FOR nextSubs: LIST OF SubEvent _ historyEvent.subevents, nextSubs.rest UNTIL nextSubs=NIL DO sub: SubEvent _ nextSubs.first; IF sub#NIL THEN sub.historyProc[sub.historyData, currentEvent]; ENDLOOP; }; ResetHistory: PUBLIC ENTRY PROC [ggData: GGData] = { ENABLE UNWIND => NULL; sliceList, ptr: LIST OF Slice; [sliceList, ptr] _ GGUtility.StartSliceList[]; KillAdvanceCaptureInternal[ggData]; FOR historyEventList: LIST OF HistoryEvent _ ggData.history.list, historyEventList.rest UNTIL historyEventList=NIL DO ResetEvent[historyEventList.first, sliceList, ptr]; ENDLOOP; UnlinkListedSlices[sliceList]; -- do your dirty work ggData.history.list _ LIST[NIL]; ggData.history.currentIndex _ 0; ggData.history.currentEvent _ NIL; GGHistory.ClearTool[ggData]; }; ResetEvent: PROC [event: HistoryEvent, sliceList, ptr: LIST OF Slice] = { <> IF event#NIL THEN { <> AddSliceToUnlink: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { IF slice.class#NIL AND slice.historyTop<=index THEN [sliceList, ptr] _ GGUtility.AddSlice[slice, sliceList, ptr]; }; index: INT _ event.index; WalkSlicesInEvent[event, AddSliceToUnlink]; <> <<>> <> FOR subeventList: LIST OF SubEvent _ event.subevents, subeventList.rest UNTIL subeventList=NIL DO IF subeventList.first#NIL THEN subeventList.first.historyData _ NIL; ENDLOOP; event.subevents _ NIL; }; }; UnlinkListedSlices: PROC [sliceList: LIST OF Slice] = { IF doUnlink THEN FOR slices: LIST OF Slice _ sliceList, slices.rest UNTIL slices=NIL DO GGSlice.UnlinkSlice[slices.first]; -- GGSlice.UnlinkSlice deals with already unlinked slices ENDLOOP; }; Reset: PUBLIC PROC [event: HistoryEvent] = { IF event#NIL THEN { event.subevents _ NIL; event.name _ NIL; }; }; Empty: PUBLIC PROC [event: HistoryEvent] RETURNS [BOOL] = { RETURN [event=NIL OR event.subevents=NIL] }; UndoN: PUBLIC PROC [ggData: GGData, N: INT _ 0] = { IF noHistory THEN RETURN ELSE { placeHolder: Rope.ROPE = "UndoN Placeholder"; title: Rope.ROPE; actualCount: INT _ 0; currentEvent: HistoryEvent; list: LIST OF HistoryEvent _ GGHistory.GetHistory[ggData]; IF list#NIL AND list.first#NIL THEN { -- list can be LIST[NIL] <> currentEvent _ GGHistory.NewCurrent[placeHolder, ggData]; Capture[ggData, currentEvent, TRUE]; -- capture the entire scene for Undo of Undo THROUGH [0..N) DO IF list#NIL AND list.first#NIL THEN { GGHistory.Undo[historyEvent: list.first, currentEvent: NIL]; list _ list.rest; actualCount _ actualCount+1; }; ENDLOOP; title _ GGHistory.GetCurrent[ggData].event.name _ IO.PutFR["Undo %g event%g. Actually undid %g event%g.", [integer[N]], [rope[IF N>1 THEN "s" ELSE NIL ]], [integer[actualCount]], [rope[IF actualCount>1 THEN "s" ELSE NIL ]] ]; GGHistory.PushCurrent[ggData]; <> GGCaret.SitOn[ggData.caret, NIL]; -- smash the chair GGCaret.NoAttractor[ggData.caret]; -- smash the attractor GGState.SetSliceToExtend[ggData, NIL]; -- smash the extension GGState.SetExtendMode[ggData, none]; -- smash the extension slice GGSelect.DeselectAllAllClasses[ggData.scene]; -- smash existing select lists BEGIN Reselect: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { GGSelect.ReselectSliceAllClasses[slice: slice, scene: ggData.scene] }; IF doSelections THEN [] _ GGScene.WalkSlices[ggData.scene, first, Reselect]; END; Feedback.Append[ggData.router, oneLiner, $Feedback, title]; GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; } ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "No entries on the history list to Undo"]; }; }; <> BadNumber: SIGNAL = CODE; CapTool: PUBLIC PROC [caption: Rope.ROPE, ggData: GGData] = { tool: HistoryTool _ ggData.history.tool; IF tool#NIL THEN { container: Containers.Container _ tool.layout.container; container.name _ Rope.Concat["GGHistory for ", caption]; ViewerOps.PaintViewer[viewer: container, hint: caption]; }; }; BuildTool: PUBLIC PROC [caption: Rope.ROPE, ggData: GGData] RETURNS [HistoryTool]= { <<... builds a GG history tool.>> openHeight: INTEGER _ 120; viewer: ViewerClasses.Viewer; tool: HistoryTool; layout: Layout; name: Rope.ROPE _ Rope.Concat["GGHistory for ", caption]; viewer _ IF ggData.history.tool=NIL THEN NIL ELSE ggData.history.tool.layout.container; IF viewer#NIL AND NOT (viewer.destroyed OR viewer.paintingWedged) THEN { -- already have this tool around IF viewer.iconic THEN ViewerOps.OpenIcon[viewer]; Feedback.PutF[ggData.router, oneLiner, $Complaint, "GGHistory tool for %g already exists", [rope[caption]] ]; RETURN [ggData.history.tool]; }; tool _ NEW[HistoryToolObj]; layout _ tool.layout _ NEW[LayoutRec]; layout.container _ Containers.Create[[name: name, iconic: TRUE, column: right, scrollable: FALSE, data: ggData]]; [] _ BuildButton[layout, "Show", SynchShow, ggData]; [] _ BuildButton[layout, "Undo", SynchUndo, ggData]; [ ----, tool.eventNumArg] _ BuildDataFieldPair[layout, "since Gargoyle event number: ", EventNumberButton, ggData, 1]; layout.heightSoFar _ layout.heightSoFar + layout.entryVSpace/2; [] _ BuildButton[layout, "Get", SynchGetSize, ggData]; [] _ BuildButton[layout, "Set", SynchSetSize, ggData]; [ ----, tool.sizeArg] _ BuildDataFieldPair[layout, "history size: ", HistorySizeButton, ggData, 1]; HRule[layout]; tool.textField _ ViewerOps.CreateViewer[flavor: $Text, info: [parent: layout.container, wx: layout.entryLeft, wy: layout.heightSoFar, wh: 700, ww: 200, border: FALSE, scrollable: TRUE], paint: FALSE]; <<-- make the textField grow as the container grows>> Containers.ChildYBound[layout.container, tool.textField]; Containers.ChildXBound[layout.container, tool.textField]; ViewerOps.SetOpenHeight[layout.container, openHeight]; <> RETURN [tool]; }; EventNumberButton: Buttons.ButtonProc = { <> ggData: GGData _ NARROW[clientData]; tool: HistoryTool _ ggData.history.tool; DataFieldButton[tool.eventNumArg, mouseButton#red]; }; HistorySizeButton: Buttons.ButtonProc = { ggData: GGData _ NARROW[clientData]; tool: HistoryTool _ ggData.history.tool; DataFieldButton[tool.sizeArg, mouseButton#red]; }; SynchShow: Buttons.ButtonProc = { ggData: GGData _ NARROW[clientData]; handle: SlackHandle _ ggData.slackHandle; event: LIST OF REF _ LIST[$HistoryShow]; GGEvent.PrintAllInput[ggData, event]; -- KAP SlackProcess.QueueAction[handle, DoShow, event, ggData, NIL]; }; DoShow: PROC [clientData: REF ANY, inputAction: REF] = { ggData: GGData _ NARROW[clientData]; tool: HistoryTool _ ggData.history.tool; num, stop: INT _ 0; h: IO.STREAM _ IO.ROS[]; list: LIST OF HistoryEvent _ GGHistory.GetHistory[ggData]; num _ GGHistory.GetCurrent[ggData].index; -- current event number stop _ GetInt[tool.eventNumArg ! BadNumber => { stop _ 0; CONTINUE }]; -- "since event number" argument. Used for Show and Undo WHILE num > stop AND list#NIL AND list.first#NIL DO h.PutF["%g\t%g\n", [integer[num]], [rope[list.first.name]] ]; num _ num-1; list _ list.rest; ENDLOOP; TEditOps.SetTextContents[tool.textField, IO.RopeFromROS[h]]; }; SynchUndo: Buttons.ButtonProc = { ggData: GGData _ NARROW[clientData]; handle: SlackHandle _ ggData.slackHandle; event: LIST OF REF _ LIST[$HistoryUndo]; GGEvent.PrintAllInput[ggData, event]; -- KAP SlackProcess.QueueAction[handle, DoUndo, event, ggData, NIL]; }; DoUndo: PROC [clientData: REF ANY, inputAction: REF] = { ggData: GGData _ NARROW[clientData]; { tool: HistoryTool _ ggData.history.tool; num: INT _ GGHistory.GetCurrent[ggData].index; -- current event number stop: INT _ GetInt[tool.eventNumArg ! BadNumber => GOTO Bad]; -- since event number" argument. Used for Show and Undo back: INT _ num-stop; IF back > 0 THEN GGHistory.UndoN[ggData, back]; EXITS Bad => Feedback.Append[ggData.router, oneLiner, $Complaint, "Please fill in the \"since Gargoyle event number\" field with a positive integer event number"]; }; }; SynchGetSize: Buttons.ButtonProc = { ggData: GGData _ NARROW[clientData]; handle: SlackHandle _ ggData.slackHandle; event: LIST OF REF _ LIST[$HistoryGetSize]; GGEvent.PrintAllInput[ggData, event]; -- KAP SlackProcess.QueueAction[handle, DoGetSize, event, ggData, NIL]; }; DoGetSize: PROC [clientData: REF ANY, inputAction: REF] = { ggData: GGData _ NARROW[clientData]; tool: HistoryTool _ ggData.history.tool; SetInt[tool.sizeArg, ggData.history.maxSize]; }; SynchSetSize: Buttons.ButtonProc = { ggData: GGData _ NARROW[clientData]; handle: SlackHandle _ ggData.slackHandle; event: LIST OF REF _ LIST[$HistorySetSize]; GGEvent.PrintAllInput[ggData, event]; -- KAP SlackProcess.QueueAction[handle, DoSetSize, event, ggData, NIL]; }; DoSetSize: PROC [clientData: REF ANY, inputAction: REF] = { ggData: GGData _ NARROW[clientData]; { tool: HistoryTool _ ggData.history.tool; num: INT _ GetInt[tool.sizeArg ! BadNumber => GOTO Bad]; ggData.history.maxSize _ IF num<=0 THEN 1 ELSE num; SetInt[tool.sizeArg, ggData.history.maxSize]; <> EXITS Bad => Feedback.Append[ggData.router, oneLiner, $Complaint, "Please fill in the \"history size\" field with a positive integer size"]; }; }; ClearTool: PUBLIC PROC [ggData: GGData] = { <> tool: HistoryTool _ ggData.history.tool; IF tool#NIL THEN { TEditOps.SetTextContents[tool.textField, NIL]; TEditOps.SetTextContents[tool.eventNumArg, NIL]; }; }; <> BuildButton: PROC [info: Layout, name: Rope.ROPE, proc: Buttons.ButtonProc, clientData: REF ANY _ NIL, fork: BOOL _ FALSE, gapAfter: BOOL _ TRUE, border: BOOL _ FALSE] RETURNS [button: Buttons.Button] = { button _ MBQueue.CreateButton[q: queue, info: [name: name, parent: info.container, wx: info.entryLeft, wy: info.heightSoFar, border: border], proc: proc, clientData: clientData, paint: FALSE]; info.entryLeft _ info.entryLeft + button.ww; IF gapAfter THEN info.entryLeft _ info.entryLeft + info.gapSize; RETURN[ button ]; }; DataFieldButton: PROC [arg: ViewerClasses.Viewer, clear: BOOL] = { IF clear THEN ViewerTools.SetContents[arg, NIL]; -- clear contents of field ViewerTools.SetSelection[arg, NIL]; -- make pending delete selection of field contents }; BuildDataFieldPair: PROC [info: Layout, buttonRope: Rope.ROPE, buttonProc: Buttons.ButtonProc, clientData: REF ANY _ NIL, lines: CARDINAL _ 2] RETURNS [button: Buttons.Button, arg: ViewerClasses.Viewer] = { fudge: CARDINAL = 1; button _ BuildButton[info: info, name: buttonRope, proc: buttonProc, clientData: clientData, fork: FALSE, gapAfter: FALSE]; arg _ ViewerOps.CreateViewer[flavor: $Text, info: [parent: info.container, wx: info.entryLeft, wy: info.heightSoFar+fudge, ww: ViewerSpecs.openRightWidth-info.entryLeft-5, wh: info.entryHeight*lines, border: FALSE, scrollable: FALSE], paint: FALSE]; info.heightSoFar _ info.heightSoFar + info.entryHeight*lines; info.entryLeft _ info.initLeft; Containers.ChildXBound[info.container, arg]; }; GetInt: PROC [arg: ViewerClasses.Viewer] RETURNS [num: INT] = { t: TextNode.RefTextNode _ WITH arg.data SELECT FROM tdd: TEditDocument.TEditDocumentData => TextNode.NarrowToTextNode[TextNode.FirstChild[tdd.text]], ENDCASE => NIL; rope: Rope.ROPE _ TextEdit.GetRope[t]; h: IO.STREAM _ IO.RIS[rope]; num _ IO.GetInt[h ! IO.Error, IO.EndOfStream => GOTO BadNum]; EXITS BadNum => SIGNAL BadNumber }; SetInt: PROC [arg: ViewerClasses.Viewer, num: INT] = { h: IO.STREAM _ IO.ROS[]; IO.Put[h,IO.int[num]]; TEditOps.SetTextContents[arg, IO.RopeFromROS[h]]; }; HRule: PROC [info: Layout, thickness: CARDINAL _ 1, gapAbove, gapBelow: BOOL _ TRUE] = { rule: Rules.Rule; IF gapAbove THEN info.heightSoFar _ info.heightSoFar + info.entryVSpace*2; rule _ Rules.Create[info: [parent: info.container, wx: 0, wy: info.heightSoFar, ww: ViewerSpecs.openRightWidth, wh: thickness], paint: FALSE]; Containers.ChildXBound[container: info.container, child: rule]; info.heightSoFar _ info.heightSoFar + thickness; IF gapBelow THEN info.heightSoFar _ info.heightSoFar + info.entryVSpace*2; }; queue: MBQueue.Queue _ MBQueue.Create[]; -- single queue, just for synchronization END.