<> <> <> DIRECTORY Convert USING [RopeFromInt], Graph USING [CaretIndex, ColorIndex, Entity, EntityGroupList, EntityHashSize, EntityRec, EntityGroup, EntityGroupRec, EntityList, FontIndex, GraphHandle, HashIndex, NestedEntitiesRec, NumberOfColors, NumberOfFonts, PaintAction, ROPE, Text, TextRec, Texts, UnitLineWidth, ValueList, Viewer, XY], GraphCarets USING [Move, TurnOff, TurnOn], GraphConvert USING [RopeOfSlope, VLFromSDL, VLsFromValues], GraphCleanUp USING [CleanUpVL], GraphPrivate USING [AddEntityButton, AddGroupButton, AddTextButton, GraphAtomProc, PaintAll, PaintEntity, PaintGrids, PaintTarget, PaintText, UpdateEntityButton, UpdateTextButton, systemColor], GraphUtil USING [Almost, AppendEGL, AppendEntityList, BlinkMsg, EntityFromId, GetIntField, GetRealField, InitSegAll, InitSegEnd, LengthOfVL, NewEntityId, NewGroupId, NewTextId, RaiseError, RealToScreenI, SetColor, SetIntField, SetSegments, TextFromId, UpdateSegAll, UpdateSegEnd], Imager USING [Error], ImagerFont USING [Find, Font, Scale], IO USING [int, PutFR], Rope USING [Cat, Concat], VFonts USING [EstablishFont], ViewerTools USING [GetContents, SetContents]; GraphUpdate: CEDAR PROGRAM IMPORTS Convert, GraphCarets, GraphCleanUp, GraphConvert, GraphPrivate, GraphUtil, Imager, ImagerFont, IO, Rope, VFonts, ViewerTools EXPORTS GraphPrivate = { OPEN Graph, GraphPrivate, GraphUtil; Update: PUBLIC GraphAtomProc = { <> <> IF handle # NIL THEN IF handle.controller # NIL THEN { OPEN handle; msg: ROPE; i: INT; -- r: REAL; SELECT atom FROM $Divisions => UpdateDivisions[handle]; $Bounds => UpdateBounds[handle]; $Carets => UpdateCarets[handle]; $Targets => UpdateTargets[handle]; $Grids => UpdateGrids[handle]; $Color => { [msg, i] _ GetIntField[controller.colorIndex]; IF msg = NIL THEN IF i IN ColorIndex THEN UpdateColor[handle, i] ELSE BlinkMsg[Rope.Cat[ "Index must be in [0..", Convert.RopeFromInt[NumberOfColors], ")"]]; }; $Font => { [msg, i]_ GetIntField[controller.fontIndex]; IF msg = NIL THEN IF i IN FontIndex THEN UpdateFont[handle, i] ELSE BlinkMsg[Rope.Cat[ "Index must be in [0..", Convert.RopeFromInt[NumberOfFonts], ")"]]; }; $Text => { [msg, i] _ GetIntField[controller.textId]; IF msg = NIL THEN { text: Text _ TextFromId[handle.allTexts, i]; UpdateText[handle, text, i]; -- text = nil => add it. }; }; $Entity, $EntityAndValues => { [msg, i] _ GetIntField[controller.entityId]; IF msg = NIL THEN { entity: Entity _ EntityFromId[entityHash[i MOD EntityHashSize], i]; UpdateEntity[handle, entity, i, atom = $EntityAndValues]; }; }; $XYValues => { msg: ROPE; i: INT; [msg, i] _ GetIntField[controller.idOfy]; IF msg = NIL THEN { entity: Entity _ EntityFromId[entityHash[i MOD EntityHashSize], i]; IF entity = NIL THEN BlinkMsg["No curve with this id. To add an entity, please use the Curve entry on the Spec menu."] ELSE UpdateXYValues[handle, entity]; }; }; <<$CurveGroup => {>> <<[msg, i] _ GetIntField[controller.entityId];>> <> <> <> <<};>> <<};>> <<$Crosssection => {>> <<[msg, r] _ GetRealField[controller.entityId];>> <> <<};>> ENDCASE => RaiseError[$UnknownAtom, "in Update"]; }; }; -- Update UpdateDivisions: PROC[handle: GraphHandle] = { OPEN handle; graph.auto[divisions] _ controller.auto[divisions]; graph.division[x] _ UpdateFromIntField[controller.divX, graph.division[x]]; graph.division[y] _ UpdateFromIntField[controller.divY, graph.division[x]]; IF graph.division[x] < 1 OR graph.division[y] < 1 THEN BlinkMsg["x or y divisions < 1."] ELSE IF chart.viewer # NIL THEN PaintAll[handle, TRUE]; -- paint everything. }; -- SetDivisionFields UpdateBounds: PROC[handle: GraphHandle] = { OPEN handle; graph.auto[bounds] _ controller.auto[bounds]; graph.bounds _ [ xmin: UpdateFromRealField[controller.xmin, graph.bounds.xmin], ymin: UpdateFromRealField[controller.ymin, graph.bounds.ymin], xmax: UpdateFromRealField[controller.xmax, graph.bounds.xmax], ymax: UpdateFromRealField[controller.ymax, graph.bounds.ymax] ]; IF NOT graph.auto[bounds] AND (graph.bounds.xmin >= graph.bounds.xmax OR graph.bounds.ymin >= graph.bounds.ymax) THEN BlinkMsg["Illegal bounds. (xmin >= xmax or ymin >= ymax.)"] ELSE IF chart.viewer # NIL THEN PaintAll[handle, TRUE]; }; -- UpdateBounds UpdateCarets: PROC[handle: GraphHandle] = { OPEN handle; UpdateCaret: PROC[index: CaretIndex] = { vx: Viewer _ controller.caretPlace[index][x]; vy: Viewer _ controller.caretPlace[index][y]; rx: REAL _ UpdateFromRealField[vx, graph.caret[index].place.x]; ry: REAL _ UpdateFromRealField[vy, graph.caret[index].place.y]; sx: INTEGER _ RealToScreenI[handle, rx, x]; sy: INTEGER _ RealToScreenI[handle, ry, y]; graph.caret[index]^ _ [on: controller.caretOn[index], place: [rx, ry]]; IF chart.viewer # NIL THEN { GraphCarets.Move[handle, sx, sy, index]; IF graph.caret[index].on THEN GraphCarets.TurnOn[handle, index] ELSE GraphCarets.TurnOff[handle, index]; <> }; }; -- UpdateCaret FOR i: CaretIndex IN CaretIndex DO UpdateCaret[primary] ENDLOOP; ViewerTools.SetContents[controller.slope, IF (graph.showSlope _ controller.slopeOn) THEN GraphConvert.RopeOfSlope[graph.caret[primary].place, graph.caret[secondary].place] ELSE NIL]; }; -- UpdateCarets UpdateTargets: PROC[handle: GraphHandle] = { OPEN handle; UpdateTarget: PROC[xy: XY] = { IF chart.viewer # NIL AND graph.target[xy].on THEN PaintTarget[handle, erase, xy]; graph.target[xy].value _ UpdateFromRealField[ controller.targetValue[xy], graph.target[xy].value]; graph.target[xy].width _ UpdateFromRealField[ controller.targetWidth[xy], graph.target[xy].width]; graph.target[xy].colorIndex _ UpdateFromIntField[ controller.targetColor[xy], graph.target[xy].colorIndex]; IF graph.target[xy].colorIndex NOT IN ColorIndex THEN { graph.target[xy].colorIndex _ 13; BlinkMsg["Color index out of range. Default is used."]; }; IF (graph.target[xy].on _ controller.targetOn[xy]) THEN IF chart.viewer # NIL THEN PaintTarget[handle, paint, xy]; }; UpdateTarget[x]; UpdateTarget[y]; }; -- UpdateTargets UpdateGrids: PROC[handle: GraphHandle] = { OPEN handle; UpdateGrid: PROC[wasOn, isOn: BOOL, xy: XY] = { IF wasOn # isOn THEN { IF chart.viewer # NIL THEN { IF wasOn THEN PaintGrids[handle, erase, xy]; PaintGrids[handle, paint, xy]; -- paint grids or ticks. }; graph.grids[xy] _ isOn; }; }; UpdateGrid[graph.grids[x], controller.gridOn[x], x]; UpdateGrid[graph.grids[y], controller.gridOn[y], y]; }; -- UpdateGrids UpdateColor: PROC[handle: GraphHandle, index: ColorIndex] = { OPEN handle; GetRGB: PROC[v: Viewer, r: REAL] RETURNS [rgb: REAL] = { rgb _ UpdateFromRealField[v, r]; IF rgb > 1.0 OR rgb < 0.0 THEN { rgb _ MAX[0.0, MIN[1.0, rgb]]; BlinkMsg["color value limited by [0..1]."]; }; }; graph.color[index].R _ GetRGB[controller.red, graph.color[index].R]; graph.color[index].G _ GetRGB[controller.green, graph.color[index].G]; graph.color[index].B _ GetRGB[controller.blue, graph.color[index].B]; systemColor[index] _ SetColor[index, graph.color[index]]; }; -- UpdateColor UpdateFont: PROC[handle: GraphHandle, index: FontIndex] = { OPEN handle; PaintTextsOfThisFont: PROC [action: PaintAction] = { IF chart.viewer # NIL THEN FOR ts: Texts _ graph.texts, ts.rest UNTIL ts = NIL DO text: Text _ ts.first; IF text.fontIndex = index THEN PaintText[handle, action, text]; ENDLOOP; }; -- PaintTextsOfThisFont msg: ROPE; vFontSize: INT; pFontScale: REAL; vFont, pFont: ImagerFont.Font _ NIL; fontFamily: ROPE _ ViewerTools.GetContents[controller.fontFamily]; IF fontFamily = NIL THEN {BlinkMsg["Specify font family."]; RETURN}; [msg, vFontSize] _ GetIntField[controller.vFontSize]; IF msg # NIL THEN RETURN; [msg, pFontScale] _ GetRealField[controller.pFontScale]; IF msg # NIL THEN RETURN; vFont _ VFonts.EstablishFont[fontFamily, vFontSize, controller.boldOn, controller.italicOn, TRUE]; -- true means default on failure. pFont _ ImagerFont.Scale[ ImagerFont.Find[Rope.Cat[ "Xerox/PressFonts/", fontFamily, Rope.Concat[IF controller.boldOn THEN "-b" ELSE "-m", IF controller.italicOn THEN "ir" ELSE "rr"]]], pFontScale ! Imager.Error => {pFont _ NIL; CONTINUE}]; IF pFont = NIL THEN BlinkMsg["Can't find font."] ELSE { PaintTextsOfThisFont[erase]; graph.font[index] _ [fontFamily, controller.boldOn, controller.italicOn, vFontSize, pFontScale]; imagerFonts[screen][index] _ vFont; imagerFonts[interpress][index] _ pFont; PaintTextsOfThisFont[paint]; }; }; -- UpdateFont UpdateText: PROC[handle: GraphHandle, text: Text, id: INT] = { OPEN handle; rope: ROPE _ ViewerTools.GetContents[controller.textContent]; msg: ROPE _ NIL; add: BOOL _ text = NIL; int: INT; GetTextPlace: PROC [v: Viewer, original: REAL] RETURNS [r: REAL] = { r _ UpdateFromRealField[v, original]; IF r > 3.0 OR r < - 3.0 THEN { r _ MAX[-3.0, MIN[3.0, r]]; BlinkMsg["Text coordinates limited by [-3..3]"]; }; }; -- GetTextPlace IF rope = NIL THEN {BlinkMsg["name field is empty."]; RETURN}; IF add THEN text _ NEW[TextRec _ []] ELSE IF chart.viewer # NIL THEN PaintText[handle, erase, text]; int _ UpdateFromIntField[controller.textFont, text.fontIndex]; IF int NOT IN FontIndex THEN msg _ "Illegal font index." ELSE text.fontIndex _ int; int _ UpdateFromIntField[controller.textColor, text.colorIndex]; IF int NOT IN ColorIndex THEN msg _ msg.Cat[" Illegal color index."] ELSE text.colorIndex _ int; IF msg # NIL THEN {BlinkMsg[msg]; text _ NIL; RETURN}; text^ _ [ text: rope, place: [GetTextPlace[controller.textPlaceX, text.place.x], GetTextPlace[controller.textPlaceY, text.place.y]], fontIndex: text.fontIndex, colorIndex: text.colorIndex, rotation: UpdateFromRealField[controller.textRotation, text.rotation], justifX: controller.justifX, justifY: controller.justifY, id: IF add THEN NewTextId[handle, id] ELSE id ]; IF add THEN SetIntField[controller.textId, text.id]; IF chart.viewer # NIL THEN PaintText[handle, paint, text]; IF add THEN { handle.allTexts _ CONS[text, handle.allTexts]; graph.texts _ CONS[text, graph.texts]; AddTextButton[handle, text]; } ELSE UpdateTextButton[handle, text]; }; -- UpdateText <> <> <> <<[vList, last, length, pos] _ GraphConvert.ValueListFromRope[>> <> <> <> <> <> <> <<"numbers in the values field."]];>> <> <> <> <<}; -- UpdateX>> UpdateEntity: PROC [handle: GraphHandle, entity: Entity, id: INT, valuesToo: BOOL _ FALSE] = { OPEN handle; <> <> <> <> <> <> <> <> <> <> <> <> msg: ROPE _ NIL; index: INT _ 0; add: BOOL _ entity = NIL; plotted: BOOL _ IF add THEN FALSE ELSE (EntityFromId[graph.entityList, entity.id] # NIL); plotExits: BOOL _ chart.viewer # NIL; vlx, vly, lastX, lastY: ValueList; lenX, lenY: INT; group: EntityGroup _ NIL; <> <> <<[msg, groupId] _ GetIntField[controller.entityGroupId, "group id"];>> <> <> <> <> <> <<};>> <<[vList, length, pos] _ GraphConvert.ValueListFromRope[>> <> <> <> <> <> <> <<"numbers in the values field."]];>> <> <> <<1. check color index>> index _ UpdateFromIntField[controller.entityColor, IF add THEN 0 ELSE entity.colorIndex]; IF index NOT IN ColorIndex THEN {BlinkMsg[" Illegal color index."]; RETURN}; <<2. enforce adding entity thru spec menu>> IF add AND NOT valuesToo THEN { BlinkMsg["Click with ctrl-shift at the Curve entry, if you really want to add a new curve."]; RETURN; }; <<3. check values>> IF valuesToo THEN { [vlx, vly, lastX, lastY, msg] _ GraphConvert.VLsFromValues[handle]; IF msg = NIL THEN { lenX _ LengthOfVL[vlx]; lenY _ LengthOfVL[vly]; IF lenX # lenY THEN msg _ IO.PutFR[ "number of x values = %g, but number of y values = %g.", IO.int[lenX], IO.int[lenY]] ELSE IF NOT add THEN IF lenX # entity.group.length THEN msg _ IO.PutFR[ "There should be %g values on this curve, but only %g are specified.", IO.int[entity.group.length], IO.int[lenX]]; }; IF msg # NIL THEN {BlinkMsg[msg]; RETURN} }; IF add THEN { -- allocate entity (and perhaps its group too). iHash: HashIndex; FOR egl: EntityGroupList _ entityGroupList, egl.rest UNTIL egl = NIL DO eg: EntityGroup _ egl.first; IF AlmostSameVL[vlx, IF eg.x.segments = NIL THEN eg.x.oldValues ELSE GraphConvert.VLFromSDL[eg.x.segments]] THEN { group _ eg; EXIT}; ENDLOOP; IF group = NIL THEN { -- create a new group, add it to entityGroupList, and init x fields. group _ NEW[EntityGroupRec _ [ x: NEW[EntityRec _ [ name: "X", oldValues: vlx, lastValue: lastX, id: NewEntityId[handle, 0] ]], ys: NEW[NestedEntitiesRec _ []], id: NewGroupId[handle, 0], length: lenX ]]; <> <> group.ys.comment _ Convert.RopeFromInt[group.id]; entityGroupList _ AppendEGL[entityGroupList, CONS[group, NIL]]; InitSegEnd[group.x]; AddGroupButton[handle, group]; }; entity _ NEW[EntityRec _ [ name: ViewerTools.GetContents[controller.entityName], comment: ViewerTools.GetContents[controller.entityCmt], colorIndex: index, mark: controller.mark, width: UpdateFromRealField[controller.entityWidth, entity.width], oldValues: vly, group: group, parent: group.ys, id: NewEntityId[handle, id], lastValue: lastY ]]; InitSegAll[entity]; graph.entityList _ AppendEntityList[graph.entityList, CONS[entity, NIL]]; group.ys.entityList _ AppendEntityList[group.ys.entityList, CONS[entity, NIL]]; iHash _ entity.id MOD EntityHashSize; entityHash[iHash] _ CONS[entity, entityHash[iHash]]; SetIntField[controller.entityId, entity.id]; SetIntField[controller.idOfy, entity.id]; AddEntityButton[handle, entity]; PaintEntity[handle, paint, entity, TRUE]; } ELSE { -- not adding a new entity sameX, sameY: BOOL; IF plotExits AND plotted THEN PaintEntity[handle, erase, entity, TRUE]; entity^ _ [ name: ViewerTools.GetContents[controller.entityName], comment: ViewerTools.GetContents[controller.entityCmt], colorIndex: index, mark: controller.mark, width: UpdateFromRealField[controller.entityWidth, entity.width], oldValues: entity.oldValues, group: entity.group, parent: entity.parent, segments: entity.segments, id: entity.id ]; UpdateEntityButton[handle, entity]; IF NOT plotted THEN { IF entity.group.x.segments = NIL THEN InitSegEnd[entity.group.x]; InitSegAll[entity]; graph.entityList _ AppendEntityList[graph.entityList, CONS[entity, NIL]]; }; IF valuesToo THEN { sameX _ AlmostSameVL[vlx, GraphConvert.VLFromSDL[entity.group.x.segments]]; sameY _ AlmostSameVL[vly, GraphConvert.VLFromSDL[entity.segments]]; } ELSE sameX _ sameY _ TRUE; IF sameX THEN { IF NOT sameY THEN UpdateSegAll[entity, vly]; IF plotExits THEN PaintEntity[handle, paint, entity, TRUE]; } ELSE { -- x changed, whether same y or not. UpdateSegEnd[entity.group.x, vlx]; FOR el: EntityList _ handle.graph.entityList, el.rest UNTIL el = NIL DO SetSegments[el.first]; ENDLOOP; IF plotExits THEN PaintAll[handle]; }; }; IF valuesToo THEN { vlx _ GraphCleanUp.CleanUpVL[vlx]; vly _ GraphCleanUp.CleanUpVL[vly]; lastX _ GraphCleanUp.CleanUpVL[lastX]; lastY _ GraphCleanUp.CleanUpVL[lastY]; }; }; -- UpdateEntity UpdateXYValues: PROC [handle: GraphHandle, entity: Entity] = { xvl, yvl, lastX, lastY: ValueList; msg: ROPE; [xvl, yvl, lastX, lastY, msg] _ GraphConvert.VLsFromValues[handle]; IF msg # NIL THEN BlinkMsg[msg] ELSE { lenX: INT _ LengthOfVL[xvl]; lenY: INT _ LengthOfVL[yvl]; IF lenX # lenY THEN BlinkMsg[IO.PutFR[ "number of x values = %g, but number of y values = %g.", IO.int[lenX], IO.int[lenY]]] ELSE IF lenX # entity.group.length THEN BlinkMsg[IO.PutFR[ "number of values should be %g but there are only %g.", IO.int[entity.group.length], IO.int[lenX]]] ELSE { OPEN handle; -- length of x, y, and group x are the same. sameX, sameY: BOOL; plotExits: BOOL _ chart.viewer # NIL; plotted: BOOL _ EntityFromId[graph.entityList, entity.id] # NIL; IF NOT plotted THEN { IF entity.group.x.segments = NIL THEN InitSegEnd[entity.group.x]; InitSegAll[entity]; graph.entityList _ AppendEntityList[graph.entityList, CONS[entity, NIL]]; }; sameX _ AlmostSameVL[xvl, GraphConvert.VLFromSDL[entity.group.x.segments]]; sameY _ AlmostSameVL[yvl, GraphConvert.VLFromSDL[entity.segments]]; IF sameX THEN { IF NOT sameY THEN { IF plotExits AND plotted THEN PaintEntity[handle, erase, entity, TRUE]; UpdateSegAll[entity, yvl]; }; } ELSE { -- x changed, whether same y or not. UpdateSegEnd[entity.group.x, xvl]; FOR el: EntityList _ handle.graph.entityList, el.rest UNTIL el = NIL DO SetSegments[el.first]; ENDLOOP; }; IF plotExits THEN { IF sameX THEN {IF NOT sameY THEN PaintEntity[handle, paint, entity, TRUE]} ELSE PaintAll[handle]; }; }; }; xvl _ GraphCleanUp.CleanUpVL[xvl]; yvl _ GraphCleanUp.CleanUpVL[yvl]; lastX _ GraphCleanUp.CleanUpVL[lastX]; lastY _ GraphCleanUp.CleanUpVL[lastY]; }; -- UpdateXYValues AlmostSameVL: PROC [vla, vlb: ValueList] RETURNS [BOOL] = { va: ValueList _ vla; vb: ValueList _ vlb; DO IF va = NIL THEN { IF vb = NIL THEN RETURN[TRUE] ELSE RETURN[FALSE]; } ELSE IF vb = NIL THEN RETURN[FALSE]; IF NOT Almost[va.first, vb.first] THEN RETURN[FALSE]; va _ va.rest; vb _ vb.rest; ENDLOOP; }; -- AlmostSameVL <> <<}; -- UpdateEntityGroup>> <> <> <> <> <> <> <> <> <> <<[ok, y] _ Crosssection[xEntity.newValues, el.first, x];>> <> <> <> <> <> <> <<}; -- UpdateCrosssection>> UpdateFromIntField: PROC [field: Viewer, origianl: INT _ 0, showMsg: BOOL _ TRUE] RETURNS [int: INT _ 0] = { <> msg: ROPE; [msg, int] _ GetIntField[field, showMsg]; IF msg # NIL THEN int _ origianl; }; -- UpdateFromIntField UpdateFromRealField: PROC [field: Viewer, origianl: REAL _ 0.0, showMsg: BOOL _ TRUE] RETURNS [real: REAL _ 0.0] = { <> msg: ROPE; [msg, real] _ GetRealField[field, showMsg]; IF msg # NIL THEN real _ origianl; }; -- UpdateFromRealField }. LOG. SChen, created at October 9, 1985 8:57:21 pm PDT.