File: TuplesEditorImpl.mesa
Created by Donahue November 16, 1982 2:38 pm
Last edited by:
Donahue, April 14, 1983 10:15 am
Cattell, June 24, 1983 3:09 pm
Last Edited by: Beach, March 28, 1984 9:25:58 pm PST
DIRECTORY
Atom USING [GetPropFromList],
BasicTime USING [GMT],
Buttons USING [ButtonProc, ReLabel],
DB,
DBEnvironment,
IO,
Menus USING[ MouseButton ],
Nut,
NutOps,
NutViewer USING [Error, Message],
Rope,
TuplesEditor,
VFonts USING[CharWidth, FontHeight],
ViewerOps USING [AddProp, FetchProp, FetchViewerClass, PaintViewer],
Containers,
ViewerTools USING [GetContents, SetContents, SetSelection],
ViewerClasses, VTables;
TuplesEditorImpl:
CEDAR
MONITOR
IMPORTS
Atom, Buttons, DB, DBEnvironment, IO, Nut, NutOps,
NutViewer, Rope, VTables, VFonts, ViewerOps, ViewerTools
EXPORTS TuplesEditor =
{ OPEN DB, Rope, Nut, ViewerClasses, VTables;
PosPair: TYPE = RECORD[ row: CARDINAL, col: CARDINAL];
anyErrors: BOOL; -- Set if any procedure discovered user errors in saving the new entries
defaultOld: PUBLIC BOOL← TRUE; -- FALSE => attributes start in NewOrOld mode, else OldOnly
updateList: AttributeValueList;
allFieldsNull: BOOLEAN;
relTable: Viewer;
relRows: CARDINAL;
newRelship, oldRelship: Relship;
DisplayTuples:
PUBLIC
ENTRY
PROC[e: Entity, attrList: AttributeList, parent: Viewer]
RETURNS[ TuplesTable: VTable ] =
Main procedure in this module: puts up a subviewer in the parent viewer containing
all the tuples that do or could refer to the entity e. Main loop goes through all
attributes potentially referencing e's domain, and calls AddRow to add a blank tuple
if the tupleList referencing e is empty for that attribute, or the tupleList if it is
not empty.
{
ENABLE
UNWIND =>
NULL;
row: CARDINAL ← 0;
TuplesTable ← Create[ parent: parent, rows: Length[attrList]+1, columns: 1 ];
FOR attrL: AttributeList ← attrList, attrL.rest
UNTIL attrL =
NIL
DO
{ tupleList:
LIST
OF Relship =
IF e =
NIL
THEN
NIL
ELSE NutOps.GetTuples[e, attrL.first];
relTable: VTable = Create[ rows: 1, columns: 3, parent: TuplesTable ];
IF tupleList = NIL THEN AddRow[ relTable, attrL.first ]
ELSE
FOR tlT:
LIST
OF Relship ← tupleList, tlT.rest
UNTIL tlT=
NIL
DO
AddRow[ relTable, attrL.first, tlT.first ] ENDLOOP;
SetTableEntry[table: TuplesTable, row: row, flavor: $Viewer,
clientData: relTable, border: NullBorder ];
row ← row+1 }
ENDLOOP;
ViewerOps.AddProp[ TuplesTable, $Entity, e ];
Install[TuplesTable, FALSE] };
AddRow:
PROC[ table: VTable, attribute: Attribute, relship: Relship ←
NIL ] =
Add a row to the table, where the entity being displayed is assumed to be the value of
the attribute supplied. If a relship is supplied, take the values of the other fields from it;
otherwise, display empty fields.
{ row:
CARDINAL ← GetRowsAndColumns[table: table].rows-1;
r: Relation = NutOps.GetRelation[attribute];
allAttrs: LIST OF Attribute ← NutOps.AttributesOf[r];
firstRow: BOOLEAN ← TRUE;
displayAttrs: LIST OF Attribute = NutOps.RemoveAttribute[attribute, allAttrs];
howLong: INT = Length[displayAttrs];
howMuchSpace: INT = IF howLong MOD 2 = 0 THEN howLong/2 ELSE (howLong/2)+1;
i: INT ← 1; -- the index of the next column to be updated
The structure of the table now is to have three columns; the first is the name of the relation
(or blank if in succeeding rows), while the second and third give two attributes of the
relation
set the first entry
SetTableEntry[ table: table, row: row, column: 0, flavor: $Button, name: GetName[r],
clientData: attribute, proc: RelProc, border: NullBorder ];
{ relViewer: Viewer = GetTableEntry[ table: table, row: row, column: 0 ];
ViewerOps.AddProp[ relViewer, $newLine, NEW[BOOLEAN ← TRUE] ];
ViewerOps.AddProp[ viewer: relViewer, prop: $relShip, val: relship ];
ViewerOps.AddProp[ viewer: relViewer, prop: $r, val: r];
ViewerOps.AddProp[ viewer: relViewer, prop: $a, val: attribute ];
ViewerOps.AddProp[ viewer: relViewer, prop: $row, val: NEW[ INT ← row ] ] };
expand the table to contain as many rows as it will need for the display attributes
SetRowsAndColumns[ table: table, rows: row+1+howMuchSpace, columns: 3 ];
FOR da:
LIST
OF Attribute ← displayAttrs, da.rest
UNTIL da =
NIL
DO
nextAttr: Attribute = da.first;
attrViewer: VTable = Create[ parent: table, columns: 4 ];
ViewerOps.AddProp[ attrViewer, $attr, nextAttr ];
ViewerOps.AddProp[ attrViewer, $which, NEW[ INT← IF defaultOld THEN 2 ELSE 0 ] ];
VTables.SetTableEntry[
table: attrViewer, column: 0, flavor: $Button, proc: AttrProc,
clientData: attrViewer, name: Rope.Cat[DB.NameOf[nextAttr], ":"], border: NullBorder];
IF NutOps.EntityValued[nextAttr]
THEN
{ VTables.SetTableEntry[ table: attrViewer, column: 2, name:
IF defaultOld
THEN "O"
ELSE "",
flavor: $Button, w: VFonts.CharWidth['O]+5, border: NullBorder,
clientData: attrViewer, proc: CycleNewOld ];
VTables.GetTableEntry[ table: attrViewer, column: 2].border ← TRUE };
VTables.SetTableEntry[ table: attrViewer, column: 3, useMaxSize:
TRUE, border: NullBorder,
w: 20*VFonts.CharWidth['A], flavor: $Text ];
VTables.GetTableEntry[ table: attrViewer, column: 3 ].scrollable ← TRUE;
IF relship #
NIL
THEN
ViewerTools.SetContents[ viewer: GetTableEntry[ table: attrViewer, column: 3 ],
contents: GetFS[ relship, nextAttr ], paint: FALSE ];
Install[ attrViewer, FALSE ];
VTables.SetTableEntry[ table: table, row: row, column: i, flavor: $Viewer, clientData: attrViewer,
border: NullBorder, useMaxSize: TRUE ];
i ← (i+1) MOD 3;
IF i = 0
THEN
-- it's time for a new row
{ row ← row + 1;
SetTableEntry[ table: table, row: row, column: 0, name: "", border: NullBorder ];
ViewerOps.AddProp[ GetTableEntry[ table, row, 0 ], $newLine, NEW[BOOLEAN ← FALSE] ];
i ← i+1 }
ENDLOOP;
VTables.Install[ table,
FALSE ] };
Length:
PROC[ l:
LIST
OF Attribute ]
RETURNS[ n:
INT ] =
{ n ← 0; UNTIL l = NIL DO n ← n+1; l ← l.rest ENDLOOP };
AttrProc: Buttons.ButtonProc =
TRUSTED
{ viewer: ViewerClasses.Viewer =
NARROW[ parent, ViewerClasses.Viewer ];
attrViewer: VTable = NARROW[ clientData, VTable ];
textViewer: Viewer = GetTableEntry[ table: attrViewer, column: 3 ];
attr: Attribute = V2E[ViewerOps.FetchProp[attrViewer, $attr]];
domain: Domain = V2E[GetP[attr, aTypeIs, aTypeOf]];
entity: Entity;
SELECT mouseButton
FROM
Menus.MouseButton[red] =>
ViewerTools.SetSelection[ textViewer, NIL ];
Menus.MouseButton[yellow] =>
{ entity ← DeclareEntity[ domain, ViewerTools.GetContents[textViewer], Version[OldOnly] !
DB.Error => { entity ← NIL; CONTINUE } ];
IF entity # NIL THEN [] ← Nut.Display[ entity ]
ELSE NutViewer.Message[viewer, ViewerTools.GetContents[textViewer], " not found!"] };
ENDCASE => ViewerTools.SetSelection[ textViewer, NIL ] };
RelProc: Buttons.ButtonProc =
TRUSTED
{ viewer: ViewerClasses.Viewer =
NARROW[ parent, ViewerClasses.Viewer ];
attribute: Attribute = V2E[clientData];
relTable: VTable ← viewer.parent;
TuplesTable: VTable = relTable.parent;
SELECT mouseButton
FROM
Menus.MouseButton[red] =>
{ u: Uniqueness = V2U[GetP[attribute, aUniquenessIs, aUniquenessOf]];
IF u = Key
OR u = OptionalKey
THEN
{anyErrors← TRUE; NutViewer.Message[viewer, "Only one allowed!"]}
ELSE AddRow[ relTable, attribute ] };
Menus.MouseButton[blue] =>
{ row:
INT ← (
NARROW[ ViewerOps.FetchProp[ viewer, $row ],
REF
INT ])^;
newLine: BOOLEAN ← FALSE;
UNTIL newLine
DO
FOR i:
NAT
IN [0..2)
DO
{ attrViewer: VTable = GetTableEntry[ relTable, row, i+1 ];
IF attrViewer #
NIL
THEN
{ textViewer: Viewer ← GetTableEntry[ attrViewer, 0, 3 ];
attrButton: Viewer ← GetTableEntry[ attrViewer, 0, 0 ];
xoff, yoff: INTEGER;
text: ROPE = ViewerTools.GetContents[ textViewer ];
textHeight: INT = textViewer.wh;
newTextViewer: Viewer;
[xoff, yoff] ← GetEntryOffset[ table: relTable, row: row, column: 0 ];
SetTableEntry[ table: attrViewer, column: 3, flavor: $Text, border: NullBorder, useMaxSize: TRUE, w: 20*VFonts.CharWidth['A], h: textHeight+VFonts.FontHeight[]+1 ];
newTextViewer ← GetTableEntry[ table: attrViewer, column: 3 ];
newTextViewer.scrollable ← TRUE;
ViewerTools.SetContents[ viewer: newTextViewer, contents: text, paint: FALSE ];
Install[ attrViewer, FALSE ];
newTextViewer.newVersion ← textViewer.newVersion;
SetEntryOffset[ table: relTable, row: row, column: 0, yoff: GetEntryOffset[ table: attrViewer, column: 0 ].yoff ];
Install[ relTable, FALSE ] } }
ENDLOOP;
row ← row + 1;
IF row = GetRowsAndColumns[relTable].rows-1
THEN
EXIT;
{ relViewer: VTable = GetTableEntry[ relTable, row, 0 ];
newLineProp:
REF
BOOLEAN =
IF relViewer = NIL THEN NIL
ELSE NARROW[ViewerOps.FetchProp[relViewer, $newLine]];
newLine ← IF newLineProp = NIL THEN TRUE ELSE newLineProp^ }
ENDLOOP;
Install[ relTable, FALSE ] };
Menus.MouseButton[yellow] =>
{ row:
INT ← (
NARROW[ ViewerOps.FetchProp[ viewer, $row ],
REF
INT ])^;
newLine: BOOLEAN ← FALSE;
UNTIL newLine
DO
FOR i:
NAT
IN [0..2)
DO
{ attrViewer: VTable = GetTableEntry[ relTable, row, i+1 ];
IF attrViewer #
NIL
THEN
{ attr: Attribute = V2E[ViewerOps.FetchProp[attrViewer, $attr]];
type: Entity = V2E[GetP[attr, aTypeIs, aTypeOf]];
SetTableEntry[ table: attrViewer, row: 0, column: 1, name: TypeLabel[type], border: NullBorder ];
Install[ attrViewer, FALSE ] } }
ENDLOOP;
row ← row + 1;
IF row = GetRowsAndColumns[relTable].rows-1
THEN
EXIT;
{ relViewer: VTable = GetTableEntry[ relTable, row, 0 ];
newLineProp:
REF
BOOLEAN =
IF relViewer = NIL THEN NIL
ELSE NARROW[ViewerOps.FetchProp[relViewer, $newLine]];
newLine ← IF newLineProp = NIL THEN TRUE ELSE newLineProp^ }
ENDLOOP;
Install[ relTable, FALSE ] };
ENDCASE;
InstallAll[ TuplesTable] };
InstallAll:
PROC[ viewer: Viewer ] = {
tableClass: ViewerClasses.ViewerClass = ViewerOps.FetchViewerClass[$VTable];
IF viewer = NIL THEN RETURN;
UNTIL viewer.parent =
NIL
DO
IF viewer.class = tableClass THEN Install[viewer, FALSE];
viewer ← viewer.parent;
ENDLOOP;
ViewerOps.PaintViewer[viewer, all] };
TypeLabel:
PROC[ type: Entity ]
RETURNS[ Rope.
ROPE ] =
{
IF
DB.Eq[type,
DB.RopeType]
THEN
RETURN["(String)"]
ELSE IF DB.Eq[type, DB.IntType] THEN RETURN["(Int)"]
ELSE IF DB.Eq[type, DB.BoolType] THEN RETURN["(Bool)"]
ELSE IF DB.Eq[type, DB.TimeType] THEN RETURN["(Time)"]
ELSE RETURN[ Rope.Cat["(", GetName[type], ")"] ] };
CycleNewOld: Buttons.ButtonProc =
TRUSTED
{ viewer: ViewerClasses.Viewer =
NARROW[ parent, ViewerClasses.Viewer ];
Labels: ARRAY[0..3) OF ROPE = ["", "N", "O"];
attrViewer: VTable = viewer.parent;
relRow: VTable = attrViewer.parent;
TuplesTable: VTable = relRow.parent;
EventTable: VTable = TuplesTable.parent;
attr: Attribute = V2E[ ViewerOps.FetchProp[ attrViewer, $attr ] ];
which: REF INT = NARROW[ViewerOps.FetchProp[attrViewer, $which], REF INT];
IF NOT NutOps.EntityValued[attr] THEN RETURN;
which^ ← (which^ + 1) MOD 3;
Buttons.ReLabel[viewer, Labels[which^] ];
ViewerOps.AddProp[attrViewer, $which, which ];
Install[ relRow, FALSE ];
Install[ TuplesTable, FALSE ];
Install[ EventTable, TRUE ] };
SaveTuples:
PUBLIC
ENTRY
PROC[viewer: VTable, newEntity: Entity ←
NIL ]
RETURNS [errors:
BOOL] =
Called to update the relationships for an entity. If newEntity is NIL, then destroy
all the relationships for the oldEntity (previously associated with $Entity property
of the viewer). If oldEntity was NIL, then create a new entity with the relationships
that have been filled in by the user. If newEntity=oldEntity, update the relationships
that were modified by the user. Finally, if NIL#newEntity#oldEntity#NIL, then
copy all of oldEntities relationships to reference newEntity (leaves oldEntity intact).
Returns TRUE if any errors were reported to the user as a result of bad entries.
{
ENABLE
UNWIND =>
NULL;
tupleRows: CARDINAL = GetRowsAndColumns[ viewer ].rows;
oldEntity: DB.Entity = V2E[ViewerOps.FetchProp[viewer, $Entity]];
update: BOOLEAN = NOT DB.Null[oldEntity] AND DB.Eq[oldEntity, newEntity];
destroy: BOOLEAN = NOT DB.Null[oldEntity] AND newEntity = NIL;
create: BOOLEAN = NOT DB.Null[newEntity] AND oldEntity = NIL;
copy: BOOLEAN = NOT (destroy OR create OR update);
parent: Viewer = viewer.parent;
anyErrors← FALSE;
FOR i:
CARDINAL
IN [0..tupleRows-1)
DO
j: CARDINAL ← 0;
relTable ← GetTableEntry[ table: viewer, row: i, column: 0 ];
relRows ← GetRowsAndColumns[ relTable ].rows;
WHILE j < relRows-1
DO
The relRows is the number of rows with relationships from relation rel; for each
tuple (maybe >1 row), we want to destroy, create, or update the relationship as specified.
relViewer: Viewer = GetTableEntry[relTable, j, 0];
rel: Relation = V2E[ViewerOps.FetchProp[relViewer, $r ] ];
attr: Attribute = V2E[ViewerOps.FetchProp[ relViewer, $a ] ];
oldRelship← V2E[ViewerOps.FetchProp[relViewer, $relShip ] ];
updateList← NIL; allFieldsNull← TRUE; newRelship← NIL;
Advance j through attributes of this tuple, AddFields picking off two at a time, to
see what the attributes are and which we might have to change.
DO
AddFields[ parent: parent, row: j, copy:
NOT (destroy
OR create
OR update) ];
NOTE: AddFields modifies global updateList and allFieldsNull as side effect
IF PastLastRowOfTuple[j← j + 1, relRows, relTable] THEN EXIT;
ENDLOOP;
Now we want to either create, destroy, or update the relationship.
IF destroy
THEN
Destroy this relationship. We've already advanced j to skip over its rows.
{IF NOT DB.Null[oldRelship] THEN DB.DestroyRelship[oldRelship]}
ELSE
IF updateList#
NIL
THEN
-- Continue only if user modified something in this tuple..
IF update
AND
NOT
DB.Null[oldRelship]
THEN
Update mode, and there was an existing relationship: modify or delete it.
IF allFieldsNull THEN DB.DestroyRelship[oldRelship]
ELSE UpdateTuple[oldRelship, updateList]
ELSE
IF
NOT allFieldsNull
THEN
Either we are adding relship to a new entity, or updating old entity with no existing
oldRelship: create new tuple, including first attr (that refs entity) in update list.
{ updateList ←
CONS[ AttributeValue[ attribute: attr, lo: newEntity], updateList ];
UpdateTuple[CreateRelship[rel], updateList];
ViewerOps.AddProp[ relViewer, $relShip, newRelship ] };
ENDLOOP;
ENDLOOP;
RETURN[anyErrors]
PastLastRowOfTuple:
PROC[j:
CARDINAL, relRows:
CARDINAL, relTable: VTable ]
RETURNS [
BOOL] =
Used only by SaveTuples above, to decide when cross tuple boundaries in VTable.
Returns TRUE if j has passed the last row in one of the tuples for the given relation relTable.
Since $newLine is only attached to the relation button, this is true iff $newLine
is attached to this entry, or j is greater than the last row in the relTable.
{
RETURN[ j >= relRows-1
OR
NARROW[ViewerOps.FetchProp[GetTableEntry[relTable, j, 0], $newLine], REF BOOL]^]};
UpdateTuple:
PROC[ t: Relship, updateList: AttributeValueList ] =
Changes the Relship t as specified by updateList. If ALL of the values in the update list
are NILs, then just destroy the whole Relship, 'cause user has left all blanks.
{
First set all of the key values
FOR ul: AttributeValueList ← updateList, ul.rest
UNTIL ul =
NIL
DO
av: AttributeValue = ul.first;
u: Uniqueness = V2U[GetP[av.attribute, aUniquenessIs]];
IF u = Key THEN SetF[ t, av.attribute, av.lo ]
ENDLOOP;
Now set all of the non-key attributes
FOR ul: AttributeValueList ← updateList, ul.rest
UNTIL ul =
NIL
DO
av: AttributeValue = ul.first;
u: Uniqueness = V2U[GetP[av.attribute, aUniquenessIs]];
IF u # Key THEN SetF[ t, av.attribute, av.lo ]
ENDLOOP };
PropertyValueList: TYPE = LIST OF PropertyValue;
PropertyValue:
TYPE =
RECORD[
from: Attribute, -- entity-valued attribute of a relation
avl: AttributeValueList -- value contraints on other attributes of same relation
];
QueryTuples:
PUBLIC
ENTRY
PROC[ viewer: VTable, domain: Domain ]
RETURNS [el: LIST OF Entity] =
Given a table set up by DisplayTuples, finds all the entities in the given domain
that satisfy the query filled into the table by the user. Inner loop similar to SaveTuples,
but must create PropertyValueList instead of updating tuples.
{
ENABLE
UNWIND =>
NULL;
tupleRows: CARDINAL = GetRowsAndColumns[ viewer ].rows;
queryList: PropertyValueList← NIL;
parent: Viewer = viewer.parent;
FOR i:
CARDINAL
IN [0..tupleRows-1)
DO
j: CARDINAL ← 0;
relTable ← GetTableEntry[ table: viewer, row: i, column: 0 ];
relRows ← GetRowsAndColumns[ relTable ].rows;
WHILE j < relRows-1
DO
The relRows is the number of rows with relationships from relation rel; for each
tuple (maybe >1 row), we want to handle the relationship as specified.
relViewer: Viewer = GetTableEntry[relTable, j, 0];
rel: Relation = V2E[ViewerOps.FetchProp[relViewer, $r ] ];
attr: Attribute = V2E[ViewerOps.FetchProp[ relViewer, $a ] ];
updateList← NIL; allFieldsNull← TRUE;
Advance j through attributes of this tuple, AddFields picking off two at a time, to
see what the attributes are and which we might have to change.
DO
AddFields[ parent: parent, row: j, copy:
FALSE ];
NOTE: AddFields modifies global updateList and allFieldsNull as side effect
IF PastLastRowOfTuple[j← j + 1, relRows, relTable] THEN EXIT;
ENDLOOP;
IF updateList#
NIL
AND
NOT allFieldsNull
THEN
Continue only if user modified and put something in this tuple..
queryList← CONS[[from: attr, avl: updateList], queryList];
ENDLOOP; -- WHILE j < relRows-1
ENDLOOP; -- FOR i: CARDINAL
RETURN[EntityQuery[queryList]];
};
EntityQuery:
PROC[pvl: PropertyValueList]
RETURNS[
LIST
OF Entity] =
A handy procedure for finding all the entities of a domain that have the given
property values. Finds all the entities which, for each PropertyValue in the
PropertyValueList list, there exists at least one relationship whose
"from" attribute equals the entity and whose other attributes satisfy the
attribute value list.
{ f: Attribute← pvl.first.from;
r: Relation← V2E[GetP[f, aRelationIs]];
elNew: LIST OF Entity← NIL;
nestedRS: RelshipSet← RelationSubset[r, pvl.first.avl];
FOR t: Relship← NextRelship[nestedRS], NextRelship[nestedRS]
UNTIL t=
NIL
DO
elNew← CONS[V2E[GetF[t, f]], elNew] ENDLOOP;
ReleaseRelshipSet[nestedRS];
RETURN[NestedEntityQuery[elNew, pvl.rest]]
};
NestedEntityQuery:
PROC[elOld:
LIST
OF Entity, pvl: PropertyValueList]
RETURNS[LIST OF Entity] =
Finds the entities in elOld that satisfy pvl. We do this recursively as follows.
For each entity in elOld, we do a RelationSubset to find the tuples that satisfy pvl.first
and also reference the entity. If this list is not empty, we can keep that entity in a
new list elNew; otherwise we toss it. When all the entities in elOld that also
satisfy pvl.first have been added to elNew in this way, we recursively call
NestedEntityQuery, returning the entities in elNew which also satisfy pvl.rest.
{
IF pvl=
NIL
THEN
RETURN[elOld]
ELSE
{ f: Attribute← pvl.first.from;
r: Relation← V2E[GetP[f, aRelationIs]];
elNew, elEnd: LIST OF Entity← NIL; -- will equal those in elOld that satisfy pvl.first
FOR elOldT:
LIST
OF Entity← elOld, elOldT.rest
UNTIL elOldT=
NIL
DO
nestedRS: RelshipSet← RelationSubset[r, CONS[ [f, elOldT.first], pvl.first.avl]];
The nestedRS contains tuples that reference e and also satisfy first element of pvl.
We can keep elOldT.first in the elNew list iff this list is non-empty.
IF NextRelship[nestedRS] #
NIL
THEN
IF elNew=
NIL
THEN elEnd← elNew←
CONS[elOldT.first,
NIL]
ELSE elEnd← elEnd.rest← CONS[elOldT.first, NIL];
ReleaseRelshipSet[nestedRS];
ENDLOOP;
RETURN[NestedEntityQuery[elNew, pvl.rest]];
}};
AddFields:
PROC[parent: Viewer, row:
CARDINAL, copy:
BOOL] =
NOTE THIS PROCEDURE UPDATES GLOBAL VARIABLES AS A SIDE EFFECT.
THE MONITOR ON THIS MODULE IS TO PROTECT THESE GLOBAL VARIABLES.
Adds to updateList the attributes in row that need to be assigned in order to create
the relationship. Caller must add the first attribute (that refs the entity),
if desired. If the user does not modify a field, it does not appear in the updateList
unless there was an existing relationship and we are in "copy" mode.
Thus updateList=NIL if the user modified no fields of a tuple. Sets allFieldsNull to
FALSE if any of the fields are non-null.
{
FOR i: INT IN [0..2) DO
attrViewer: VTable = GetTableEntry[relTable, row, i+1];
IF attrViewer #
NIL
THEN
{ attr: Attribute = V2E[ViewerOps.FetchProp[attrViewer, $attr]];
newOld: INT = NARROW[ViewerOps.FetchProp[attrViewer, $which], REF INT]^;
textViewer: Viewer = GetTableEntry[table: attrViewer, column: 3];
contents: ROPE = ViewerTools.GetContents[textViewer];
type: Entity = V2E[GetP[attr, aTypeIs, aTypeOf]];
value: DB.Value;
IF
NOT textViewer.newVersion
THEN
Skip this guy unless copying and there was an existing relationship
IF oldRelship=NIL OR NOT copy THEN LOOP;
IF NOT Rope.Equal[contents, ""] THEN allFieldsNull← FALSE;
SELECT type
FROM
IntType => {
val: INT = IF Rope.Equal[contents, ""] THEN 0 ELSE IO.GetInt[ IO.RIS[contents] ];
value ← I2V[val]};
RopeType =>
value ← S2V[ contents ];
BoolType =>
value ← B2V[ Rope.Equal[contents, "TRUE"] ];
TimeType =>
{ time: DB.GMT;
time.time ←
IO.GetTime[
IO.
RIS[contents] !
IO.Error =>
TRUSTED {
anyErrors← TRUE;
NutViewer.Error[parent, "Unintelligible time/date: ", contents]}];
value ← T2V[time] };
ENDCASE =>
IF Rope.Equal[contents, ""] THEN value← NIL
ELSE
SELECT newOld
FROM
0 =>
-- NewOrOld
{ value ← DeclareEntity[ d: type, name: contents, version: OldOnly ];
IF value =
NIL
THEN {
NutViewer.Message[parent, "Automatically creating ", contents];
value ← DeclareEntity[ d: type, name: contents ];
IF value =
NIL
THEN {
allFieldsNull← TRUE; -- pretend this tuple didn't happen
anyErrors← TRUE;
NutViewer.Error[parent, " Error: ", contents, " could not be created automatically."]; } } };
1 =>
-- NewOnly
{ value ← CreateEntity[ d: type, name: contents !
DBEnvironment.Error =>
TRUSTED {
codeRef: REF DBEnvironment.ErrorCode ~ NEW[DBEnvironment.ErrorCode ← code];
NutViewer.Error[parent, IO.PutFR["%g", IO.refAny[codeRef]], " Error trying to create \"", contents, "\" entity."];
allFieldsNull← TRUE; -- pretend this tuple didn't happen
anyErrors← TRUE;
CONTINUE}]};
2 =>
-- OldOnly
{ value ← DeclareEntity[ type, contents, Version[OldOnly] ];
IF value =
NIL
THEN {
allFieldsNull← TRUE; -- pretend this tuple didn't happen
anyErrors← TRUE;
NutViewer.Error[parent, " Error: ", contents, " does not exist."] }};
ENDCASE;
updateList ← CONS[ AttributeValue[attribute: attr, lo: value], updateList ] }
ENDLOOP
};
}.
Change Log:
Changed by Cattell April 6, 1983 1:12 pm: The CONS in AddRow's first loop was causing it to reverse the order of the attributes to be displayed from the order in the database. Changed loop to destructively modify list, more efficient anyway since no space allocation.
Changed by Cattell April 15, 1983 11:08 am: Numerous changes and bug fixes to SaveTuples. Reduced complexity of inner loop from 22 to 11 lines!
Edited on March 21, 1984 9:17:51 pm PST, by Beach
added Cedar formatting via TiogaMesa.
changes to: AddFields to detect errors more often when creating entities automatically.
Edited on March 28, 1984 9:25:52 pm PST, by Beach
cosmetic changes to the {} conventions and indentation.
changes to: RelProc to preserve the newVersion status when expanding a relation row with the blue button.