<<>> <> <> <> <> <> <> <<>> <> <<>> 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 <> 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 }]; <<>> <> 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 ¬ 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]; <<>> <> 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; }; <> tool.cursor ¬ LoganQuery.BooleanFilterEntries[input: tool.cursor, query: QueryTreeFromAPs[form], inputOrder: baseType, primaryKey: $MsgID]; <<>> <> 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 { <> 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]; EXITS abt => NULL; }; DoBrowseToMsgSetProc: PROC[wH: WalnutHandle, tool: LoganBerryBrowser.Tool, move: BOOL] = { <> 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]; <> 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; }; <<>> <> 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; }; <<* * * * * * * * * *>> <> browserIcon: Icons.IconFlavor ¬ Icons.NewIconFromFile[file: "Wallaby.icons", n: 0]; queryIcon: Icons.IconFlavor ¬ Icons.NewIconFromFile[file: "Wallaby.icons", n: 1]; END.