WallabyImpl.mesa
Copyright Ó 1985, 1987, 1988, 1989, 1992 by Xerox Corporation. All rights reserved.
Willie-Sue, May 5, 1989 3:16:03 pm PDT
Jack Kent June 1, 1987 5:57:28 pm PDT
Doug Terry, July 21, 1992 11:16 am PDT
Contents: Wallaby Implementation
Note: This code is much simpler than it used to be, but could be simpler yet. There is a fair amount of code included here that is also in LoganQueryImpl. There is also some wasted effort; for instance, BrowseBody calls LoganQuery.QueryEntries and then throws away the returned cursor. This should all be cleaned up someday... DBT
DIRECTORY
Atom USING [GetPName],
TiogaButtons USING [CreateButton, AppendToButton, TiogaButton, TiogaButtonProc],
Icons USING [ IconFlavor, NewIconFromFile ],
IO,
Menus USING [AppendMenuEntry, ClickProc, CreateEntry, CreateMenu, MenuProc, MouseButton],
LoganBerry,
LoganBerryBrowser USING [CreateTool, DBInfoRec, DetailsProc, HistoryProc, ReadEntryForm, ReportFeedback, Tool, ToolBody],
LoganQuery USING [AbortQuery, AttributePatterns, BooleanFilterEntries, Cursor, EndGenerate, NextEntry, ParseNode, ParseNodeRec, ParseTree, QueryEntries, QueryPlan, SyntaxError],
LoganQueryClass USING [DestroyProc, QueryClass, QueryClassRec, RetrieveProc],
Rope USING [Cat, Concat, Length, ROPE, Substr],
ViewerClasses USING [Viewer],
ViewerOps,
Wallaby,
WalnutInternal USING [AddorAppendTo, DoWaitCall, GetSelectedMsgSets, GetButton, MoveTo],
WalnutDefs USING [Error],
WalnutOps USING [EntryObject, EntryRef, GenerateEntriesPlusDate, GeneralEnumerator, NextEntry],
WalnutWindow USING [DisplayMsg, Report, ReportRope],
WalnutWindowPrivate USING [MsgAndHandle, MsgAndHandleRec, MsgSetButton, WalnutHandle, WalnutHandleRec];
WallabyImpl:
CEDAR
PROGRAM
IMPORTS
Atom, Menus, Icons, IO, LoganBerry, LoganBerryBrowser, LoganQuery, Rope, TiogaButtons, ViewerOps, WalnutDefs, WalnutInternal, WalnutOps, WalnutWindow
EXPORTS Wallaby, WalnutWindow =
BEGIN
Walnut Viewers types and global data
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;
qabt: ROPE = "Query Aborted";
WalnutHandle: TYPE = WalnutWindowPrivate.WalnutHandle;
WalnutHandleRec: PUBLIC TYPE = WalnutWindowPrivate.WalnutHandleRec;
MsgSetButton: TYPE = WalnutWindowPrivate.MsgSetButton;
MsgAndHandle: TYPE = WalnutWindowPrivate.MsgAndHandle;
MsgAndHandleRec: TYPE = WalnutWindowPrivate.MsgAndHandleRec;
WalnutClass: LoganQueryClass.QueryClass = NEW[LoganQueryClass.QueryClassRec ¬ [$Walnut, WalnutRetrieve, WalnutDestroy]];
WalnutCursor: TYPE = REF CursorRecord;
CursorRecord:
TYPE =
RECORD[
start, end: ROPE,
enumerator: WalnutOps.GeneralEnumerator
];
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
MsgButtonProc: TiogaButtons.TiogaButtonProc = {
maH: MsgAndHandle = NARROW[clientData];
[] ¬ WalnutWindow.DisplayMsg[wH: maH.wH, msg: maH.msg];
};
ReportLBError:
PROC [ec: LoganBerry.ErrorCode, explanation:
ROPE, tool: LoganBerryBrowser.Tool]
RETURNS [] ~ {
[] ¬ TiogaButtons.CreateButton[viewer: tool.inner, rope: IO.PutFR["ERROR: %g - %g", [atom[ec]], [rope[explanation]]] ];
};
PresentationType:
TYPE = {browse, browseAdd, browseMove};
BrowseBody:
PROC [wH: WalnutHandle, proc: LoganBerry.EntryProc, tool: LoganBerryBrowser.Tool, presentationType: PresentationType] ~ {
ENABLE LoganBerry.Error => { ReportLBError[ec, explanation, tool]; CONTINUE };
form: LoganQuery.AttributePatterns;
base: LoganBerry.AttributeType;
plan: LoganQuery.QueryPlan;
entry: LoganBerry.Entry;
queryStart: ROPE ¬ "\n************* Start of Query ************\n";
queryEnd: ROPE ¬ "\n************* End of Query ************\n";
count: INT ¬ 0;
dateStart: LoganBerry.AttributeValue;
dateEnd: LoganBerry.AttributeValue;
baseStart: LoganBerry.AttributeValue;
baseEnd: LoganBerry.AttributeValue;
baseType: LoganBerry.AttributeType;
[form, , base] ¬ LoganBerryBrowser.ReadEntryForm[tool ! LoganQuery.SyntaxError => { ReportLBError[$MalformedInput, explanation, tool]; CONTINUE }];
check if $Subject was one of the fields specified; if so, clip to 20 char
FOR aL: LoganQuery.AttributePatterns ¬ form, aL.rest
UNTIL aL =
NIL
DO
IF aL.first.attr.type # $Subject THEN LOOP;
IF aL.first.attr.value.Length[] > 20 THEN
aL.first.attr.value ¬ Rope.Substr[aL.first.attr.value, 0, 20];
EXIT;
ENDLOOP;
plan query; get type and start/end of baseIndex
plan ¬ LoganQuery.QueryEntries[db: tool.databases.first.db, patterns: form, baseIndex: base, planOnly: TRUE].plan;
LoganBerryBrowser.ReportFeedback[plan, tool];
[dateStart: dateStart, dateEnd: dateEnd] ¬ GetDateStartDateEnd[plan: plan];
[baseStart: baseStart, baseEnd: baseEnd, baseType: baseType] ¬ GetMinCostIndex[plan: plan];
get cursor for base query
tool.cursor ¬ GenerateWalnutEntries[wH: wH, db: tool.databases.first.db, key: baseType, start: baseStart, end: baseEnd, dateStart: dateStart, dateEnd: dateEnd];
IF tool.cursor.data =
NIL
THEN {
noQuery: ROPE = "No msgs satisfied the query";
[] ¬ TiogaButtons.CreateButton[viewer: tool.inner, rope: noQuery];
WalnutWindow.Report[wH, noQuery];
RETURN;
};
build up filters
tool.cursor ¬ LoganQuery.BooleanFilterEntries[input: tool.cursor, query: QueryTreeFromAPs[form], inputOrder: baseType, primaryKey: $MsgID];
run query
WalnutWindow.Report[wH, "Query in Progress"];
[] ¬ TiogaButtons.CreateButton[viewer: tool.inner, rope: queryStart];
entry ¬ LoganQuery.NextEntry[tool.cursor];
WHILE entry #
NIL
DO
[] ¬ proc[entry];
IF (count ¬ count + 1) MOD 10 = 0 THEN WalnutWindow.ReportRope[wH, "~"];
entry ¬ LoganQuery.NextEntry[tool.cursor];
ENDLOOP;
LoganQuery.EndGenerate[tool.cursor];
WalnutWindow.Report[wH, "Query Finished"];
[] ¬ TiogaButtons.CreateButton[viewer: tool.inner, rope: queryEnd];
};
DoBrowseProc:
PROC[wH: WalnutHandle, tool: LoganBerryBrowser.Tool, mouseButton: Menus.MouseButton] = {
ENABLE
BEGIN
UNWIND, WalnutDefs.Error => {
[] ¬ TiogaButtons.CreateButton[viewer: tool.inner, rope: qabt];
LoganQuery.AbortQuery[tool.cursor];
WalnutWindow.Report[wH, qabt];
};
ABORTED => {
[] ¬ TiogaButtons.CreateButton[viewer: tool.inner, rope: qabt];
LoganQuery.AbortQuery[tool.cursor];
WalnutWindow.Report[wH, qabt];
GOTO abt;
};
END;
PutEntry: LoganBerry.EntryProc = {
[entry: LoganBerry.Entry] RETURNS [continue: BOOL]
header: ROPE;
data: ROPE;
headerLooks: ROPE;
dataLooks: ROPE;
msgID: ROPE ¬ MsgIDFromLogan[entry: entry];
maH: MsgAndHandle ¬ NEW[MsgAndHandleRec ¬ [msgID, wH]];
headerButton: TiogaButtons.TiogaButton ¬ TiogaButtons.CreateButton[viewer: tool.inner, proc: MsgButtonProc, rope: " \n", format: "body", clientData: maH];
FOR e: LoganBerry.Entry ¬ entry, e.rest
UNTIL e =
NIL
DO
IF e.first.type#$MessageText
AND e.first.type#$MsgID
THEN {
client data should be the message ID
header ¬ Atom.GetPName[e.first.type];
data ¬ Rope.Cat[": " , e.first.value, "\n"];
headerLooks ¬ "b";
dataLooks ¬ " ";
[] ¬ TiogaButtons.AppendToButton[button: headerButton, rope: header, looks: headerLooks];
[] ¬ TiogaButtons.AppendToButton[button: headerButton, rope: data, looks: dataLooks];
};
ENDLOOP;
[] ¬ TiogaButtons.AppendToButton[button: headerButton, rope: "\n"];
WalnutWindow.ReportRope[wH, "! "];
RETURN[continue: TRUE];
};
IF mouseButton # blue THEN tool.inner.class.init[tool.inner];
tool.outer.icon ¬ queryIcon;
BrowseBody[wH: wH, proc: PutEntry, tool: tool, presentationType: browse];
tool.outer.icon ¬ browserIcon;
IF tool.outer.iconic
THEN ViewerOps.PaintViewer[viewer: tool.outer, hint: all];
};
DoBrowseToMsgSetProc:
PROC[wH: WalnutHandle, tool: LoganBerryBrowser.Tool, move:
BOOL] = {
If there is walnut kernel error(probably from abort), catch it and revalidate viewers
ENABLE
BEGIN
UNWIND, WalnutDefs.Error => {
[] ¬ TiogaButtons.CreateButton[viewer: tool.inner, rope: qabt];
LoganQuery.AbortQuery[tool.cursor];
WalnutWindow.Report[wH, qabt];
};
ABORTED => {
[] ¬ TiogaButtons.CreateButton[viewer: tool.inner, rope: qabt];
LoganQuery.AbortQuery[tool.cursor];
WalnutWindow.Report[wH, qabt];
GOTO abt
};
END;
AddEntry: LoganBerry.EntryProc = {
[entry: LoganBerry.Entry] RETURNS [continue: BOOL]
msg: ROPE ¬ MsgIDFromLogan[entry: entry];
srcMsgSetButton ¬ WalnutInternal.GetButton[wH: wH,
msgSet: MsgSetNameFromLogan[entry: entry] ];
WalnutInternal.AddorAppendTo[wH: wH, msg: msg, isAdd: TRUE, fromButton: srcMsgSetButton, toButton: destMsgSetButton];
RETURN[continue: TRUE];
};
MoveEntry: LoganBerry.EntryProc = {
[entry: LoganBerry.Entry] RETURNS [continue: BOOL]
msg: ROPE ¬ MsgIDFromLogan[entry: entry];
srcMsgSetButton ¬ WalnutInternal.GetButton[wH: wH,
msgSet: MsgSetNameFromLogan[entry: entry] ];
WalnutInternal.MoveTo[msg: msg, fromButton: srcMsgSetButton, toButton: destMsgSetButton];
RETURN[continue: TRUE];
};
selMsgSetsButtons: LIST OF MsgSetButton ¬ WalnutInternal.GetSelectedMsgSets[wH];
destMsgSetButton: MsgSetButton;
srcMsgSetButton : MsgSetButton;
putEntry: LoganBerry.EntryProc ¬ IF move THEN MoveEntry ELSE AddEntry;
IF selMsgSetsButtons =
NIL
THEN {
[] ¬ TiogaButtons.CreateButton[viewer: tool.inner, rope: "Error: No message set selected"];
RETURN;
};
destMsgSetButton ¬ WalnutInternal.GetButton[wH: wH, msgSet: selMsgSetsButtons.first.msgSet.name];
I probably don't need to do a GetButton each time for source message set as I won't ever need to revalidate the source message set. But for now...
tool.outer.icon ¬ queryIcon;
BrowseBody[
wH: wH,
proc: putEntry,
tool: tool,
presentationType: IF move THEN browseMove ELSE browseAdd];
tool.outer.icon ¬ browserIcon;
IF tool.outer.iconic THEN ViewerOps.PaintViewer[viewer: tool.outer, hint: all];
EXITS abt => NULL;
};
StopProc: Menus.ClickProc =
TRUSTED {
[parent: REF ANY, clientData: REF ANY ← NIL, mouseButton: Menus.MouseButton ← red, shift: BOOL ← FALSE, control: BOOL ← FALSE]
tool: LoganBerryBrowser.Tool ¬ NARROW[clientData];
LoganQuery.AbortQuery[tool.cursor];
};
BrowseProc: Menus.ClickProc = {
tool: LoganBerryBrowser.Tool = NARROW[clientData];
wH: WalnutHandle = NARROW[tool.clientData];
Bp: PROC = { DoBrowseProc[wH, tool, mouseButton] };
[] ¬ WalnutInternal.DoWaitCall[wH, Bp];
};
BrowseMoveToMsgSetProc: Menus.ClickProc = {
tool: LoganBerryBrowser.Tool = NARROW[clientData];
wH: WalnutHandle = NARROW[tool.clientData];
Bmsp: PROC = { DoBrowseToMsgSetProc[wH: wH, tool: tool, move: TRUE] };
[] ¬ WalnutInternal.DoWaitCall[wH, Bmsp];
};
BrowseAddToMsgSetProc: Menus.ClickProc = {
tool: LoganBerryBrowser.Tool = NARROW[clientData];
wH: WalnutHandle = NARROW[tool.clientData];
Bamsp: PROC = { DoBrowseToMsgSetProc[wH: wH, tool: tool, move: FALSE] };
[] ¬ WalnutInternal.DoWaitCall[wH, Bamsp];
};
QueryProc:
PUBLIC Menus.MenuProc = {
wH: WalnutHandle = NARROW[clientData];
queryTool: LoganBerryBrowser.Tool ¬ NEW[LoganBerryBrowser.ToolBody];
queryTool.clientData ¬ wH;
queryTool.name ¬ wH.identifierPrefix.Concat[" Wallaby Queries"];
queryTool.wantInputArea ¬ FALSE;
queryTool.innerFlavor ¬ $TiogaButtons;
queryTool.fields ¬
IF wH.opsH.completeSchema
THEN
LIST[$MsgSetName, $Sender, $From, $To, $Cc, $Date, $Subject, $MessageText]
ELSE LIST[$MsgSetName, $Sender, $Date, $Subject, $MessageText];
queryTool.icon ¬ browserIcon;
queryTool.menu ¬ Menus.CreateMenu[lines: 1];
wH.openDB ¬ LoganBerry.Open[NIL, wH.opsH.dbName];
queryTool.databases ¬ LIST [NEW [LoganBerryBrowser.DBInfoRec ¬ [db: wH.openDB]] ];
Menus.AppendMenuEntry[
menu: queryTool.menu,
entry: Menus.CreateEntry[name: "STOP!", proc: StopProc, clientData: queryTool]];
Menus.AppendMenuEntry[
menu: queryTool.menu,
entry: Menus.CreateEntry[name: "Browse", proc: BrowseProc, clientData: queryTool]];
Menus.AppendMenuEntry[
menu: queryTool.menu,
entry: Menus.CreateEntry[name: "BrowseAddMsgSet", proc: BrowseAddToMsgSetProc, clientData: queryTool]];
Menus.AppendMenuEntry[
menu: queryTool.menu,
entry: Menus.CreateEntry[name: "BrowseMoveMsgSet", proc: BrowseMoveToMsgSetProc, clientData: queryTool]];
Menus.AppendMenuEntry[
menu: queryTool.menu,
entry: Menus.CreateEntry[name: "History", proc: LoganBerryBrowser.HistoryProc, clientData: queryTool]];
Menus.AppendMenuEntry[
menu: queryTool.menu,
entry: Menus.CreateEntry[name: "Details", proc: LoganBerryBrowser.DetailsProc, clientData: queryTool]];
LoganBerryBrowser.CreateTool[ tool: queryTool ];
ViewerOps.AddProp[queryTool.outer, $WallabyBrowserTool, queryTool];
wH.wallabyViewerList ¬ CONS[queryTool.outer, wH.wallabyViewerList];
queryTool.outer.label ¬ wH.identifierPrefix;
};
clients should be prepared to catch errors resulting from wallaby viewers being destroyed whilst a query is in progress
ShutDownWallaby:
PUBLIC
PROC[wH: WalnutHandle] = {
FOR vL:
LIST
OF Viewer ¬ wH.wallabyViewerList, vL.rest
UNTIL vL=
NIL
DO
IF NOT vL.first.destroyed THEN ViewerOps.DestroyViewer[vL.first];
vL.first ¬ NIL;
ENDLOOP;
wH.wallabyViewerList ¬ NIL;
};
GetDateStartDateEnd:
PROC [plan: LoganQuery.QueryPlan]
RETURNS [dateStart: LoganBerry.AttributeValue ¬
NIL, dateEnd: LoganBerry.AttributeValue ¬
NIL] = {
FOR p: LoganQuery.QueryPlan ¬ plan, p.rest
WHILE p #
NIL
DO
IF p.first.attr.type = $Date
THEN {
dateStart ¬ p.first.start;
dateEnd ¬ p.first.end;
}
ENDLOOP;
};
GetMinCostIndex:
PROC [plan: LoganQuery.QueryPlan]
RETURNS [baseStart: LoganBerry.AttributeValue ¬
NIL, baseEnd: LoganBerry.AttributeValue ¬
NIL, baseType: LoganBerry.AttributeType ¬
NIL] = {
minCost: REAL ¬ 999999.0;
FOR p: LoganQuery.QueryPlan ¬ plan, p.rest
WHILE p #
NIL
DO
IF p.first.cost < minCost
THEN {
baseStart ¬ p.first.start;
baseEnd ¬ p.first.end;
baseType ¬ p.first.attr.type;
minCost ¬ p.first.cost;
}
ENDLOOP;
};
WalnutRetrieve: LoganQueryClass.RetrieveProc = {
[cursor: Cursor, dir: CursorDirection ← increasing] RETURNS [entry: LoganBerry.Entry]
WITH cursor.data
SELECT
FROM
wc: WalnutCursor => {
walnutEntry: WalnutOps.EntryRef ¬ WalnutOps.NextEntry[wc.enumerator];
IF walnutEntry#NIL THEN entry ¬ WalnutToLogan[walnutEntry];
};
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Walnut cursor"];
};
WalnutDestroy: LoganQueryClass.DestroyProc = {
[cursor: Cursor] RETURNS []
WITH cursor.data
SELECT
FROM
wc: WalnutCursor => NULL; -- a no-op
ENDCASE => ERROR LoganBerry.Error[$BadCursor, "Bad $Walnut cursor"];
};
GenerateWalnutEntries:
PROC [wH: WalnutHandle, db: LoganBerry.OpenDB, key: LoganBerry.AttributeType, start: LoganBerry.AttributeValue ¬
NIL, end: LoganBerry.AttributeValue ¬
NIL, dateStart: LoganBerry.AttributeValue ¬
NIL, dateEnd: LoganBerry.AttributeValue ¬
NIL]
RETURNS [cursor: LoganQuery.Cursor] = {
wc: WalnutCursor;
enumerator: WalnutOps.GeneralEnumerator =
WalnutOps.GenerateEntriesPlusDate[opsHandle: wH.opsH,
attr: key, start: start, end: end, dateStart: dateStart, dateEnd: dateEnd];
IF enumerator = NIL THEN RETURN[[NIL, NIL]];
wc ¬ NEW[CursorRecord ¬ [start: start, end: end, enumerator: enumerator] ];
cursor.class ¬ WalnutClass;
cursor.data ¬ wc;
};
WalnutToLogan:
PROC [walnutEntry: WalnutOps.EntryObject]
RETURNS [entry: LoganBerry.Entry] ~ {
subj:
ROPE ¬
IF walnutEntry.seFromToCcSuDaMid.subject.Length[] < 20
THEN
walnutEntry.seFromToCcSuDaMid.subject
ELSE Rope.Substr[walnutEntry.seFromToCcSuDaMid.subject, 0, 20];
entry ¬
LIST [
[$MessageText, walnutEntry.msgText],
[$Sender, walnutEntry.seFromToCcSuDaMid.sender],
[$Subject, subj],
[$SubjectText, walnutEntry.seFromToCcSuDaMid.fullSubjectText],
[$Date, walnutEntry.seFromToCcSuDaMid.date]
];
FOR rL:
LIST
OF
ROPE ¬ walnutEntry.seFromToCcSuDaMid.keyword, rL.rest
UNTIL rL=
NIL
DO
entry ¬ CONS[[$Keyword, rL.first], entry];
ENDLOOP;
FOR rL:
LIST
OF
ROPE ¬ walnutEntry.seFromToCcSuDaMid.cc, rL.rest
UNTIL rL=
NIL
DO
entry ¬ CONS[[$Cc, rL.first], entry];
ENDLOOP;
FOR rL:
LIST
OF
ROPE ¬ walnutEntry.seFromToCcSuDaMid.to, rL.rest
UNTIL rL=
NIL
DO
entry ¬ CONS[[$To, rL.first], entry];
ENDLOOP;
FOR rL:
LIST
OF
ROPE ¬ walnutEntry.seFromToCcSuDaMid.from, rL.rest
UNTIL rL=
NIL
DO
entry ¬ CONS[[$From, rL.first], entry];
ENDLOOP;
$MsgID must be first, $MsgSetName must be next (see below)
entry ¬ CONS[[$MsgSetName, walnutEntry.msgSetName], entry];
entry ¬ CONS[[$MsgID, walnutEntry.seFromToCcSuDaMid.msgID], entry];
};
MsgSetNameFromLogan:
PROC [entry: LoganBerry.Entry]
RETURNS [
ROPE] ~ {
RETURN[entry.rest.first.value];
};
MsgIDFromLogan:
PROC [entry: LoganBerry.Entry]
RETURNS [
ROPE] ~ {
RETURN[entry.first.value];
};
QueryTreeFromAPs:
PROC [aps: LoganQuery.AttributePatterns]
RETURNS [qt: LoganQuery.ParseTree] ~ {
qt ¬ NIL;
FOR aL: LoganQuery.AttributePatterns ← aps, aL.rest
WHILE aL #
NIL
DO
node: LoganQuery.ParseNode ¬ NEW[LoganQuery.ParseNodeRec ¬ [tag: $ap, ap: aL.first]];
IF qt =
NIL
THEN qt ¬ node
ELSE qt ¬ NEW[LoganQuery.ParseNodeRec ¬ [tag: $and, child1: qt, child2: node]];
ENDLOOP;
};
* * * * * * * * * *
Initialization
browserIcon: Icons.IconFlavor ¬ Icons.NewIconFromFile[file: "Wallaby.icons", n: 0];
queryIcon: Icons.IconFlavor ¬ Icons.NewIconFromFile[file: "Wallaby.icons", n: 1];
END.