MatchViewerImpl.mesa
Last edited by: David Kurlander - September 5, 1987 4:36:00 pm PDT
Bier, September 5, 1987 1:26:38 am PDT
Pier, September 8, 1987 3:16:38 pm PDT
DIRECTORY
AtomButtons, AtomButtonsTypes, Basics, BiScrollers, BufferedRefresh, Buttons, Commander, Containers, Feedback, FunctionCache, Geom2D, GGAlign, GGBasicTypes, GGBoundBox, GGCaret, GGContainer, GGFont, GGGravity, GGInterfaceTypes, GGMenu, GGModelTypes, GGMouseEvent, GGMultiGravity, GGOutline, GGRefresh, GGScene, GGSegmentTypes, GGSelect, GGSequence, GGShapes, GGSlice, GGTraj, GGUserInput, GGUtility, GGWindow, GraphicsButton, Icons, Imager, ImagerTransformation, InputFocus, IO, Labels, Match, MatchGrep, MatchViewer, Rope, SlackProcess, TIPUser, ViewerClasses, ViewerOps;
MatchViewerImpl:
CEDAR
PROGRAM
IMPORTS AtomButtons, Basics, BiScrollers, BufferedRefresh, Buttons, Commander, Containers, Feedback, FunctionCache, Geom2D, GGAlign, GGBoundBox, GGCaret, GGFont, GGGravity, GGMenu, GGMouseEvent, GGMultiGravity, GGOutline, GGRefresh, GGScene, GGSelect, GGSequence, GGSlice, GGShapes, GGTraj, GGUserInput, GGUtility, GGWindow, GraphicsButton, Icons, Imager, ImagerTransformation, InputFocus, IO, Labels, Match, MatchGrep, Rope, TIPUser, ViewerOps
EXPORTS MatchViewer = BEGIN
CameraObj: TYPE = GGModelTypes.CameraObj;
Caret: TYPE = REF CaretObj;
CaretObj: TYPE = GGInterfaceTypes.CaretObj;
ChoiceDataObj: TYPE = MatchViewer.ChoiceDataObj;
FeedbackData: TYPE = Feedback.FeedbackData;
FiltersObj: TYPE = GGInterfaceTypes.FiltersObj;
GGDataObj: TYPE = GGInterfaceTypes.GGDataObj;
GGData: TYPE = GGInterfaceTypes.GGData;
MatchData: TYPE = MatchViewer.MatchData;
MatchDataObj: TYPE = MatchViewer.MatchDataObj;
Point: TYPE = GGBasicTypes.Point;
Sequence: TYPE = GGModelTypes.Sequence;
Scene: TYPE = GGModelTypes.Scene;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator;
Traj: TYPE = GGModelTypes.Traj;
Viewer: TYPE = ViewerClasses.Viewer;
actionAreaHeight: CARDINAL = 200; -- height of action area biscroller viewer
actionAreaMargin: CARDINAL = 3; -- distance between two action areas
actionAreaStartY: CARDINAL = entryHeight;
buttonAlign: CARDINAL = 2; -- vertical alignment differential for the 2 kinds of buttons used
entryHeight: CARDINAL = 15; -- height of a line of items
entryHSpace: CARDINAL = 2; -- space between ButtonLines on same line
entryVSpace: CARDINAL = 2; -- vertical leading between lines
InitViewers:
PROC [] = {
matchContainerClass: ViewerClasses.ViewerClass ←
NEW[ViewerClasses.ViewerClassRec ← [
init: MatchContainerInit,
adjust: MatchContainerAdjust,
topDownCoordSys: TRUE,
icon: matchToolIcon
]];
ViewerOps.RegisterViewerClass[$MatchContainer, matchContainerClass]; -- plug in to Viewers
[] ← ViewerOps.CreateViewer [flavor: $MatchContainer,
info: [name: "MatchTool",
menu: NIL,
iconic: FALSE,
column: right,
scrollable: FALSE
],
paint: TRUE];
};
fromViewer, toViewer: Viewer ← NIL;
fromData, toData, grepData: GGData ← NIL;
fromLine, toLine: Viewer ← NIL;
Building a MatchTool Viewer
MatchContainerInit: ViewerClasses.InitProc = {
fromData ← BuildDefaultGGData[self];
toData ← BuildDefaultGGData[self];
grepData ← BuildDefaultGGData[self]; -- this data is used to search scenes during grep
CreateFeedbackLine[toData, fromData, grepData];
fromViewer ← BuildActionArea[self, fromData];
toViewer ← BuildActionArea[self, toData];
fromLine ← BuildActionAreaLine[fromData, 0];
toLine ← BuildActionAreaLine[toData, 0];
InitControlPanel[self, actionAreaStartY + actionAreaHeight + entryHeight + upperMargin];
};
GetFromData:
PUBLIC
PROC
RETURNS [GGData] = {
RETURN[fromData];
};
GetToData:
PUBLIC
PROC
RETURNS [GGData] = {
RETURN[toData];
};
GetGrepData:
PUBLIC
PROC
RETURNS [GGData] = {
RETURN[grepData];
};
BuildDefaultGGData:
PROC [outer: Viewer]
RETURNS [ggData: GGData] = {
Builds the GGData for the MatchTool From and To viewers. outer is the MatchTool Container.
ggData ← NEW[GGDataObj ← matchData.hostData^];
ggData.biScroller ← NIL;
outer.data ← ggData;
ggData.outer ← outer;
ggData.hitTest ← NEW[FiltersObj ← matchData.hostData.hitTest^];
ggData.hitTest.triggerBag ← GGAlign.CreateTriggerBag[];
ggData.hitTest.sceneBag ← GGAlign.CreateTriggerBag[];
ggData.hitTest.alignBag ← GGAlign.CreateAlignBag[];
ggData.refresh.lineCache ← FunctionCache.Create[maxEntries: 200];
ggData.scene ← GGScene.CreateScene[];
ggData.caret ← NEW[CaretObj];
ggData.anchor ← NEW[CaretObj];
ggData.camera ← NEW[CameraObj];
GGMouseEvent.InitializeFSM[ggData];
ggData.drag.savedCaret ← NEW[CaretObj];
ggData.drag.selectState ← none;
ggData.drag.extendMode ← none;
ggData.refresh.startBoundBox ← GGBoundBox.NullBoundBox[];
ggData.refresh.sandwich ← GGRefresh.CreateSandwich[];
ggData.defaults ← NEW[GGInterfaceTypes.DefaultDataObj ← [strokeColor: Imager.black, fillColor: GGOutline.fillColor, font: GGFont.DefaultDefaultFontData[] ] ];
ggData.height ← 0;
ggData.outer.newVersion ← FALSE;
ggData.gravityPool ← GGGravity.NewGravityPool[];
ggData.multiGravityPool ← GGMultiGravity.NewMultiGravityPool[];
[] ← SlackProcess.EnableAborts[handle: ggData.slackHandle];
};
CreateFeedbackLine:
PROC [data1, data2, grepData: GGData] = {
Creates a single feedback line for two action areas (represented by data1 and data2) and the grepData
GGMenu.BuildFeedbackLine[data1];
grepData.feedback ← data2.feedback ← data1.feedback;
data2.height ← data2.height + entryHeight; -- data1 was incremented by BuildFeedbackLine
};
MatchContainerAdjust: ViewerClasses.AdjustProc = {
margin1: CARD ← (self.ww - actionAreaMargin) / 2;
margin2: CARD ← (self.ww + actionAreaMargin) / 2;
IF fromViewer # NIL THEN ViewerOps.MoveViewer[fromViewer, 0, fromViewer.wy, margin1, fromViewer.wh, FALSE];
IF toViewer # NIL THEN ViewerOps.MoveViewer[toViewer, margin2, toViewer.wy, margin1, toViewer.wh, FALSE];
IF fromLine # NIL THEN ViewerOps.MoveViewer[fromLine, fromLine.wx, fromLine.wy, margin1, fromLine.wh, FALSE];
IF toLine # NIL THEN ViewerOps.MoveViewer[toLine, margin2, toLine.wy, margin1, toLine.wh, FALSE];
};
BuildActionArea:
PRIVATE
PROC [outer: Viewer, ggData: GGData]
RETURNS [Viewer] = {
Create an action area, complete with handy Viewer-Control line
actionAreaClass: BiScrollers.BiScrollerClass ← BiScrollers.GetStyle[].NewBiScrollerClass[[
flavor: $ActionArea,
extrema: GGExtremaProc,
notify: GGUserInput.InputNotify,
paint: GGActionAreaPaint,
tipTable: TIPUser.InstantiateNewTIPTable["Gargoyle.TIP"],
mayStretch: FALSE, -- NOT OK to scale X and Y differently
offsetsMustBeIntegers: TRUE,
preferIntegerCoefficients: FALSE,
vanilla: GGBasicTransformProc, --proc which provides the vanilla transform for BiScrollers
preserve: [X: 0.5, Y: 0.5] --this specifies point that stays fixed when viewer size changes
]];
bs: BiScrollers.BiScroller ← BiScrollers.GetStyle[].CreateBiScroller[
class: actionAreaClass,
info: [
parent: ggData.outer,
wx: 0, wy: actionAreaStartY,
ww: 700, --ggData.outer.ww,
wh: actionAreaHeight, --ggData.outer.wh,
only initial values for ww and wh. They are constrained below
data: ggData,
border: TRUE,
scrollable: FALSE],
paint: FALSE
];
ggData.actionArea ← bs.QuaViewer[inner: TRUE];
ggData.biScroller ← bs;
GGWindow.SetCursorLooks[ggData.hitTest.gravityType, ggData]; -- assumes gravity is turned ON
ggData.height ← ggData.height + actionAreaHeight;
RETURN[bs.QuaViewer[inner: FALSE]];
};
GGExtremaProc:
PROC [clientData:
REF
ANY, direction: Geom2D.Vec]
RETURNS [min, max: Geom2D.Vec]
--BiScrollers.ExtremaProc-- = {
This proc is required by BiScrollers to return the extremes of the displayed data
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];
};
GGActionAreaPaint:
PROC [self: Viewer, context: Imager.Context, whatChanged:
REF
ANY, clear:
BOOL]
RETURNS [quit:
BOOL ←
FALSE] = {
ViewerClasses.PaintProc. whatChanged is a GGData. self is an ActionArea.
PaintEntireViewer:
PROC =
CHECKED {
BufferedRefresh.FitSandwichToScreen[ggData.refresh.sandwich, self.cw, self.ch];
GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE];
};
clientToViewer: Imager.Transformation ← BiScrollers.GetStyle[].GetTransforms[BiScrollers.QuaBiScroller[self]].clientToViewer;
ggData: GGData;
IF whatChanged =
NIL
THEN {
--we are being called by Window Manager
We are called for one of two reasons: scrolling in an already open window, or reopening a window. If we are reopening we can take advantage of the fact that the sandwich may still be valid from when we closed the window. We detect this by comparing the new BiScrollers clientToViewer transform with the one we saved last time we came through here. If they are the same, we assume we can use the old sandwich.
ggData ← NARROW[BiScrollers.ClientDataOfViewer[self]];
BufferedRefresh.FitSandwichToScreen[ggData.refresh.sandwich, self.cw, self.ch];
IF ggData.refresh.clientToViewer#
NIL
AND ImagerTransformation.Equal[clientToViewer, ggData.refresh.clientToViewer]
THEN {
-- Window Motion
GGWindow.RestoreScreenAndInvariants[paintAction: $ViewersPaintAllPlanes, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE];
}
ELSE {
-- Scrolling
GGWindow.RestoreScreenAndInvariants[paintAction: $ViewersPaintEntireScene, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE];
};
}
ELSE {
ggData ← NARROW[whatChanged];
IF ggData.refresh.paintAction=$PaintEntireViewer THEN PaintEntireViewer[] ELSE
GGRefresh.ActionAreaPaint[context, ggData.refresh.paintAction, ggData];
};
ggData.refresh.clientToViewer ← clientToViewer; -- for next time around
BuildActionAreaLine:
PROC [ggData: GGData, startX:
CARD]
RETURNS [Viewer] = {
height1: CARDINAL = buttonAlign;
buttonHolder: Viewer ← GetButtonHolder[parent: ggData.outer, wx: startX, wy: ggData.height];
nextX:
INTEGER ← AtomButtons.BuildButtonLine [buttonHolder, 0, 0, ggData, GGUserInput.EventNotify,
LIST[[button["Fetch",
LIST[
LIST[$MatchFetch]], -1,
FALSE,
NIL]]
]];
tV: Viewer ← BiScrollers.CreateFit[ [parent: buttonHolder, wx: nextX+entryHSpace, wy: height1, border: FALSE], ggData.biScroller];
tV ← BiScrollers.CreateReset[ [parent: buttonHolder, wx: tV.wx+tV.ww+entryHSpace, wy: height1, border: FALSE], ggData.biScroller];
tV← Buttons.Create[info: [parent: buttonHolder, name: "CenterSel", wx: tV.wx+tV.ww+entryHSpace, wy: 0, border: FALSE], proc: CenterSel, clientData: ggData, paint: TRUE];
RETURN[buttonHolder];
};
RegisterMatchActions:
PROC = {
GGUserInput.RegisterAction[atom: $MatchFetch, eventProc: Fetch, argType: none, ensureUnique: FALSE]; -- Fetch button
GGUserInput.RegisterAction[atom: $MatchSetFrom, eventProc: SetFromColumn, argType: none, ensureUnique: FALSE];
GGUserInput.RegisterAction[atom: $MatchSetTo, eventProc: SetToColumn, argType: none, ensureUnique: FALSE];
GGUserInput.RegisterAction[atom: $MatchToggle, eventProc: ToggleFromOrTo, argType: none, ensureUnique: FALSE]; -- Toggle From/To buttons
GGUserInput.RegisterAction[atom: $MatchSearch, eventProc: Match.SearchEvent, argType: none, ensureUnique: FALSE]; -- Search button
GGUserInput.RegisterAction[atom: $MatchYes, eventProc: Match.YesEvent, argType: none, ensureUnique: FALSE]; -- Yes button
GGUserInput.RegisterAction[atom: $MatchChangeAll, eventProc: Match.ChangeAll, argType: none, ensureUnique: FALSE]; -- ChangeAll button
GGUserInput.RegisterAction[atom: $MatchActionChange, eventProc: MatchActionChange, argType: none, ensureUnique: FALSE]; -- Flip through action types
GGUserInput.RegisterAction[atom: $MatchLevelChange, eventProc: MatchLevelChange, argType: none, ensureUnique: FALSE]; -- Flip through action types
GGUserInput.RegisterAction[atom: $MatchSearchOpChange, eventProc: MatchSearchOpChange, argType: none, ensureUnique: FALSE]; -- Flip through SearchOps
GGUserInput.RegisterAction[atom: $MatchToleranceChange, eventProc: MatchToleranceChange, argType: none, ensureUnique: FALSE]; -- Adjust match tolerance
GGUserInput.RegisterAction[atom: $MatchExact, eventProc: MatchExact, argType: none, ensureUnique: FALSE]; -- Toggle the Exact Match button
GGUserInput.RegisterAction[atom: $MatchStartOps, eventProc: Match.StartOps, argType: none, ensureUnique: FALSE]; -- Invoke a procedure to intitiate recording of To events
GGUserInput.RegisterAction[atom: $MatchEndOps, eventProc: Match.EndOps, argType: none, ensureUnique: FALSE]; -- Invoke a procedure to terminate recording of To events
GGUserInput.RegisterAction[atom: $MatchGrep, eventProc: MatchGrep.Grep, argType: rope, ensureUnique: FALSE]; -- Grep for the pattern in the From viewer
GGUserInput.RegisterAction[atom: $MatchGrepListOnly, eventProc: MatchGrep.GrepListOnly, argType: rope, ensureUnique: FALSE]; -- Grep for the pattern in the From viewer
GGUserInput.RegisterAction[atom: $MatchNextFile, eventProc: MatchGrep.NextFile, argType: none, ensureUnique: FALSE]; -- Edit the last gargoyle file found with grep
};
GetButtonHolder:
PROC [parent: Viewer, wx, wy, ww:
CARD ← 0, wh:
CARD ← entryHeight]
RETURNS [Viewer] = {
IF ww = 0 THEN ww ← parent.ww;
RETURN[Containers.Create[info: [name: "holder", parent: parent, wx: wx, wy: wy, ww: ww, wh: wh, border: FALSE, scrollable: FALSE], paint: FALSE]];
};
Implementations of Viewing Operations
CenterSel:
PUBLIC Buttons.ButtonProc = {
ggData: GGData ← NARROW[clientData];
caretPos: Point ← GGCaret.GetPoint[ggData.caret];
bBox: GGModelTypes.BoundBox ← GGScene.BoundBoxOfSelected[scene: ggData.scene, selectClass: normal];
IF bBox.null THEN BiScrollers.Align[bs: ggData.biScroller, client: [variant: coord[x: caretPos.x, y: caretPos.y]], viewer: [variant: fraction[fx: 0.5, fy: 0.5]], paint: TRUE]
ELSE BiScrollers.Align[bs: ggData.biScroller, client: [variant: coord[x: (bBox.loX+bBox.hiX)/2.0, y: (bBox.loY+bBox.hiY)/2.0]], viewer: [variant: fraction[fx: 0.5, fy: 0.5]], paint: TRUE];
};
FitSel: Buttons.ButtonProc = {
ggData: GGData ← NARROW[clientData];
tbBox, vBox, tvBox: Geom2D.Rect;
cToV: ImagerTransformation.Transformation;
bBox: GGModelTypes.BoundBox ← GGScene.BoundBoxOfSelected[scene: ggData.scene, selectClass: normal];
IF
NOT bBox.null
THEN {
cToV ← BiScrollers.GetStyle[].GetTransforms[ggData.biScroller].clientToViewer;
tbBox ← ImagerTransformation.TransformRectangle[cToV, GGBoundBox.RectangleFromBoundBox[bBox] ];
vBox ← BiScrollers.ViewportBox[ggData.biScroller];
tvBox ← ImagerTransformation.TransformRectangle[cToV, vBox];
BiScrollers.BoxScale[bs: ggData.biScroller, from: tbBox, to: tvBox];
Change scale and offsets so that client data previously in `from' covers as much as possible of `to' (from and to in viewer coords).
};
};
Handling Match Mode Buttons Presses
SetFromColumn: GGUserInput.UserInputProc = {
SetColumn[matchData.from, event.rest.first = $SetAll];
};
SetToColumn: GGUserInput.UserInputProc = {
SetColumn[matchData.to, event.rest.first = $SetAll];
};
SetColumn:
PROC [choice: MatchViewer.ChoiceData, state:
BOOL] = {
AtomButtons.SetBinaryState[choice.class, state];
AtomButtons.SetBinaryState[choice.type, state];
AtomButtons.SetBinaryState[choice.shape, state];
AtomButtons.SetBinaryState[choice.color, state];
AtomButtons.SetBinaryState[choice.fillColor, state];
AtomButtons.SetBinaryState[choice.string, state];
AtomButtons.SetBinaryState[choice.font, state];
AtomButtons.SetBinaryState[choice.fontTform, state];
AtomButtons.SetBinaryState[choice.dash, state];
AtomButtons.SetBinaryState[choice.joints, state];
AtomButtons.SetBinaryState[choice.ends, state];
AtomButtons.SetBinaryState[choice.width, state];
};
ToggleFromOrTo: GGUserInput.UserInputProc = {
FlipThreeAsUnit:
PROC = {
AtomButtons.SwitchBinaryState[matchData.to.class];
AtomButtons.SwitchBinaryState[matchData.to.type];
AtomButtons.SwitchBinaryState[matchData.to.shape];
};
SELECT event.rest.first
FROM
$FromClass => AtomButtons.SwitchBinaryState[matchData.from.class];
$ToClass => FlipThreeAsUnit[];
$FromType => AtomButtons.SwitchBinaryState[matchData.from.type];
$ToType => FlipThreeAsUnit;
$FromShape => AtomButtons.SwitchBinaryState[matchData.from.shape];
$ToShape => FlipThreeAsUnit;
$FromColor => AtomButtons.SwitchBinaryState[matchData.from.color];
$ToColor => AtomButtons.SwitchBinaryState[matchData.to.color];
$FromFillColor => AtomButtons.SwitchBinaryState[matchData.from.fillColor];
$ToFillColor => AtomButtons.SwitchBinaryState[matchData.to.fillColor];
$FromString => AtomButtons.SwitchBinaryState[matchData.from.string];
$ToString => AtomButtons.SwitchBinaryState[matchData.to.string];
$FromFont => AtomButtons.SwitchBinaryState[matchData.from.font];
$ToFont => AtomButtons.SwitchBinaryState[matchData.to.font];
$FromFontTform => AtomButtons.SwitchBinaryState[matchData.from.fontTform];
$ToFontTform => AtomButtons.SwitchBinaryState[matchData.to.fontTform];
$FromDash => AtomButtons.SwitchBinaryState[matchData.from.dash];
$ToDash => AtomButtons.SwitchBinaryState[matchData.to.dash];
$FromJoints => AtomButtons.SwitchBinaryState[matchData.from.joints];
$ToJoints => AtomButtons.SwitchBinaryState[matchData.to.joints];
$FromEnds => AtomButtons.SwitchBinaryState[matchData.from.ends];
$ToEnds => AtomButtons.SwitchBinaryState[matchData.to.ends];
$FromWidth => AtomButtons.SwitchBinaryState[matchData.from.width];
$ToWidth => AtomButtons.SwitchBinaryState[matchData.to.width];
$RotationInv => AtomButtons.SwitchBinaryState[matchData.rotationInv];
$ScaleInv => AtomButtons.SwitchBinaryState[matchData.scaleInv];
$Polarity => AtomButtons.SwitchBinaryState[matchData.polarity];
$Structure => AtomButtons.SwitchBinaryState[matchData.structure];
$MatchNodes => AtomButtons.SwitchBinaryState[matchData.matchNodes];
$ContextSensitive => AtomButtons.SwitchBinaryState[matchData.contextSensitive];
ENDCASE;
};
MatchActionChange: GGUserInput.UserInputProc = {
name: Rope.ROPE;
AtomButtons.TimeToFlipThru[LIST[$FlipForward, matchData.replaceOpButton]];
name ← matchData.replaceOpButton.flipLabel.name;
SELECT
TRUE
FROM
Rope.Equal[name, "Do Replace"] => {matchData.replaceOp ← doReplace;};
Rope.Equal[name, "Do Operations"] => {matchData.replaceOp ← doOperations;};
ENDCASE => ERROR;
};
MatchLevelChange: GGUserInput.UserInputProc = {
name: Rope.ROPE;
AtomButtons.TimeToFlipThru[LIST[$FlipForward, matchData.levelButton]];
name ← matchData.levelButton.flipLabel.name;
SELECT
TRUE
FROM
Rope.Equal[name, "Anywhere"] => matchData.matchLevel ← anywhere;
Rope.Equal[name, "Trajectory Level"] => matchData.matchLevel ← trajLevel;
Rope.Equal[name, "Slice Level"] => matchData.matchLevel ← sliceLevel;
ENDCASE => ERROR;
};
MatchSearchOpChange: GGUserInput.UserInputProc = {
name: Rope.ROPE;
AtomButtons.TimeToFlipThru[LIST[$FlipForward, matchData.searchOpButton]];
name ← matchData.searchOpButton.flipLabel.name;
SELECT
TRUE
FROM
Rope.Equal[name, "Disjunction"] => matchData.searchOp ← disjunction;
Rope.Equal[name, "Conjunction"] => matchData.searchOp ← conjunction;
ENDCASE => ERROR;
};
minMatchTol: REAL ← .02;
MatchToleranceChange: GGUserInput.UserInputProc = {
toleranceData: ToleranceData ← NARROW[GraphicsButton.GetValue[matchData.toleranceButton].buttonData];
IF AtomButtons.GetBinaryState[matchData.exactMatch]
THEN {
-- Exact button is on
ErrorFeedback["First turn Exact match off."];
RETURN;
};
SELECT event.rest.first
FROM
$ValueUp => toleranceData.current ← toleranceData.current + .1;
$InitialValue => toleranceData.current ← initialTolerance;
$ValueDown => toleranceData.current ← toleranceData.current - .1;
ENDCASE => ERROR;
toleranceData.current ← MAX[MIN[toleranceData.current, 1.0], minMatchTol];
GraphicsButton.SetButtonValueAndPaint[matchData.toleranceButton, ggData, toleranceData];
};
MatchExact: GGUserInput.UserInputProc = {
toleranceData: ToleranceData ← NARROW[GraphicsButton.GetValue[matchData.toleranceButton].buttonData];
AtomButtons.SwitchBinaryState[matchData.exactMatch];
GraphicsButton.SetButtonValueAndPaint[matchData.toleranceButton, ggData, toleranceData];
};
GetMatchTolerance:
PUBLIC
PROC
RETURNS [
REAL] = {
toleranceData: ToleranceData ← NARROW[GraphicsButton.GetValue[matchData.toleranceButton].buttonData];
IF AtomButtons.GetBinaryState[matchData.exactMatch] THEN RETURN[minMatchTol] -- Exact button is on, so it overrides tolerance value
ELSE RETURN[toleranceData.current];
};
GetContextSensitive:
PUBLIC
PROC [matchData: MatchData]
RETURNS [contextSensitive:
BOOL] = {
contextSensitive ← AtomButtons.GetBinaryState[matchData.contextSensitive];
};
Fetch Selected from Gargoyle Viewer
Fetch: GGUserInput.UserInputProc = {
Borrowed with modifications from GGMouseEventImplA (UpdateSceneForCopy)
SortNewEntities:
PROC [entityList:
LIST
OF Slice]
RETURNS [sorted:
LIST
OF Slice] = {
Sort back to front (in increasing order).
CompareProc: GGUtility.SliceCompareProc = {
[ref1: Slice, ref2: Slice] RETURNS [Basics.Comparison]
priority1, priority2: INT;
priority1 ← ref1.priority;
priority2 ← ref2.priority;
RETURN[Basics.CompareInt[priority1, priority2]];
};
sorted ← GGUtility.SortSliceList[entityList, CompareProc];
};
theirScene: Scene ← NIL;
ourScene: Scene ← ggData.scene;
feedback: FeedbackData ← ggData.feedback;
sliceDGen: SliceDescriptorGenerator;
outSeqGen: GGSelect.OutlineSequenceGenerator;
newOutline, outline: Slice;
newTraj: Traj;
newSlice: Slice;
newSlices: LIST OF Slice;
Get Scene from InputFocus' Viewer
theirData: GGData ← GetGGInputFocus[];
IF theirData = NIL THEN RETURN
ELSE theirScene ← theirData.scene;
Copy selected slices.
sliceDGen ← GGSelect.SelectedSlices[theirScene, normal];
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDGen], GGSelect.NextSliceDescriptor[sliceDGen]
UNTIL sliceD =
NIL
DO
IF sliceD.slice.class.type#$Outline
THEN {
-- outlines are handled separately, below
newSlice ← GGSlice.CopySlice[sliceD.slice]; -- make the new one
newSlice.priority ← GGScene.GetPriority[theirScene, sliceD.slice];
GGSelect.DeselectSlice[sliceD.slice, sliceD.parts, theirScene, normal];
newSlices ← CONS[newSlice, newSlices];
};
ENDLOOP;
BEGIN
Copy selected outlines, or outline parts.
outSeqGen ← GGSelect.SelectedOutlineSequences[theirScene, normal];
FOR outSeq: GGSelect.OutlineSequence ← GGSelect.NextOutlineSequences[outSeqGen], GGSelect.NextOutlineSequences[outSeqGen]
UNTIL outSeq =
NIL
DO
outline ← outSeq.outline;
IF outSeq.fenceSeq #
NIL
THEN {
IF NOT GGSequence.ContainsSomeSegment[outSeq.fenceSeq] THEN GOTO NoSegmentsSelected;
If the whole outline is selected, copy it as a whole.
IF GGSelect.IsSelectedInFull[outline, theirScene, normal]
THEN {
newOutline ← outline.class.copy[outline];
newOutline.priority ← GGScene.GetPriority[theirScene, outline];
GGSelect.DeselectEntireSlice[outline, theirScene, normal]; -- deselect the old one
newSlices ← CONS[newOutline, newSlices];
LOOP;
}
Otherwise, each piece becomes a new slice.
ELSE {
newTraj ← GGTraj.CopyTrajFromRun[outSeq.fenceSeq];
newOutline ← GGOutline.CreateOutline[newTraj, outline.class.getFillColor[outline]];
newOutline.priority ← GGScene.GetPriority[theirScene, outline];
GGSelect.DeselectSequence[outSeq.fenceSeq, theirScene, normal];
newSlices ← CONS[newOutline, newSlices];
};
};
FOR holeSeq: Sequence ← GGSequence.NextSequence[outSeq.holeSeqs], GGSequence.NextSequence[outSeq.holeSeqs]
UNTIL holeSeq =
NIL
DO
IF NOT GGSequence.ContainsSomeSegment[holeSeq] THEN GOTO NoSegmentsSelected;
newTraj ← GGTraj.CopyTrajFromRun[holeSeq];
newOutline ← GGOutline.CreateOutline[newTraj, outline.class.getFillColor[outline]];
newOutline.priority ← GGScene.GetPriority[theirScene, outline];
GGSelect.DeselectSequence[holeSeq, theirScene, normal];
newSlices ← CONS[newOutline, newSlices];
ENDLOOP;
ENDLOOP;
Sort the new slices
newSlices ← SortNewEntities[newSlices];
Add all the new slices to ourScene.
FOR sliceList:
LIST
OF Slice ← newSlices, sliceList.rest
UNTIL sliceList =
NIL
DO
allParts: SliceDescriptor;
GGScene.AddSlice[ourScene, sliceList.first, -1];
allParts ← sliceList.first.class.newParts[sliceList.first, NIL, slice];
GGSelect.SelectSlice[allParts, ourScene, normal];
ENDLOOP;
GGAlign.UpdateBagsForNewSlices[newSlices, ggData];
ggData.refresh.startBoundBox ← GGScene.BoundBoxOfSelected[ggData.scene, normal];
GGWindow.RestoreScreenAndInvariants[paintAction: $FinishedDragging, ggData: ggData, remake: sceneBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
EXITS
NoSegmentsSelected => {
Feedback.AppendHerald[feedback, ". . . Cannot Copy Joints or CPs", begin];
Feedback.Blink[feedback];
GGSelect.DeselectAll[theirScene, normal];
We are about to self-abort. We deselect all so that we don't delete any of the original geometry (see AbortCopyAndDrag above).
};
END;
};
Building the MatchTool Menus
column0: CARD = 0; -- x coordinate of Characteristics column
column1: CARD = 135; -- x coordinate of From column
column2: CARD = 195; -- x coordinate of To column
column3: CARD = 245; -- x coordinate of Search button line
upperMargin: CARD = 8; -- Vertical space between actionAreas and Characteristics line
lowerMargin: CARD = 5; -- Extra space between Characteristics description line and FromTo entries
fromBoxOffset: CARD = 14; -- column1 + fromBoxOffset = x location of From boxes
toBoxOffset: CARD = 4; -- column2 + toBoxOffset = x location of To boxes
matchData: MatchData ← NewMatchDataObj[];
NewMatchDataObj:
PUBLIC
PROC
RETURNS [matchData: MatchData] = {
matchData ← NEW[MatchDataObj];
matchData.from ← NEW[ChoiceDataObj];
matchData.to ← NEW[ChoiceDataObj];
};
InitControlPanel:
PROC [parent: Viewer, startHeight:
CARD] = {
MakeFromToEntry:
PROC [string: Rope.
ROPE, parent: Viewer, height:
CARD, fromAction:
LIST
OF
REF
ANY, toAction:
LIST
OF
REF
ANY]
RETURNS [fromState, toState: AtomButtons.TwoState, newHeight:
CARD] = {
[] ← Labels.Create[[name: string, parent: parent, wx: column0, wy: height, border: FALSE]];
[] ← AtomButtons.BuildButtonLine[container: parent, x: column1 + fromBoxOffset, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries:
LIST [
[twoState[" ", fromAction, FALSE, FillGlobalTwoState]]]];
fromState ← globalTwoState;
[] ← AtomButtons.BuildButtonLine[container: parent, x: column2 + toBoxOffset, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries:
LIST [
[twoState[" ", toAction, FALSE, FillGlobalTwoState]]]];
toState ← globalTwoState;
newHeight ← height + entryHeight + entryVSpace;
};
CHARACTERISTICS FROM/TO control section
height: CARD ← startHeight;
[] ← Labels.Create[[name: "CHARACTERISTICS", parent: parent, wx: column0, wy: height, border: FALSE]];
[] ← AtomButtons.BuildButtonLine[container: parent, x: column1, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries:
LIST[
[button["FROM", LIST[LIST[$MatchSetFrom, $ClearAll], LIST[$MatchSetFrom, $ClearAll], LIST[$MatchSetFrom, $SetAll]]]]]];
[] ← AtomButtons.BuildButtonLine[container: parent, x: column2, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries:
LIST[
[button["TO", LIST[LIST[$MatchSetTo, $ClearAll], LIST[$MatchSetTo, $ClearAll], LIST[$MatchSetTo, $SetAll]]]]]];
[] ← Labels.Create[[name: "FROM", parent: parent, wx: column1, wy: height, border: FALSE]];
[] ← Labels.Create[[name: "TO", parent: parent, wx: column2, wy: height, border: FALSE]];
height ← height + entryHeight + lowerMargin;
[matchData.from.class, matchData.to.class, height] ← MakeFromToEntry["Object class", parent, height, LIST[$MatchToggle, $FromClass], LIST[$MatchToggle, $ToClass]];
[matchData.from.type, matchData.to.type, height] ← MakeFromToEntry["Object type", parent, height, LIST[$MatchToggle, $FromType], LIST[$MatchToggle, $ToType]];
[matchData.from.shape, matchData.to.shape, height] ← MakeFromToEntry["Object shape", parent, height, LIST[$MatchToggle, $FromShape], LIST[$MatchToggle, $ToShape]];
[matchData.from.color, matchData.to.color, height] ← MakeFromToEntry["Object color", parent, height, LIST[$MatchToggle, $FromColor], LIST[$MatchToggle, $ToColor]];
[matchData.from.fillColor, matchData.to.fillColor, height] ← MakeFromToEntry["Object fill color", parent, height, LIST[$MatchToggle, $FromFillColor], LIST[$MatchToggle, $ToFillColor]];
[matchData.from.string, matchData.to.string, height] ← MakeFromToEntry["Text string", parent, height, LIST[$MatchToggle, $FromString], LIST[$MatchToggle, $ToString]];
[matchData.from.font, matchData.to.font, height] ← MakeFromToEntry["Text font", parent, height, LIST[$MatchToggle, $FromFont], LIST[$MatchToggle, $ToFont]];
[matchData.from.fontTform, matchData.to.fontTform, height] ← MakeFromToEntry["Text font & transform", parent, height, LIST[$MatchToggle, $FromFontTform], LIST[$MatchToggle, $ToFontTform]];
[matchData.from.dash, matchData.to.dash, height] ← MakeFromToEntry["Line dashes", parent, height, LIST[$MatchToggle, $FromDash], LIST[$MatchToggle, $ToDash]];
[matchData.from.joints, matchData.to.joints, height] ← MakeFromToEntry["Line joints", parent, height, LIST[$MatchToggle, $FromJoints], LIST[$MatchToggle, $ToJoints]];
[matchData.from.ends, matchData.to.ends, height] ← MakeFromToEntry["Line ends", parent, height, LIST[$MatchToggle, $FromEnds], LIST[$MatchToggle, $ToEnds]];
[matchData.from.width, matchData.to.width, height] ← MakeFromToEntry["Line width", parent, height, LIST[$MatchToggle, $FromWidth], LIST[$MatchToggle, $ToWidth]];
InitRightControlPanel[parent, startHeight];
};
InitRightControlPanel:
PROC [parent: Viewer, startHeight:
CARD] = {
SEARCH/YES/NO/CHANGEALL, etc...
nextX: CARD;
height: CARD ← startHeight - buttonAlign;
Yes and ChangeAll must be unqueued so they don't cause SlackProcess monitor deadlock on doOps playback
[] ← AtomButtons.BuildButtonLine [parent, column3, height, parent.data, GGUserInput.UnQueuedEventNotify,
LIST[
[button["Search", LIST[LIST[$MatchSearch, $SearchBelow], LIST[$MatchSearch, $SearchFromTop], LIST[$MatchSearch, $SearchAbove]]]],
[button["Yes", LIST[LIST[$MatchYes, $SearchBelow], LIST[$MatchYes, $SearchFromTop], LIST[$MatchYes, $SearchAbove]]]],
[button["No", LIST[LIST[$MatchSearch, $SearchBelow], LIST[$MatchSearch, $SearchFromTop], LIST[$MatchSearch, $SearchAbove]]]],
[button["ChangeAll", LIST[LIST[$MatchChangeAll, $SearchBelow], LIST[$MatchChangeAll, $SearchFromTop], LIST[$MatchChangeAll, $SearchAbove]]]]]];
height ← height + entryHeight + entryVSpace;
matchData.replaceOpButton ← AtomButtons.BuildEnumTypeSelection[viewer: parent, x: column3, y: height, maxWidth: 0, clientData: parent.data, handleProc: GGUserInput.EventNotify, title: "Action:", default: "Do Replace", borderOnButtons: TRUE, style: flipThru, allInOneRow: TRUE, buttonNames: LIST["Do Replace", "Do Operations"], atom: $MatchActionChange];
matchData.replaceOp ← doReplace;
height ← height + entryHeight + entryVSpace;
matchData.levelButton ← AtomButtons.BuildEnumTypeSelection[viewer: parent, x: column3, y: height, maxWidth: 0, clientData: parent.data, handleProc: GGUserInput.EventNotify, title: "Match:", default: "Slice Level", borderOnButtons: TRUE, style: flipThru, allInOneRow: TRUE, buttonNames: LIST["Anywhere", "Trajectory Level", "Slice Level"], atom: $MatchLevelChange];
matchData.matchLevel ← trajLevel;
height ← height + entryHeight + entryVSpace;
matchData.searchOpButton ← AtomButtons.BuildEnumTypeSelection[viewer: parent, x: column3, y: height, maxWidth: 0, clientData: parent.data, handleProc: GGUserInput.EventNotify, title: "SearchOp:", default: "Disjunction", borderOnButtons: TRUE, style: flipThru, allInOneRow: TRUE, buttonNames: LIST["Disjunction", "Conjunction"], atom: $MatchSearchOpChange];
matchData.searchOp ← disjunction;
height ← height + entryHeight + entryVSpace;
nextX ← AtomButtons.BuildButtonLine[container: parent, x: column3, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries:
LIST[
[twoState["Exact", LIST[$MatchExact], TRUE, FillExact]],
[label["Tolerance:"]]]];
nextX ← GraphicsButton.BuildGraphicsButton[container: parent, x: nextX, y: height, w: 60, h: entryHeight, clientData: parent.data, choices: LIST[[LIST[$MatchToleranceChange, $ValueDown]], [LIST[$MatchToleranceChange, $InitialValue]], [LIST[$MatchToleranceChange, $ValueUp]]], handleProc: GGUserInput.EventNotify, repaintProc: ToleranceButtonRepaint, buttonData: NEW[ToleranceDataObj], updateProc: ToleranceButtonUpdate];
height ← height + entryHeight + entryVSpace;
nextX ← AtomButtons.BuildButtonLine[container: parent, x: column3, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries:
LIST[
[twoState["RotationInv", LIST[$MatchToggle, $RotationInv], FALSE, FillRotationInv]],
[twoState["ScaleInv", LIST[$MatchToggle, $ScaleInv], FALSE, FillScaleInv]]]];
height ← height + entryHeight + entryVSpace;
nextX ← AtomButtons.BuildButtonLine[container: parent, x: column3, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries:
LIST[
[twoState["Polarity", LIST[$MatchToggle, $Polarity], TRUE, FillPolarity]],
[twoState["ContextSensitive", LIST[$MatchToggle, $ContextSensitive], FALSE, FillContextSensitive]]]];
height ← height + entryHeight + entryVSpace;
nextX ← AtomButtons.BuildButtonLine[container: parent, x: column3, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries:
LIST[
[button["StartOps", LIST[LIST[$MatchStartOps], LIST[$MatchStartOps], LIST[$MatchStartOps]]]],
[button["EndOps", LIST[LIST[$MatchEndOps], LIST[$MatchEndOps], LIST[$MatchEndOps]]]]]];
height ← height + entryHeight + entryVSpace;
nextX ← AtomButtons.BuildButtonLine[container: parent, x: column3, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries:
LIST [
[button["Grep", LIST[LIST[$MatchGrep], LIST[$MatchGrep], LIST[$MatchGrepListOnly]]]],
[button["NextFile", LIST[LIST[$MatchNextFile], LIST[$MatchNextFile], LIST[$MatchNextFile]]]]]];
};
Various InitTwoStateProcs used in creating TwoStateButtons during control panel initialization
globalTwoState: AtomButtons.TwoState;
FillGlobalTwoState: AtomButtons.InitTwoStateProc = {globalTwoState ← twoState;};
FillExact: AtomButtons.InitTwoStateProc = {matchData.exactMatch ← twoState;};
FillRotationInv: AtomButtons.InitTwoStateProc = {matchData.rotationInv ← twoState;};
FillScaleInv: AtomButtons.InitTwoStateProc = {matchData.scaleInv ← twoState;};
FillPolarity: AtomButtons.InitTwoStateProc = {matchData.polarity ← twoState;};
FillStructure: AtomButtons.InitTwoStateProc = {matchData.structure ← twoState;};
FillMatchNodes: AtomButtons.InitTwoStateProc = {matchData.matchNodes ← twoState;};
FillContextSensitive: AtomButtons.InitTwoStateProc = {matchData.contextSensitive ← twoState;};
Handling Presses Of The Tolerance Button.
initialTolerance: REAL = 0.2;
ToleranceData: TYPE = REF ToleranceDataObj;
ToleranceDataObj:
TYPE =
RECORD [
current: REAL ← initialTolerance
];
ToleranceButtonRepaint:
PROC [dc: Imager.Context, clientData:
REF
ANY, buttonData:
REF
ANY, button: Viewer] = {
Essentially GGMenusImpl.GravityExtentRepain
caretPoint: Point;
tol: REAL ← GetMatchTolerance[];
extent: REAL ← tol * button.ww;
Imager.TranslateT[dc, [0, button.wh/2.0]];
caretPoint ← [extent, 0.0];
GGShapes.DrawCaret[dc, caretPoint, [0, -1]];
};
ToleranceButtonUpdate: AtomButtonsTypes.UpdateGraphicsButtonProc = {
matchData.toleranceButton ← stateInfo;
};
Miscellaneous
ErrorFeedback:
PUBLIC PROC [message: Rope.
ROPE] = {
ggData: GGData ← GetToData[];
Feedback.PutFHerald[ggData.feedback, oneLiner, message];
Feedback.Blink[ggData.feedback];
};
GetGGInputFocus:
PUBLIC PROC
RETURNS [theirData: GGData ←
NIL] = {
inputFocus: InputFocus.Focus ← InputFocus.GetInputFocus[];
theirViewer: Viewer;
IF inputFocus = NIL OR inputFocus.owner = NIL THEN RETURN;
theirViewer ← inputFocus.owner;
IF theirViewer.class.flavor # $ActionArea THEN ErrorFeedback["Place input focus in a Gargoyle viewer."]
ELSE theirData ← NARROW[BiScrollers.ClientDataOf[NARROW[theirViewer.data, BiScrollers.BiScroller]], GGData];
};
GetMatchData:
PUBLIC
PROC
RETURNS [MatchData] = {
RETURN [matchData];
};
FindAGargoyleViewer:
PROC
RETURNS [ggData: GGData ←
NIL] = {
IsGargoyleViewer: ViewerOps.EnumProc = {
IF Rope.Equal["Gargoyle", Rope.Substr[v.name, 0, 8]]
AND
ISTYPE[v.data, GGData]
THEN {
ggData ← NARROW[v.data];
RETURN[FALSE]; -- terminate enumeration
}
ELSE RETURN[TRUE]; -- continue enumerating
};
ViewerOps.EnumerateViewers[IsGargoyleViewer]; -- look for a viewer whose name starts with "Gargoyle"
};
Actions to perform on load.
MatchCommand: Commander.CommandProc = {
matchData.commander ← cmd;
matchData.hostData ← FindAGargoyleViewer[]; -- so we can leach on its menu and use its SlackProcess
IF matchData.hostData = NIL THEN IO.PutRope[cmd.err, "Invoke a gargoyle viewer first.\n"]
ELSE InitViewers[];
};
matchToolIcon: Icons.IconFlavor ← Icons.NewIconFromFile["Gargoyle.icons", 4]; -- shares Icon file with Gargoyle
RegisterMatchActions[];
Commander.Register["MatchTool", MatchCommand];
END.