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]= { 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]; 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. <GGHistoryImpl.mesa Copyright Ó 1988 by Xerox Corporation. All rights reserved. Pier, February 25, 1991 7:19 pm PST Bier, March 13, 1991 5:44 pm PST Booleans which control tricky spots to aid in bug tracking temporarily do a capture instead of a NewCurrent for all operations except transforms, interactive motion, undo, and set scale unit temporary capture SetCurrent[ggData, Create[nameAndCount]]; Capture[ggData, GetCurrent[ggData].event, FALSE]; Maintain the maximum size of the history list Here's a neat trick. Mark all the slices in the scene with the new Index by setting every historyTop value to the new index. Then, for every event in the tail, unlink any slices that do not appear in higher numbered history events. Objects in the scene will never be unlinked because they have just been marked with the highest possible number, the new index. PROC [data: REF] RETURNS [results: REF _ NIL]; new Advance stuff PROC [data: REF] RETURNS [results: REF _ NIL]; synchronize this process with the advance capture procces via a JOIN. If that process status=done, a new legit capture has completed. If advanceProcess=NIL, need to do a capture from scratch. Advance capture process is ABORTED (KillAdvanceCapture) by DoAdvanceCapture or NewCurrent before a new advance capture is started. a legitimate advance may be available Whoppee!! Advance worked Bah!! Advance failed. This is a rare case not covered by KillAdvanceCapture currentEvent: HistoryEvent _ NewCurrent[name, ggData]; Capture[ggData, currentEvent, FALSE]; currentEvent: HistoryEvent _ NewCurrent[name, ggData]; Capture[ggData, currentEvent, FALSE]; adds a subevent which captures the scene list, making copies of objects that are likely to change (selected objects). Actions performed during the current operation should not add more subevents to this event. GGHistoryTypes.HistoryProc This proc is called by the Undo mechanism. Caller should have done a global capture to cover this undo/uncapture operation, making undo of Uncapture possible. RETURN [NEW[HistoryEventObj _ [name, -1, LIST[NIL]] ]]; call the walkProc for every Slice found in the event, including shadow slices and their original storage manipulates some pointers to change list in place splice the new list element between the first and second list elements exchange the contents of the first and (new) second list elements Refuse to append any more subevents to a capture type subevent calls historyProc[historyRef] for each subevent in reverse order that subevents originally happened ResetEvent appends unlinkable slices to sliceList. It does not do its own unlinking. Callers should gather up all the unlinkable slices for all resettable events by iterative calls to ResetEvent, then walk the list, WHICH WILL CONTAIN DUPLICATE SLICES, and unlink each slice INDIVIDUALLY using GGSlice.UnlinkSlice and NOT GGSliceOps.Unlink. This is necessary since some slices may appear multiple times in a history, having been first a child slice and then subsequently made top level or reparented numerous times. This proc pays NO ATTENTION to whether slices in the event are in the scene, but only to the value of slice.historyTop. Caller must protect slices in scene if the scene is not to be trashed. IF doUnlink THEN FOR nextVictims: LIST OF Slice _ victims, nextVictims.rest UNTIL nextVictims=NIL DO GGSlice.UnlinkSlice[nextVictims.first] ENDLOOP; Take apart the subevent list and the event reference to the subevent. don't call NewCapture, because have to capture all independent of selection The following smashes are done because Undo has made bogus slice descriptors all over the place Following taken from GGHistoryToolImpl.mesa on September 1, 1988 ... builds a GG history tool. -- make the textField grow as the container grows ViewerOps.OpenIcon[icon: layout.container, closeOthers: FALSE, bottom: TRUE, paint: TRUE]; ButtonProc: TYPE = PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; new maxSize takes effect on next operation Clear the history tool viewers Following excerpted from EditToolBuilderImpl.mesa on August 31, 1988 Ê!=˜codešœ™K™œŸ ˜Sšœ#œŸ˜CKšœŸG˜bKšœ)˜)Kšœ˜!K˜—šœ˜ Kšœ™Kšœ)™)Kšœ*œ™1KšœŸ?˜YKšœœ˜ K˜——K˜—šœ˜KšœŸG˜bKšœ)˜)Kšœ˜!K˜—K˜—K˜K˜—šž œœœŸ)˜Wšœ œœœ˜Kšœ/˜/Kšœœœ ˜.šœœœ˜š ž œœœœœ˜CKšœ˜K˜—KšœœœŸ,˜QKšœ œ"Ÿ˜HKšœ)Ÿ‘˜ºKšœ˜Kšœ!˜!Kšœ(Ÿ(˜P˜K™-—KšœŸ˜7šœœœœœœœ˜CKšœ˜Kšœ˜K˜Kšœ˜—š œœœ œœŸ˜>š ž œœœœœ˜@š ž œœœœœ˜@Kšœ˜K˜—Kšœ!˜!K˜—Kšœœœ˜Kšœ.˜.K˜Kšœê™êKšœ9˜9K˜š œ œœ&œ œ˜TKšœ-˜-Kšœ˜—KšœœŸ)˜=KšœŸ˜4K˜—K˜—K˜—K˜—K˜šžœœœœ˜:Kšœœœ˜Kšœ#˜#K˜K˜—šžœœœ˜>KšœE˜EKšœ9˜9Kšœœœ ˜.šœœœ˜+Kšœ œ˜$Kšœœ˜"Kšœ-œ$˜WKšœ6œ˜œ ˜^Kšœ œœœ˜Kšœœœ ˜.K™>Kšœœœœœœ0œœ˜Žšœœœ˜(Kšœ.˜.—šœ˜K˜——šžœœœ=˜NKšœ0™0Kšœ3™3Kšœœœœ˜ Kšœœœ ˜.š œ œœ2œ œ˜\Kšœ˜Kšœœœ0˜?Kšœ˜—Kšœ˜K˜—šž œœœœ˜4Kšœœœ˜Kšœœœ˜Kšœ.˜.Kšœ#˜#š œœœ;œœ˜uKšœ3˜3Kšœ˜—KšœŸ˜4Kšœœœ˜ Kšœ ˜ Kšœœ˜"K˜Kšœ˜K˜—šž œœ'œœ ˜JKšž œ°ž œœœœ œœ œœÃ™†šœœœ˜Kšœœ œ¤™¿š žœœœœœ˜FKšœ œœœ>˜qK˜K˜—Kšœœ˜Kšœ+˜+K˜Kšœ œœœœ#œ œœ(œ™”K™K™Eš œœœ/œœ˜aKšœœœ"œ˜DKšœ˜—Kšœœ˜Kšœ˜—Kšœ˜K˜—šžœœ œœ ˜7šœ œœ œœ œœ˜WKšœ#Ÿ9˜\Kšœ˜—Kšœ˜K˜—šžœœœ˜,šœœœ˜Kšœœ˜Kšœ œ˜Kšœ˜—Kšœ˜—K˜šžœœœœœœœœœ˜hK˜—š žœœœžœœ ˜3šœ œœœ˜K–[format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šœœ˜-K–[format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šœ œ˜Kšœ œ˜Kšœ˜Kšœœœ-˜:–K[undoEvent: GGUndoEvent.UndoEvent, currentEvent: GGUndoEvent.UndoEvent]š œœœ œœŸ˜>K™KKšœ9˜9K–1[name: ROPE, ggData: GGInterfaceTypes.GGData]šœœŸ,˜Qšœ˜–K[undoEvent: GGUndoEvent.UndoEvent, currentEvent: GGUndoEvent.UndoEvent]š œœœ œœ˜%Kšœ7œ˜J–:[slice: GGModelTypes.Slice, scene: GGModelTypes.Scene]šœC˜CJ˜—Kšœœ8˜LKšœ˜—K–Ý[feedback: Feedback.FeedbackData, msgType: Feedback.MsgType, format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šœ;˜;Kšœpœœ˜ŽK˜—K–0[feedback: Feedback.FeedbackData, msg: ROPE]šœ`˜dK˜—K˜—K˜J™@K˜Kšž œœœ˜J˜šžœœœœ˜=Jšœ(˜(šœœœ˜Jšœ8˜8Jšœ8˜8J–{[viewer: ViewerClasses.Viewer, hint: ViewerClasses.PaintHint, clearClient: BOOL _ TRUE, whatChanged: REF ANY _ NIL]šœ8˜8J˜—J˜J˜—š ž œœœœœ˜VKšœ œ™Kšœ œ˜Kšœ˜Kšœ˜Kšœ˜Kšœ œ*˜9Kš œ œœœœœ&˜Wš œœœœœœŸ ˜iKšœœ˜1Kšœm˜mKšœ˜Kšœ˜—Kšœœ˜Kšœœ ˜&Kšœ:œœ˜qKšœ4˜4Kšœ4˜4šœŸœ˜KšœZ˜Z—K˜?Kšœ6˜6Kšœ6˜6šœŸœ˜KšœK˜K—Kšœ˜šœW˜WKšœHœœ˜aKšœœ˜—KšŸ1™1Kšœ9˜9Kšœ9˜9Kšœ6˜6Kšœ8œ œ œ™ZKšœ˜˜K˜——šžœ˜)Kšœ œœœœœ3œœ™ƒKšœœ ˜$Kšœ(˜(Kšœ3˜3Kšœ˜K˜—šžœ˜)Kšœœ ˜$Kšœ(˜(Kšœ/˜/Kšœ˜K˜—šž œ˜!Kšœœ ˜$Jšœ)˜)Jš œœœœœ˜(Kšœ&Ÿ˜,Jšœ8œ˜=K˜K˜—š žœœœœœ˜8Kšœœ ˜$Kšœ(˜(Kšœ œ˜Kš œœœœœ˜Kšœœœ-˜:Kšœ*Ÿ˜AKšœ:œŸ8˜š œ œœœ œ˜3K–¯[stream: STREAM, format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]˜=Kšœ ˜ Kšœ˜Kšœ˜—Kšœ)œ˜