DIRECTORY Atom USING [GetPName], BasicTime USING [GMT, Now, Period], BlackCherry, BlackCherryInternal, BlackCherrySidedoor, Buttons USING [Button, ButtonProc, Create, SetDisplayStyle], Commander USING [CommandProc, Handle, Register], CommanderOps USING [NextArgument], Containers USING [ChildXBound, ChildYBound], FileNames USING [CurrentWorkingDirectory, GetShortName, StripVersionNumber], IO, MailBasics, MailBasicsFileTypes, MailRetrieve USING [Close, Create, Handle, MBXState, NewUser], MailUtils USING [Credentials], MailSend USING [GetRegisteredSendProcsList, MailSendProcsRef], Menus USING [CreateEntry, CreateMenu, AppendMenuEntry, FindEntry, Menu, MenuEntry, MenuProc], PFS, PFSNames, PopUpSelection USING [Request], Process USING [Detach, PauseMsec], RefText, Rope, SendMailOps, SystemNames USING [SimpleHomeDirectory], TEditDocument USING [maxClip, TEditDocumentData], TiogaAccess USING [CharSet, Create, DoneWith, EndOf, Get, Put, Reader, TiogaChar, Writer], TiogaAccessViewers USING [FromSelection, FromViewer, WriteSelection, WriteViewer], TiogaButtons USING [TiogaButton, TiogaButtonProc, CreateButton, CreateViewer, ChangeButtonLooks, DeleteButton, SetStyleFromRope], TiogaMenuOps USING [AllLevels], TiogaOpsDefs USING [ ], TiogaOps USING [CancelSelection, FindText, GetSelection, RestoreSelB, SaveSelB, SearchDir, SelectDocument, SetStyle, ViewerDoc], UserProfile USING [Boolean, Token], ViewerClasses USING [Column, IconFlavor, Viewer], ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc], ViewerIO USING [CreateViewerStreams, GetViewerFromStream], ViewerOps USING [AddProp, BlinkIcon, CloseViewer, CreateViewer, DestroyViewer, FetchProp, FindViewer, GrowViewer, OpenIcon, PaintViewer, SetMenu, SetNewVersion], ViewerLocks USING [CallUnderWriteLock], ViewerSpecs USING [bwScreenHeight, openLeftWidth], ViewerTools USING [EnableUserEdits, GetSelectedViewer, GetSelectionContents, InhibitUserEdits, SetTiogaContents, TiogaContentsRec, TiogaContents]; BlackCherryImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, BlackCherryInternal, Buttons, Commander, CommanderOps, Containers, FileNames, IO, MailRetrieve, MailSend, Menus, PFS, PFSNames, PopUpSelection, Process, Rope, SendMailOps, TiogaAccess, TiogaAccessViewers, TiogaButtons, TiogaMenuOps, TiogaOps, SystemNames, UserProfile, ViewerEvents, ViewerIO, ViewerOps, ViewerLocks, ViewerSpecs, ViewerTools EXPORTS BlackCherry, BlackCherryInternal, BlackCherrySidedoor ~ BEGIN OPEN BlackCherry, BlackCherryInternal, BlackCherrySidedoor; ROPE: TYPE ~ Rope.ROPE; STREAM: TYPE ~ IO.STREAM; CantParse: SIGNAL ~ CODE; Viewer: TYPE ~ ViewerClasses.Viewer; TiogaButton: TYPE ~ TiogaButtons.TiogaButton; TiogaContents: TYPE ~ ViewerTools.TiogaContents; TiogaContentsRec: TYPE ~ ViewerTools.TiogaContentsRec; RetrieveState: TYPE ~ ATOM; iconFileName: ROPE ~ "BlackCherry.icons"; debugging: BOOL ¬ FALSE; msgSetStyle: ROPE ¬ "BeginStyle (cedar) AttachStyle (header) \"a message header\" { default 200 pt restIndent } StyleRule EndStyle"; msgSetIcon: PUBLIC ViewerClasses.IconFlavor ¬ tool; msgIcon: PUBLIC ViewerClasses.IconFlavor ¬ tool; plainTextStyle: ROPE; -- BlackCherry.PlainTextStyle showDels: BOOL; -- BlackCherry.ShowDeletedMsgs tocDefaultLooks: ROPE; -- BlackCherry.TOCDefaultLooks tocSelectedLooks: ROPE; -- BlackCherry.TOCSelectedLooks tocUnreadLooks: ROPE; -- BlackCherry.TOCUnreadLooks deletedLooks: ROPE; -- BlackCherry.DeletedLooks tocDelAndUnreadLooks: ROPE; -- an optimization! tsViewerEventRegistration: ViewerEvents.EventRegistration ¬ NIL; BadName: PUBLIC ERROR ~ CODE; OpenFailed: PUBLIC ERROR ~ CODE; fileProcs: FileProcs ¬ NIL; RegisterFileProcs: PUBLIC ENTRY PROC[procs: FileProcs] = { fileProcs ¬ procs }; CreateBCMailLogFromStream: PUBLIC PROC[fileName: ROPE, msgProc: MsgFromStreamProc] RETURNS[ok: BOOL ¬ FALSE] ~ { fileData: BCFileData ¬ fileProcs.openOrCreate[fileName, $create ! OpenFailed => { GOTO Failed }]; ok ¬ BCMailLog[fileData, msgProc, NIL]; EXITS Failed => Report[noCreateRope, [rope[fileName]] ]; }; noCreateRope: ROPE ~ "\n*** could not create %g for new BlackCherry mailLog - quitting\n"; CreateBCMailLogFromRopes: PUBLIC PROC[fileName: ROPE, msgProc: MsgFromRopesProc] RETURNS[ok: BOOL ¬ FALSE] ~ { fileData: BCFileData ¬ fileProcs.openOrCreate[fileName, $create ! OpenFailed => { GOTO Failed }]; ok ¬ BCMailLog[fileData, NIL, msgProc]; EXITS Failed => Report[noCreateRope, [rope[fileName]] ]; }; StrmFlush: PUBLIC PROC[stream: STREAM, msiData: MsiData] ~ { IF stream # NIL THEN { stream.Flush[]; msiData.timeOfLastFlush ¬ BasicTime.Now[]; }; }; CheckForFlush: PROC [msInfo: MsgSetInfo] ~ { now: BasicTime.GMT = BasicTime.Now[]; ro: REF BOOL ~ NARROW[msInfo.fileData.clientData]; msiData: MsiData ~ NARROW[msInfo.data]; IF ro­ THEN RETURN; IF ( BasicTime.Period[from: msiData.timeOfLastFlush, to: now] > 60 ) THEN { StrmFlush[msInfo.fileData.writeStream, msiData]; StrmFlush[msInfo.fileData.readStream, msiData]; }; }; CloseAndCreate: PROC [msInfo: MsgSetInfo] ~ { IF ( msInfo.fileData.writeStream # NIL ) THEN { msiData: MsiData ~ NARROW[msInfo.data]; StrmFlush[msInfo.fileData.writeStream, msiData]; msInfo.fileData.writeStream.Close[]; }; IF ( msInfo.fileData.readStream # NIL ) AND ( msInfo.fileData.readStream # msInfo.fileData.writeStream ) THEN msInfo.fileData.readStream.Close[ ! IO.Error => CONTINUE]; msInfo.fileData ¬ fileProcs.openOrCreate[msInfo.fileName, $create]; }; CloseAndOpen: PUBLIC PROC [msInfo: MsgSetInfo] ~ { now: BasicTime.GMT = BasicTime.Now[]; ro: REF BOOL ~ NARROW[msInfo.fileData.clientData]; IF ( msInfo.fileData.writeStream # NIL ) THEN { msiData: MsiData ~ NARROW[msInfo.data]; StrmFlush[msInfo.fileData.writeStream, msiData]; msInfo.fileData.writeStream.Close[]; }; IF ( msInfo.fileData.readStream # NIL ) AND ( msInfo.fileData.readStream # msInfo.fileData.writeStream ) THEN msInfo.fileData.readStream.Close[ ! IO.Error => CONTINUE]; msInfo.fileData ¬ fileProcs.openOrCreate[msInfo.fileName, IF ro­ THEN $openRO ELSE $open]; }; SetFileLength: PUBLIC PROC [msInfo: MsgSetInfo, len: INT] ~ { ro: REF BOOL ~ NARROW[msInfo.fileData.clientData]; IF ro­ THEN RETURN; msInfo.fileData.writeStream.SetIndex[len]; msInfo.fileData.writeStream.Close[]; IF ( msInfo.fileData.readStream # msInfo.fileData.writeStream ) THEN msInfo.fileData.readStream.Close[ ! IO.Error => CONTINUE]; fileProcs.setByteCount[msInfo.fileData, len]; msInfo.fileData ¬ fileProcs.openOrCreate[msInfo.fileName, $open]; }; GetRetrieveHandle: PUBLIC PROC RETURNS[RetrieveHandle] ~ { RETURN[curRH] }; GetRH: ENTRY PROC RETURNS[RetrieveHandle] ~ { RETURN[curRH] }; PathInHomeDirectory: PROC [ shortName: ROPE ] RETURNS [ path: ROPE ] ~ { path ¬ Rope.Concat[SystemNames.SimpleHomeDirectory[], shortName]; }; CaptionName: PROC[fileName: ROPE] RETURNS [ROPE] = { RETURN[Rope.Concat["Messages on ", fileName] ] }; Blackie: Commander.CommandProc ~ { IF ( msgSetIcon = tool ) AND ( fileProcs # NIL ) THEN fileProcs.iconSetter[iconFileName]; { wDir: ROPE ~ FileNames.CurrentWorkingDirectory[]; fileName: ROPE; doMail: BOOL ¬ TRUE; readOnly: BOOL ¬ FALSE; msiData: MsiData ¬ NEW[MsiDataRec]; msiData.okToFlushMail ¬ UserProfile.Boolean["BlackCherry.okToFlushMail", TRUE]; msiData.allowNewMail ¬ CheckForNewMailAllowed[]; debugging ¬ FALSE; -- reset each time FOR this: ROPE ¬ CommanderOps.NextArgument[cmd], CommanderOps.NextArgument[cmd] UNTIL this = NIL DO SELECT TRUE FROM ( this.Length[] < 2 ) => { NULL }; -- can't be a switch or fileName ( this.Fetch[0] # '- ) => { fileName ¬ this }; -- can't be a switch ENDCASE => { SELECT this.Fetch[1] FROM 'c, 'C => { msiData.checkStateReporting ¬ TRUE }; 'd, 'D => { msiData.okToFlushMail ¬ FALSE }; 'f, 'F => { msiData.okToFlushMail ¬ TRUE }; 'n, 'N => { doMail ¬ FALSE }; 'r, 'R => { readOnly ¬ TRUE; doMail ¬ FALSE; msiData.allowNewMail ¬ FALSE }; 'x, 'X => { debugging ¬ TRUE }; 'z, 'Z => { doMail ¬ FALSE; msiData.allowNewMail ¬ FALSE }; ENDCASE => { NULL }; }; ENDLOOP; IF NOT msiData.allowNewMail THEN doMail ¬ FALSE; IF doMail THEN { [] ¬ UpdateUser[msiData]; CreateAlertButton[]; }; IF ( fileName = NIL ) THEN fileName ¬ UserProfile.Token[key: "BlackCherry.MailLog", default: NIL]; IF ( fileName = NIL ) THEN fileName ¬ PathInHomeDirectory["BlackCherry.mailLog"]; { old: STREAM ¬ reportStrm; reportStrm ¬ TSStream["BlackCherry"]; IF old # NIL THEN { IF debugging THEN reportStrm.PutF1["\n\t unregister old: %g\n", [integer[LOOPHOLE[old]]] ]; SendMailOps.UnregisterReporter[old]; }; SendMailOps.RegisterReporter[reportStrm]; }; IF debugging THEN reportStrm.PutF1["\n\t register reportStrm: %g\n", [integer[LOOPHOLE[reportStrm]]] ]; fileName ¬ fileProcs.fullName[fileName, wDir ! BadName => { GOTO Failed }]; { oldV: Viewer; IF (oldV ¬ ViewerOps.FindViewer[CaptionName[fileName]]) # NIL THEN { ViewerOps.BlinkIcon[oldV]; Report["\n*** BlackCherry is already looking at %g\n", [rope[fileName]] ]; RETURN }; }; { msInfo: MsgSetInfo ¬ NEW[MsgSetInfoRec]; msInfo.data ¬ msiData; IF readOnly THEN msInfo.whichMenu ¬ $readOnly ELSE IF NOT msiData.allowNewMail THEN msInfo.whichMenu ¬ $dontAllowNewMail; CheckForFlushMsgs[msiData]; plainTextStyle ¬ UserProfile.Token[key: "BlackCherry.PlainTextStyle", default: "cedar"]; showDels ¬ UserProfile.Boolean[key: "BlackCherry.ShowDeletedMsgs", default: FALSE]; tocDefaultLooks ¬ UserProfile.Token[key: "BlackCherry.TOCDefaultLooks", default: ""]; tocSelectedLooks ¬ UserProfile.Token[key: "BlackCherry.TOCSelectedLooks", default: "sb"]; tocUnreadLooks ¬ UserProfile.Token[key: "BlackCherry.TOCUnreadLooks", default: "i"]; msiData.useFromFieldInTOC ¬ UserProfile.Boolean[key: "BlackCherry.UseFromFieldInTOC", default: FALSE]; deletedLooks ¬ UserProfile.Token[key: "BlackCherry.DeletedLooks", default: "y"]; tocDelAndUnreadLooks ¬ deletedLooks.Concat[tocUnreadLooks]; msInfo.fileData ¬ fileProcs.openOrCreate[fileName, IF readOnly THEN $openRO ELSE $openOrCreate ! OpenFailed => { GOTO Failed }]; msInfo.fileName ¬ fileName; AddToMsInfoList[msInfo]; TRUSTED { Process.Detach[FORK StartBlackCherryRead[msInfo, doMail, readOnly]] }; }; }; EXITS Failed => { result ¬ $Failure }; }; StartBlackCherryRead: ENTRY PROC [msInfo: MsgSetInfo, readNewNow, readOnly: BOOL] ~ { ENABLE UNWIND => { IF ( msInfo.fileData.writeStream # NIL ) THEN msInfo.fileData.writeStream.Close[ ! IO.Error => CONTINUE]; IF ( msInfo.fileData.readStream # NIL ) THEN msInfo.fileData.readStream.Close[ ! IO.Error => CONTINUE]; IF ( msInfo.viewer # NIL ) THEN msInfo.viewer.inhibitDestroy ¬ FALSE; }; caption: ROPE ~ CaptionName[msInfo.fileName]; label: ROPE ~ FileNames.GetShortName[msInfo.fileName]; numUndel, totalMsgs: INT; v: Viewer; msiData: MsiData ~ NARROW[msInfo.data]; menuToUse: Menus.Menu ¬ IF readOnly THEN readOnlyMenu ELSE IF NOT msiData.allowNewMail THEN dontAllowNewMailMenu ELSE displayerMenu; ok: BOOL ¬ FALSE; column: ViewerClasses.Column ¬ left; WhichCol: PROC[rp: ROPE, df: ViewerClasses.Column] RETURNS[ViewerClasses.Column] = { SELECT TRUE FROM rp.Equal["left", FALSE] => RETURN[left]; rp.Equal["right", FALSE] => RETURN[right]; rp.Equal["color", FALSE] => RETURN[color]; ENDCASE => RETURN[df]; }; columnRope: ROPE ¬ UserProfile.Token[key: "BlackCherry.MsgSetColumn", default: "right"]; column ¬ WhichCol[columnRope, right]; Report["\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~ %g\n", [time[BasicTime.Now[]]] ]; Report["\tUsing BlackCherry file %g\n", [rope[msInfo.fileName]] ]; BEGIN ENABLE BEGIN PFS.Error => { Report["\n*** PFS.Error during ReadBWFile: %g\n", [rope[error.explanation]] ]; CONTINUE }; IO.Error => { Report["\n*** IO.Error during ReadBWFile: %g\n", [integer[ORD[ec]]] ]; CONTINUE; }; END; numUndel ¬ - 1; [numUndel, totalMsgs, ok] ¬ ReadBWFile[msInfo]; IF NOT ok THEN { readNewNow ¬ FALSE; menuToUse ¬ readOnlyMenu; msInfo.whichMenu ¬ $readOnly; }; END; InfoMsg[numUndel, totalMsgs, msInfo.fileData.readStream]; IF ( readNewNow ) THEN ReadAnyNewMail[msInfo]; v ¬ msInfo.viewer ¬ ViewerOps.CreateViewer[flavor: $Container, info: [name: caption, label: label, menu: menuToUse, iconic: TRUE, icon: msgSetIcon, column: column, scrollable: FALSE, inhibitDestroy: TRUE] ]; msInfo.destroyER ¬ ViewerEvents.RegisterEventProc[DestroyMSViewer, destroy, v, TRUE]; msInfo.tiogaViewer ¬ TiogaButtons.CreateViewer[info: [parent: v, border: FALSE, ww: ViewerSpecs.openLeftWidth, wh: ViewerSpecs.bwScreenHeight]]; TiogaButtons.SetStyleFromRope[v: msInfo.tiogaViewer, styleRope: msgSetStyle]; Containers.ChildXBound[v, msInfo.tiogaViewer]; Containers.ChildYBound[v, msInfo.tiogaViewer]; ViewerOps.AddProp[v, $BlackCherry, msInfo]; IF ( msInfo.first # NIL ) THEN AddMsgs[msInfo, showDels]; IF v.iconic THEN ViewerOps.OpenIcon[v]; v.inhibitDestroy ¬ FALSE; msiData.timeOfLastFlush ¬ BasicTime.Now[]; IF msiData.allowNewMail THEN TRUSTED { Process.Detach[FORK CheckState[]] }; }; CheckState: PROC ~ { p: NAT ~ NewP[]; last: MailRetrieve.MBXState; mSeconds: INT ~ 30000; -- 30 sec curMH: RetrieveHandle ¬ GetRH[]; count: CARD ¬ 0; csReporting: BOOL; IF ( curMH = NIL ) OR ( newMailAlertButton = NIL ) THEN RETURN; IF ( csReporting ¬ curMH.msiData.checkStateReporting ) THEN Report["==> CheckState: p = %g\n", [cardinal[p]] ]; last ¬ curMH.lastStateReported; Buttons.SetDisplayStyle[button: newMailAlertButton, style: $BlackOnGrey]; WHILE ( p = CurrentP[] ) --Abort earlier processes as new ones enter-- DO new: MailRetrieve.MBXState; curMH: RetrieveHandle ¬ GetRH[]; IF curMH = NIL THEN { IF csReporting THEN Report["==> Exiting CheckState (curMH=NIL): p = %g, count = %g\n", [cardinal[p]], [cardinal[count]] ]; RETURN; }; IF csReporting THEN IF ( count = 50 ) THEN Report["==> in loop: p = %g\n", [cardinal[p]] ]; count ¬ count.SUCC; new ¬ curMH.lastStateReported; SELECT new FROM badName, badPwd, cantAuth, allDown => { Buttons.SetDisplayStyle[newMailAlertButton, $BlackOnGrey]; }; unknown => { Buttons.SetDisplayStyle[newMailAlertButton, $BlackOnGrey]; }; allEmpty => { Buttons.SetDisplayStyle[newMailAlertButton, $BlackOnWhite]; }; notEmpty => { Buttons.SetDisplayStyle[newMailAlertButton, $WhiteOnBlack]; }; userOK, someEmpty => { NULL }; ENDCASE => { ERROR }; Process.PauseMsec[mSeconds] ENDLOOP; IF csReporting THEN Report["==> Exiting CheckState: p = %g, count = %g\n", [cardinal[p]], [cardinal[count]] ]; }; ForceStateOff: PUBLIC PROC = { p: NAT ~ NewP[]; curMH: RetrieveHandle ¬ GetRH[]; IF ( curMH = NIL ) OR ( newMailAlertButton = NIL ) THEN RETURN; IF curMH.msiData.checkStateReporting THEN Report["==> In ForceStateOff, p = %g\n", [cardinal[p]] ]; curMH.lastStateReported ¬ allEmpty; Buttons.SetDisplayStyle[newMailAlertButton, $BlackOnWhite]; TRUSTED { Process.Detach[FORK CheckState[]] }; }; blankMenu: Menus.Menu ¬ Menus.CreateMenu[]; displayerMenu: Menus.Menu ¬ Menus.CreateMenu[]; readOnlyMenu: Menus.Menu ¬ Menus.CreateMenu[]; dontAllowNewMailMenu: Menus.Menu ¬ Menus.CreateMenu[]; frozenMsgMenu: Menus.Menu ¬ Menus.CreateMenu[]; msgMenu: Menus.Menu ¬ Menus.CreateMenu[]; CreateMenus: PROC ~ { Menus.AppendMenuEntry[displayerMenu, Menus.CreateEntry["ShowUndelOnly", ShowUndelOnlyProc]]; Menus.AppendMenuEntry[displayerMenu, Menus.CreateEntry["Display", DisplayProc]]; Menus.AppendMenuEntry[displayerMenu, Menus.CreateEntry["Delete", DeleteProc]]; Menus.AppendMenuEntry[displayerMenu, Menus.CreateEntry["NewMail", NewMailProc]]; Menus.AppendMenuEntry[displayerMenu, Menus.CreateEntry["Find", FindProc]]; Menus.AppendMenuEntry[displayerMenu, Menus.CreateEntry["Ops", OpsMenuProc]]; Menus.AppendMenuEntry[dontAllowNewMailMenu, Menus.CreateEntry["ShowUndelOnly", ShowUndelOnlyProc]]; Menus.AppendMenuEntry[dontAllowNewMailMenu, Menus.CreateEntry["Display", DisplayProc]]; Menus.AppendMenuEntry[dontAllowNewMailMenu, Menus.CreateEntry["Delete", DeleteProc]]; Menus.AppendMenuEntry[dontAllowNewMailMenu, Menus.CreateEntry["Find", FindProc]]; Menus.AppendMenuEntry[dontAllowNewMailMenu, Menus.CreateEntry["Ops", OpsMenuProc]]; AddOpsProc["Sender", CreateSendViewerProc, $all]; AddOpsProc["MsgNumInfo", MsgNumInfoProc, $all]; AddOpsProc["ShowAll", ShowAllProc, $all]; AddOpsProc["MsgID", MsgIDProc, $all]; AddOpsProc["NewLogFile", NewFile, $writeNeeded]; AddOpsProc["Expunge", ExpungeProc, $writeNeeded]; AddOpsProc["Expunge&Sort", SortProc, $writeNeeded]; AddOpsProc["NSToTioga", NSToTioga, $all]; AddOpsProc["TiogaToNS", TiogaToNS, $all]; AddOpsProc["CloseAll", CloseAllProc, $all]; AddOpsProc["AppendMsg", AppendMsgProc, $writeNeeded]; Menus.AppendMenuEntry[readOnlyMenu, Menus.CreateEntry["ShowUndelOnly", ShowUndelOnlyProc]]; Menus.AppendMenuEntry[readOnlyMenu, Menus.CreateEntry["Display", DisplayProc]]; Menus.AppendMenuEntry[readOnlyMenu, Menus.CreateEntry["Find", FindProc]]; Menus.AppendMenuEntry[readOnlyMenu, Menus.CreateEntry["Ops", OpsMenuProc]]; FOR procs: LIST OF MailSend.MailSendProcsRef ¬ MailSend.GetRegisteredSendProcsList[], procs.rest UNTIL procs = NIL DO name: ROPE ¬ Rope.Concat[Atom.GetPName[procs.first.which], "Answer"]; Menus.AppendMenuEntry[frozenMsgMenu, Menus.CreateEntry[name: name, proc: MsgAnswerProc, clientData: procs.first.which]]; ENDLOOP; Menus.AppendMenuEntry[frozenMsgMenu, Menus.CreateEntry["Forward", MsgForwardProc]]; Menus.AppendMenuEntry[frozenMsgMenu, Menus.CreateEntry["ReSend", MsgReSendProc]]; Menus.AppendMenuEntry[frozenMsgMenu, Menus.CreateEntry["Find", MsgFindProc]]; Menus.AppendMenuEntry[frozenMsgMenu, Menus.CreateEntry["", MsgActivityProc]]; Menus.AppendMenuEntry[frozenMsgMenu, Menus.CreateEntry["NSToTioga", NSToTiogaProc]]; Menus.AppendMenuEntry[frozenMsgMenu, Menus.CreateEntry["TiogaToNS", TiogaToNSProc]]; Menus.AppendMenuEntry[msgMenu, Menus.CreateEntry["Freeze", MsgFreezeProc]]; FOR procs: LIST OF MailSend.MailSendProcsRef ¬ MailSend.GetRegisteredSendProcsList[], procs.rest UNTIL procs = NIL DO name: ROPE ¬ Rope.Concat[Atom.GetPName[procs.first.which], "Answer"]; Menus.AppendMenuEntry[msgMenu, Menus.CreateEntry[name: name, proc: MsgAnswerProc, clientData: procs.first.which]]; ENDLOOP; Menus.AppendMenuEntry[msgMenu, Menus.CreateEntry["Forward", MsgForwardProc]]; Menus.AppendMenuEntry[msgMenu, Menus.CreateEntry["ReSend", MsgReSendProc]]; Menus.AppendMenuEntry[msgMenu, Menus.CreateEntry["Find", MsgFindProc]]; Menus.AppendMenuEntry[msgMenu, Menus.CreateEntry["", MsgActivityProc]]; Menus.AppendMenuEntry[msgMenu, Menus.CreateEntry["NSToTioga", NSToTiogaProc]]; Menus.AppendMenuEntry[msgMenu, Menus.CreateEntry["TiogaToNS", TiogaToNSProc]]; }; AddDisplayerProc: PUBLIC PROC[menuName: ROPE, proc: Menus.MenuProc] ~ { Menus.AppendMenuEntry[displayerMenu, Menus.CreateEntry[menuName, proc]]; }; AddMsgProc: PUBLIC PROC[menuName: ROPE, proc: Menus.MenuProc] ~ { Menus.AppendMenuEntry[msgMenu, Menus.CreateEntry[menuName, proc]]; }; MsgFreezeProc: Menus.MenuProc ~ { self: Viewer ~ NARROW[parent]; frozen: Menus.MenuEntry ~ Menus.FindEntry[self.menu, "Freeze"]; msVInfo: MsgSetInfo; IF ( frozen = NIL ) THEN RETURN; msVInfo ¬ NARROW[ViewerOps.FetchProp[parent, $BlackCherry]]; ViewerOps.AddProp[ self, $Frozen, self ]; ViewerOps.SetMenu[self, frozenMsgMenu]; IF msVInfo.msgDisplayer = self THEN msVInfo.msgDisplayer ¬ NIL; }; MsgAnswerProc: Menus.MenuProc ~ { OPEN TiogaOps; self: Viewer ¬ NARROW[parent]; transport: ATOM ¬ NARROW[clientData]; ans: Viewer; msgR: ROPE; self.inhibitDestroy ¬ TRUE; TRUSTED { msgR ¬ SendMailOps.CreateRopeForTextNode[LOOPHOLE[ViewerDoc[self]]]}; ans ¬ SendMailOps.Answer[msgR, self, transport]; IF ( ( UserProfile.Boolean["BlackCherry.EditedAnswerSenders", FALSE] ) OR ( mouseButton = blue ) ) THEN ViewerOps.SetNewVersion[ans]; self.inhibitDestroy ¬ FALSE; }; MsgReSendProc: Menus.MenuProc ~ { self: Viewer ¬ NARROW[parent]; self.inhibitDestroy ¬ TRUE; [] ¬ SendMailOps.ReSend[self, self]; self.inhibitDestroy ¬ FALSE; }; MsgForwardProc: Menus.MenuProc ~ { self: Viewer ¬ NARROW[parent]; fw: Viewer; self.inhibitDestroy ¬ TRUE; fw ¬ SendMailOps.Forward[self, self]; IF ( ( UserProfile.Boolean["BlackCherry.EditedForwardSenders", FALSE] ) OR ( mouseButton = blue ) ) THEN ViewerOps.SetNewVersion[fw]; self.inhibitDestroy ¬ FALSE; }; MsgFindProc: Menus.MenuProc ~ { self: Viewer = NARROW[parent]; sd: TiogaOps.SearchDir ¬ SELECT mouseButton FROM red => forwards, yellow => anywhere, blue => backwards, ENDCASE => forwards; IF ( NOT TiogaOps.FindText[viewer: self, whichDir: sd, which: feedback, case: NOT shift] ) THEN ViewerOps.BlinkIcon[self]; }; MsgActivityProc: Menus.MenuProc ~ { self: Viewer = NARROW[parent]; SELECT mouseButton FROM red => self.transparentTIP ¬ TRUE; ENDCASE => self.transparentTIP ¬ FALSE; }; PutChar: PROC[ output: TiogaAccess.Writer, inChar: TiogaAccess.TiogaChar, charSet: TiogaAccess.CharSet] RETURNS[char: TiogaAccess.TiogaChar] = { char ¬ inChar; IF charSet # 0 THEN char.charSet ¬ charSet; IF char.charSet # 0 THEN char.looks['j] ¬ TRUE; TiogaAccess.Put[output, char]; }; NSToTiogaProc: ENTRY Menus.MenuProc = { self: Viewer = NARROW[parent]; input: TiogaAccess.Reader ¬ TiogaAccessViewers.FromViewer[self]; output: TiogaAccess.Writer ¬ TiogaAccess.Create[]; DoNSToTiogaProc[input, output]; ViewerTools.EnableUserEdits[self]; TiogaAccessViewers.WriteViewer[output, self]; ViewerTools.InhibitUserEdits[self]; }; DoNSToTiogaProc: INTERNAL PROC[input: TiogaAccess.Reader, output: TiogaAccess.Writer] = { State: TYPE = {normal, xns, esc, escdol, jis, jis2nd, jisesc, jisescpar}; char, escChar, postChar: TiogaAccess.TiogaChar ¬ TiogaAccess.TiogaChar[0, '\000, ALL[FALSE], NIL, FALSE, FALSE, 1, NIL]; charSet: TiogaAccess.CharSet ¬ 0; state: State ¬ State.normal; UNTIL input.EndOf[] DO char ¬ input.Get[]; SELECT state FROM State.normal => { IF char.char = '\377 THEN state ¬ State.xns ELSE IF char.char = '\033 THEN {state ¬ State.esc; escChar ¬ char;} ELSE [] ¬ PutChar[output, char, charSet]; }; State.xns => {state ¬ State.normal; charSet ¬ ORD[char.char];}; State.esc => { IF char.char = '$ THEN {state ¬ State.escdol; postChar ¬ char;} ELSE {state ¬ State.normal; [] ¬ PutChar[output, escChar, charSet]}; }; State.escdol => { IF char.char = '@ THEN state ¬ State.jis ELSE { state ¬ State.normal; [] ¬ PutChar[output, escChar, charSet]; [] ¬ PutChar[output, postChar, charSet]; }; }; State.jis => { IF char.char = '\033 THEN {state ¬ State.jisesc; escChar ¬ char;} ELSE {state ¬ State.jis2nd; charSet ¬ ORD[char.char];}; }; State.jis2nd => {state ¬ State.jis; [] ¬ PutChar[output, char, charSet];}; State.jisesc => { IF char.char = '( THEN {state ¬ State.jisescpar; postChar ¬ char;} ELSE {state ¬ State.jis; [] ¬ PutChar[output, escChar, 0];} }; State.jisescpar => { IF char.char = 'H OR char.char = 'J THEN {state ¬ State.normal; charSet ¬ 0;} ELSE { state ¬ State.jis; [] ¬ PutChar[output, escChar, 0]; [] ¬ PutChar[output, postChar, 0]; }; }; ENDCASE => ERROR; ENDLOOP; TiogaAccess.DoneWith[input]; }; TiogaToNSProc: ENTRY Menus.MenuProc ~ { self: Viewer = NARROW[parent]; input: TiogaAccess.Reader ¬ TiogaAccessViewers.FromViewer[self]; output: TiogaAccess.Writer ¬ TiogaAccess.Create[]; DoTiogaToNSProc[input, output]; ViewerTools.EnableUserEdits[self]; TiogaAccessViewers.WriteViewer[output, self]; ViewerTools.InhibitUserEdits[self]; }; DoTiogaToNSProc: INTERNAL PROC[input: TiogaAccess.Reader, output: TiogaAccess.Writer] = { char, ffChar, charSet: TiogaAccess.TiogaChar ¬ TiogaAccess.TiogaChar[0, '\000, ALL[FALSE], NIL, FALSE, FALSE, 1, NIL]; curCharSet: TiogaAccess.CharSet ¬ 0; ffChar.char ¬ '\377; UNTIL input.EndOf[] DO char ¬ input.Get[]; IF char.charSet # curCharSet THEN { charSet.char ¬ VAL[curCharSet ¬ char.charSet]; TiogaAccess.Put[output, ffChar]; TiogaAccess.Put[output, charSet]; }; char.charSet ¬ 0; char.looks['j] ¬ FALSE; TiogaAccess.Put[output, char] ENDLOOP; TiogaAccess.DoneWith[input]; }; noSelDisp: ROPE ~ " No selected msg to be displayed\n"; ShowUndelOnlyProc: ENTRY Menus.MenuProc ~ { ENABLE UNWIND => NULL; msInfo: MsgSetInfo ~ NARROW[ViewerOps.FetchProp[parent, $BlackCherry]]; FOR msgH: MsgHandle ¬ msInfo.first, msgH.next WHILE ( msgH # NIL ) DO IF ( NOT msgH.deleted ) THEN LOOP; TiogaButtons.DeleteButton[msgH.tocButton]; msgH.tocButton ¬ NIL; ENDLOOP; IF ( msInfo.selected # NIL ) THEN IF ( msInfo.selected.tocButton = NIL ) THEN msInfo.selected ¬ NIL; }; DisplayProc: ENTRY Menus.MenuProc ~ { ENABLE UNWIND => NULL; msInfo: MsgSetInfo ~ NARROW[ViewerOps.FetchProp[parent, $BlackCherry]]; selected: MsgHandle; IF ( msInfo = NIL ) THEN RETURN; selected ¬ msInfo.selected; IF ( selected = NIL ) THEN { Report[noSelDisp]; RETURN }; IF ( mouseButton # red ) THEN selected ¬ AdvanceSelection[selected]; IF ( selected = NIL ) THEN Report[noSelDisp] ELSE DisplayOneMsg[msInfo.selected ¬ selected, shift] }; DeleteProc: ENTRY Menus.MenuProc ~ { ENABLE UNWIND => NULL; msInfo: MsgSetInfo ~ NARROW[ViewerOps.FetchProp[parent, $BlackCherry]]; selected: MsgHandle; IF ( msInfo = NIL ) THEN RETURN; selected ¬ msInfo.selected; IF ( selected = NIL ) THEN { Report[" No selected msg to be deleted\n"]; RETURN }; ShowAsDeleted[selected]; selected ¬ AdvanceSelection[selected]; IF ( mouseButton # red ) THEN { IF ( selected = NIL ) THEN Report[noSelDisp] ELSE DisplayOneMsg[msInfo.selected ¬ selected, shift] }; }; NewMailProc: ENTRY Menus.MenuProc ~ { ENABLE UNWIND => NULL; msInfo: MsgSetInfo ~ NARROW[ViewerOps.FetchProp[parent, $BlackCherry]]; ReadAnyNewMail[msInfo]; AddMsgs[msInfo, showDels]; }; FindProc: Menus.MenuProc ~ { msViewer: Viewer ~ NARROW[parent]; msInfo: MsgSetInfo ~ NARROW[ViewerOps.FetchProp[parent, $BlackCherry]]; sd: TiogaOps.SearchDir ~ SELECT mouseButton FROM red => forwards, yellow => anywhere, blue => backwards, ENDCASE => forwards; IF ( msInfo = NIL ) THEN RETURN; IF ( NOT TiogaOps.FindText[viewer: msInfo.tiogaViewer, whichDir: sd, which: feedback, case: NOT shift] ) THEN ViewerOps.BlinkIcon[msViewer]; }; allPopUpFormsList: LIST OF ROPE ¬ NIL; roPopUpFormsList: LIST OF ROPE ¬ NIL; allOpsProcsList: LIST OF OpsProc ¬ NIL; roOpsProcsList: LIST OF OpsProc ¬ NIL; OpsMenuProc: Menus.MenuProc ~ { ENABLE UNWIND => NULL; viewer: Viewer ~ NARROW[parent]; msInfo: MsgSetInfo ~ NARROW[ViewerOps.FetchProp[viewer, $BlackCherry]]; procList: LIST OF OpsProc ¬ IF msInfo.whichMenu = $readOnly THEN roOpsProcsList ELSE allOpsProcsList; which: INT; which ¬ PopUpSelection.Request["Ops", IF msInfo.whichMenu = $readOnly THEN roPopUpFormsList ELSE allPopUpFormsList]; IF which <= 0 THEN RETURN; -- no selection FOR i: INT IN [1..which) DO IF procList = NIL THEN RETURN; procList ¬ procList.rest; ENDLOOP; IF procList # NIL THEN procList.first[msInfo]; }; AddOpsProc: PUBLIC ENTRY PROC[menuName: ROPE, proc: OpsProc, whichMenu: ATOM ¬ $all] ~ { ENABLE UNWIND => NULL; IF whichMenu # $writeNeeded THEN { IF roOpsProcsList = NIL THEN roOpsProcsList ¬ LIST[proc] ELSE { procList: LIST OF OpsProc ¬ roOpsProcsList; UNTIL procList.rest = NIL DO procList ¬ procList.rest; ENDLOOP; procList.rest ¬ LIST[proc]; }; IF roPopUpFormsList = NIL THEN roPopUpFormsList ¬ LIST[menuName] ELSE { rL: LIST OF ROPE ¬ roPopUpFormsList; UNTIL rL.rest = NIL DO rL ¬ rL.rest; ENDLOOP; rL.rest ¬ LIST[menuName]; }; }; IF allOpsProcsList = NIL THEN allOpsProcsList ¬ LIST[proc] ELSE { procList: LIST OF OpsProc ¬ allOpsProcsList; UNTIL procList.rest = NIL DO procList ¬ procList.rest; ENDLOOP; procList.rest ¬ LIST[proc]; }; IF allPopUpFormsList = NIL THEN allPopUpFormsList ¬ LIST[menuName] ELSE { rL: LIST OF ROPE ¬ allPopUpFormsList; UNTIL rL.rest = NIL DO rL ¬ rL.rest; ENDLOOP; rL.rest ¬ LIST[menuName]; }; }; CreateSendViewerProc: INTERNAL PROC [msInfo: MsgSetInfo] ~ { [] ¬ SendMailOps.CreateSendViewer[fromExec: FALSE]; }; MsgNumInfoProc: INTERNAL PROC [msInfo: MsgSetInfo] ~ { undel: INT ¬ 0; total: INT ¬ 0; FOR msgH: MsgHandle ¬ msInfo.first, msgH.next WHILE ( msgH # NIL ) DO IF ( NOT msgH.deleted ) THEN undel ¬ undel.SUCC; total ¬ total.SUCC; ENDLOOP; InfoMsg[undel, total, msInfo.fileData.readStream]; }; InfoMsg: PROC[undel, total: INT, stream: STREAM] = { kBytes: INT ~ ( stream.GetLength[] + 1023 ) / 1024; Report["\nThere are %g undeleted msgs (%g total); file is %g kBytes\n", [integer[undel]], [integer[total]], [integer[kBytes]] ]; }; ShowAllProc: INTERNAL PROC [msInfo: MsgSetInfo] ~ { selected: MsgHandle ~ msInfo.selected; ClearDisplayer[msInfo]; AddMsgs[msInfo, TRUE]; IF ( selected # NIL ) THEN TiogaButtons.ChangeButtonLooks[button: selected.tocButton, addLooks: tocSelectedLooks]; }; MsgIDProc: INTERNAL PROC [msInfo: MsgSetInfo] ~ { read: IO.STREAM ~ msInfo.fileData.readStream; sel: MsgHandle ~ msInfo.selected; IF ( sel = NIL ) THEN { Report["\nNo selected msg\n"]; RETURN }; IF ( sel.gvID = NIL ) THEN sel.gvID ¬ GetMsgID[msInfo, sel]; Report["\ngvID is: %g\n", [rope[sel.gvID]] ]; }; NewFile: PUBLIC PROC [msInfo: MsgSetInfo] ~ { ClearDisplayer[msInfo]; msInfo.first ¬ msInfo.last ¬ msInfo.selected ¬ NIL; CloseAndCreate[msInfo]; Report["\n\t NewLogFile %g\n", [rope[msInfo.fileName]] ]; }; SortProc: INTERNAL PROC [msInfo: MsgSetInfo] ~ { ExpungeInternal[msInfo, TRUE]; }; ExpungeProc: INTERNAL PROC [msInfo: MsgSetInfo] ~ { ExpungeInternal[msInfo, FALSE]; }; ExpungeInternal: INTERNAL PROC [msInfo: MsgSetInfo, sortem: BOOL] ~ { ENABLE PFS.Error => { Report["\n*** PFS.Error during expunge (%g)\n", [rope[error.explanation]] ]; GOTO didnt; }; newList, newLast: MsgHandle; newLogName: ROPE; kbytes: INT; which: ROPE ¬ "Renamed"; Report["\n*** Starting Expunge @ %g\n\t", [time[BasicTime.Now[]]] ]; MsgNumInfoProc[msInfo]; ClearDisplayer[msInfo]; [newList, newLast, newLogName] ¬ CopyOrExpunge[msInfo, sortem]; IF ( msInfo.fileData.writeStream # NIL ) THEN msInfo.fileData.writeStream.Close[]; IF ( msInfo.fileData.readStream # NIL ) AND ( msInfo.fileData.readStream # msInfo.fileData.writeStream ) THEN msInfo.fileData.readStream.Close[]; msInfo.fileData.readStream ¬ msInfo.fileData.writeStream ¬ NIL; BEGIN PFS.Rename[from: PFS.PathFromRope[newLogName], to: PFSNames.StripVersionNumber[PFS.PathFromRope[msInfo.fileData.name]] ! PFS.Error => GOTO tryCopy]; EXITS tryCopy => { PFS.Copy[from: PFS.PathFromRope[newLogName], to: PFSNames.StripVersionNumber[PFS.PathFromRope[msInfo.fileData.name]], confirmProc: NIL]; which ¬ "Copied"; }; END; msInfo.fileData ¬ fileProcs.openOrCreate[FileNames.StripVersionNumber[msInfo.fileData.name], $open ! OpenFailed => { GOTO Failed }]; kbytes ¬ ( msInfo.fileData.writeStream.GetLength[] + 1023 )/1024; Report["%g ", [rope[which]] ]; Report["%g to %g (%g kbytes)\n", [rope[newLogName]], [rope[msInfo.fileData.name]], [integer[kbytes]] ]; msInfo.first ¬ newList; msInfo.last ¬ newLast; msInfo.selected ¬ NIL; AddMsgs[msInfo, showDels]; IF sortem THEN Report["\n\t Expunge & Sort complete\n"] ELSE Report["\n\t Expunge complete\n"]; EXITS Failed => Report["\n*** Could not open %g after expunge and rename\n", [rope[msInfo.fileData.name]] ]; didnt => NULL; }; CopyOrExpunge: INTERNAL PROC [msInfo: MsgSetInfo, sortem: BOOL] RETURNS[newList, newLast: MsgHandle ¬ NIL, logName: ROPE]~ { ENABLE UNWIND => { ViewerOps.SetMenu[msInfo.viewer, displayerMenu] }; viewer: Viewer ¬ msInfo.viewer; msiData: MsiData ~ NARROW[msInfo.data]; altFileData: BCFileData; logName ¬ UserProfile.Token[key: "BlackCherry.altMailLog", default: NIL]; IF logName = NIL THEN logName ¬ PathInHomeDirectory["BlackCherryAlt.mailLog"]; Report["\n\t using %g as temp file\n", [rope[logName]] ]; [newList, newLast] ¬ DoCopyOrExpunge[msInfo, logName, altFileData ¬ fileProcs.openOrCreate[logName, $create], sortem ]; }; CloseAllProc: ENTRY PROC[msInfo: MsgSetInfo] = { v: Viewer ¬ msInfo.msgDisplayer; tsv: Viewer ¬ ViewerIO.GetViewerFromStream[reportStrm]; IF v # NIL AND NOT v.destroyed AND NOT (ViewerOps.FetchProp[v, $Frozen] # NIL) THEN ViewerOps.DestroyViewer[v]; IF NOT tsv.iconic THEN ViewerOps.CloseViewer[tsv]; ViewerOps.CloseViewer[msInfo.viewer]; }; AppendMsgProc: INTERNAL PROC[msInfo: MsgSetInfo] = { curSel: ROPE = ViewerTools.GetSelectionContents[]; curV: Viewer = ViewerTools.GetSelectedViewer[]; msgID, plainText, formatting: ROPE; IF curSel.Length[] < 2 THEN { tc: ViewerTools.TiogaContents; IF curV = NIL THEN { Report["No selection or selected viewer to append\n"]; RETURN }; msgID ¬ NARROW[ViewerOps.FetchProp[curV, $CherryMsgName]]; tc ¬ SendMailOps.GetCRTiogaContents[curV]; plainText ¬ tc.contents; formatting ¬ tc.formatting; } ELSE plainText ¬ curSel; IF msgID = NIL THEN msgID ¬ GenerateMsgName[plainText]; DoAppendMsg[msInfo, msgID, plainText, formatting]; AddMsgs[msInfo, showDels]; }; GenerateMsgName: PROC[rp: ROPE] RETURNS[ROPE] ~ { name: ROPE ~ IF userRNameList # NIL THEN userRNameList.first.name ELSE "NoName:Parc:Xerox"; RETURN[IO.PutFR["%g $ Selected@%g", [rope[name]], [time[BasicTime.Now[]]]] ]; }; NSToTioga: ENTRY PROC[msInfo: MsgSetInfo] = { input: TiogaAccess.Reader ¬ TiogaAccessViewers.FromSelection[]; output: TiogaAccess.Writer ¬ TiogaAccess.Create[]; DoNSToTiogaProc[input, output]; TiogaAccessViewers.WriteSelection[output]; }; TiogaToNS: ENTRY PROC[msInfo: MsgSetInfo] = { input: TiogaAccess.Reader ¬ TiogaAccessViewers.FromSelection[]; output: TiogaAccess.Writer ¬ TiogaAccess.Create[]; DoTiogaToNSProc[input, output]; TiogaAccessViewers.WriteSelection[output]; }; customProcs: PUBLIC CustomProcs ¬ NIL; RegisterCustomProcs: PUBLIC PROC[procs: CustomProcs] ~ { customProcs ¬ procs; }; GetCustomProcs: PUBLIC PROC[] RETURNS [procs: CustomProcs] ~ { procs ¬ customProcs; }; reportStrm: IO.STREAM; reportName: ROPE; TSStream: PROC [name: ROPE] RETURNS [out: IO.STREAM] ~ { v: ViewerClasses.Viewer ¬ ViewerOps.FindViewer[name]; logName: ROPE ¬ UserProfile.Token["BlackCherry.typescriptLogName", NIL]; out ¬ ViewerIO.CreateViewerStreams[name, v, logName, FALSE].out; IF debugging THEN out.PutF1["\n\t* v = %g\n", [integer[LOOPHOLE[v]]] ]; IF ( v = NIL ) THEN { v ¬ ViewerIO.GetViewerFromStream[out]; tsViewerEventRegistration ¬ ViewerEvents.RegisterEventProc[QuitBC, destroy, v]; TRUSTED { IF debugging THEN out.PutF1["\n\t***register v = %g\n", [integer[LOOPHOLE[v]]] ] }; }; IF ( v#NIL ) THEN IF ( v.iconic ) THEN ViewerOps.OpenIcon[v]; v.inhibitDestroy ¬ TRUE; reportName ¬ name; }; QuitBC: ViewerEvents.EventProc = { TRUSTED { Process.Detach[FORK QuitBCX[viewer] ] }; RETURN[TRUE]; -- we'll destroy it below }; QuitBCX: PROC[viewer: Viewer] = { v: Viewer ¬ ViewerIO.GetViewerFromStream[reportStrm]; IF debugging THEN { reportStrm.PutF["\n\t***destroy v = %g, viewer = %g\n", [integer[LOOPHOLE[v]]] , [integer[LOOPHOLE[viewer]]] ]; reportStrm.PutF1["about to unregister reportStrm: %g\n", [integer[LOOPHOLE[reportStrm]]] ]; }; IF v # viewer THEN RETURN; SendMailOps.UnregisterReporter[reportStrm]; reportStrm.Flush[]; ViewerEvents.UnRegisterEventProc[tsViewerEventRegistration, destroy]; tsViewerEventRegistration ¬ NIL; v.inhibitDestroy ¬ FALSE; ViewerOps.DestroyViewer[v]; }; Report: PUBLIC PROC [format: ROPE, v1, v2, v3: IO.Value ¬ [null[]] ] ~ { ENABLE IO.Error => IF ( ec = StreamClosed ) THEN GOTO reOpen ELSE REJECT; reportStrm.PutF[format, v1, v2, v3]; EXITS reOpen => { v: Viewer; reportStrm ¬ TSStream[reportName]; reportStrm.PutF[format, v1, v2, v3]; v ¬ ViewerIO.GetViewerFromStream[reportStrm]; }; }; GetMsgID: PUBLIC PROC [msInfo: MsgSetInfo, msgH: MsgHandle] RETURNS [id: ROPE]~ { read: IO.STREAM ~ msInfo.fileData.readStream; pos: INT ¬ msgH.entryStart; read.SetIndex[pos]; FOR n: CARD16 IN [0..2) DO WHILE TRUE DO ch: CHAR ~ read.GetChar[]; IF (ch = '\n) OR (ch = '\r) THEN EXIT; pos ¬ pos.SUCC; ENDLOOP; ENDLOOP; id ¬ read.GetLineRope[]; }; ClearDisplayer: PROC [msInfo: MsgSetInfo] ~ { FOR msgH: MsgHandle ¬ msInfo.first, msgH.next WHILE ( msgH # NIL ) DO IF ( msgH.tocButton = NIL ) THEN LOOP; TiogaButtons.DeleteButton[msgH.tocButton]; msgH.tocButton ¬ NIL; ENDLOOP; }; AddMsgs: PROC [msInfo: MsgSetInfo, doShowDels: BOOL] ~ { v: Viewer = msInfo.viewer; v.inhibitDestroy ¬ TRUE; FOR each: MsgHandle ¬ msInfo.first, each.next UNTIL each = NIL DO IF ( each.tocButton # NIL ) THEN LOOP; IF ( ( NOT each.deleted ) OR ( doShowDels ) ) THEN { each.tocButton ¬ TiogaButtons.CreateButton[ viewer: msInfo.tiogaViewer, rope: IF customProcs#NIL AND customProcs.msgButtonText#NIL THEN customProcs.msgButtonText[msInfo, each] ELSE each.toc, format: "header", looks: SELECT TRUE FROM each.deleted => IF ( each.unRead ) THEN tocDelAndUnreadLooks ELSE deletedLooks, each.unRead => tocUnreadLooks, ENDCASE => tocDefaultLooks, proc: MsgSetSelectionProc, clientData: each]; each.msInfo ¬ msInfo; }; ENDLOOP; v.inhibitDestroy ¬ FALSE; }; MsgSetSelectionProc: ENTRY TiogaButtons.TiogaButtonProc ~ { ENABLE UNWIND => NULL; msgH: MsgHandle ~ NARROW[clientData]; ok: BOOL ~ SelectMsgInMsgSet[msgH]; IF NOT ok THEN RETURN; SELECT TRUE FROM ( control AND shift ) => { stream: IO.STREAM ~ msgH.msInfo.fileData.writeStream; IF stream = NIL THEN RETURN; msgH.deleted ¬ FALSE; stream.SetIndex[msgH.charPos]; stream.PutChar[' ]; CheckForFlush[msgH.msInfo]; TiogaButtons.ChangeButtonLooks[button: msgH.tocButton, removeLooks: deletedLooks]; }; ( control AND NOT shift ) => { IF msgH.msInfo.fileData.writeStream = NIL THEN RETURN; ShowAsDeleted[msgH]; }; ( mouseButton # red ) => { DisplayOneMsg[msgH, shift]; }; ENDCASE => { NULL }; }; DisplayOneMsg: PUBLIC PROC [msgH: MsgHandle, grow: BOOL] ~ { LockedSetMenu: PROC ~ { ViewerOps.SetMenu[v, msgMenu] }; read: IO.STREAM ~ msgH.msInfo.fileData.readStream; msInfo: MsgSetInfo ~ msgH.msInfo; v: Viewer ¬ msInfo.msgDisplayer; tc: TiogaContents ~ NEW[TiogaContentsRec]; cherryMsgName: ROPE; painted: BOOL ¬ FALSE; herald: ROPE ~ msgH.toc.Substr[13]; -- after date IF ( msgH.gvID = NIL ) THEN msgH.gvID ¬ GetMsgID[msInfo, msgH]; cherryMsgName ¬ msgH.gvID; [tc.contents, tc.formatting] ¬ GetMsgContents[msInfo, msgH]; IF ( ( v = NIL ) OR ( v.destroyed ) OR ( ViewerOps.FetchProp[v, $Frozen] # NIL ) ) THEN { v ¬ ViewerOps.CreateViewer[flavor: $Text, paint: NOT grow, info: [name: herald, icon: msgIcon, scrollable: TRUE, iconic: FALSE] ]; ViewerLocks.CallUnderWriteLock[LockedSetMenu, v]; msInfo.msgDisplayer ¬ v; ViewerOps.AddProp[v, $BlackCherry, msInfo]; } ELSE { who: Viewer ¬ TiogaOps.GetSelection[feedback].viewer; IF ( who = v ) THEN TiogaOps.CancelSelection[feedback]; v.name ¬ herald; ViewerOps.PaintViewer[v, caption]; { WITH v.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { IF ( tdd.clipLevel # TEditDocument.maxClip ) THEN painted ¬ TRUE; }; ENDCASE => { NULL }; }; }; IF ( ( NOT tc.formatting.IsEmpty[] ) OR ( plainTextStyle.Equal["cedar", FALSE] ) ) THEN { -- Tioga formatted; needn't reset the style ViewerTools.SetTiogaContents[v, tc, NOT painted]; TiogaMenuOps.AllLevels[v]; } ELSE { -- Plain Text ViewerTools.SetTiogaContents[v, tc, FALSE]; TiogaOps.SaveSelB[]; TiogaOps.SelectDocument[viewer: v, level: point]; TiogaOps.SetStyle[plainTextStyle, root]; v.newVersion ¬ FALSE; -- Is there a ``better'' way to do this? ViewerOps.PaintViewer[v, all]; TiogaOps.RestoreSelB[]; }; ViewerTools.InhibitUserEdits[v]; ViewerOps.AddProp[v, $CherryMsgName, cherryMsgName ]; SELECT TRUE FROM ( v.iconic ) => { ViewerOps.OpenIcon[v, grow] }; ( grow ) => { ViewerOps.GrowViewer[v] }; ENDCASE => { NULL }; v.label ¬ herald; ShowAsRead[msgH]; }; ShowAsRead: PROC [msgH: MsgHandle] ~ { IF ( msgH.unRead ) THEN { stream: IO.STREAM ~ msgH.msInfo.fileData.writeStream; IF stream = NIL THEN RETURN; msgH.unRead ¬ FALSE; stream.SetIndex[msgH.charPos.SUCC]; stream.PutChar[' ]; CheckForFlush[msgH.msInfo]; }; TiogaButtons.ChangeButtonLooks[button: msgH.tocButton, removeLooks: tocUnreadLooks]; }; ShowAsDeleted: PROC [msgH: MsgHandle] ~ { IF ( NOT msgH.deleted ) THEN { stream: IO.STREAM ~ msgH.msInfo.fileData.writeStream; IF stream = NIL THEN RETURN; msgH.deleted ¬ TRUE; stream.SetIndex[msgH.charPos]; stream.PutChar['*]; CheckForFlush[msgH.msInfo]; }; TiogaButtons.ChangeButtonLooks[button: msgH.tocButton, addLooks: deletedLooks]; }; SelectMsgInMsgSet: PROC [msgH: MsgHandle] RETURNS[ok: BOOL ¬ TRUE] ~ { msInfo: MsgSetInfo ~ msgH.msInfo; prevSelected: MsgHandle; IF msInfo = NIL THEN { Report["\n*** trying to select msg that is no longer in a displayer\n"]; RETURN[FALSE]; }; IF ( ( prevSelected¬ msInfo.selected ) = msgH ) THEN RETURN; IF ( prevSelected # NIL ) THEN TiogaButtons.ChangeButtonLooks[ button: prevSelected.tocButton, addLooks: tocDefaultLooks, removeLooks: tocSelectedLooks]; TiogaButtons.ChangeButtonLooks[button: msgH.tocButton, addLooks: tocSelectedLooks]; msInfo.selected ¬ msgH; }; AdvanceSelection: PROC [msgH: MsgHandle, skipDeleted: BOOL ¬ TRUE] RETURNS [next: MsgHandle] ~ { next ¬ msgH.next; DO SELECT TRUE FROM ( next = NIL ) => { RETURN }; ( next.tocButton = NIL ) => { NULL }; ( next.deleted AND skipDeleted ) => { NULL }; ENDCASE => { [] ¬ SelectMsgInMsgSet[next]; RETURN }; next ¬ next.next; ENDLOOP; }; DestroyMSViewer: ViewerEvents.EventProc ~ { msInfo: MsgSetInfo ~ NARROW[ViewerOps.FetchProp[viewer, $BlackCherry]]; Shutdown[msInfo]; }; Shutdown: PROC [msInfo: MsgSetInfo] ~ { msgV: Viewer ¬ msInfo.msgDisplayer; v: Viewer ¬ ViewerIO.GetViewerFromStream[reportStrm]; msiData: MsiData ~ NARROW[msInfo.data]; IF ( msInfo.fileData.writeStream # NIL ) THEN msInfo.fileData.writeStream.Close[]; IF ( msInfo.fileData.readStream # NIL ) AND ( msInfo.fileData.readStream # msInfo.fileData.writeStream ) THEN msInfo.fileData.readStream.Close[]; msInfo.fileData.readStream ¬ msInfo.fileData.writeStream ¬ NIL; IF ( ( msgV # NIL ) AND ( NOT msgV.destroyed ) ) THEN ViewerOps.DestroyViewer[msgV]; ViewerEvents.UnRegisterEventProc[msInfo.destroyER, destroy]; v.inhibitDestroy ¬ FALSE; IF msiData.allowNewMail THEN [] ¬ NewP[]; -- Flushes current new mail alert processes Report["\n~~~~~~~~~~ Exiting BlackCherry for log %g\n", [rope[msInfo.fileName]] ]; RemoveFromMsInfoList[msInfo]; }; currentP: NAT ¬ 0; CurrentP: ENTRY PROC RETURNS [NAT] ~ { RETURN [currentP] }; NewP: ENTRY PROC RETURNS [NAT] ~ { RETURN [currentP ¬ currentP.SUCC] }; newMailAlertButton: Buttons.Button; curRH: RetrieveHandle; -- global data ReportChangesProc: TYPE ~ PROC [rh: MailRetrieve.Handle, newState: MailRetrieve.MBXState]; CheckStateClick: Buttons.ButtonProc ~ TRUSTED { Process.Detach[FORK CheckState[]] }; CreateAlertButton: PROC ~ { alertButton: BOOL ~ UserProfile.Boolean["BlackCherry.newMailAlertButton", TRUE]; IF ( ( newMailAlertButton = NIL ) AND ( alertButton ) ) THEN { newMailAlertButton ¬ Buttons.Create[info: [name: "BC-Mail"], proc: CheckStateClick]; }; }; CloseRetrieveHandle: PUBLIC INTERNAL PROC ~ { IF ( curRH # NIL ) AND ( curRH.credentials # NIL ) THEN MailRetrieve.Close[curRH.mHandle]; curRH ¬ NIL; }; EntryMakeAndSetNewHandle: ENTRY PROC [ credentials: LIST OF MailUtils.Credentials, msiData: MsiData, pollingInterval: INT ¬ 300 ] ~ { MakeAndSetNewHandle[credentials, msiData, pollingInterval] }; MakeAndSetNewHandle: PUBLIC INTERNAL PROC [ credentials: LIST OF MailUtils.Credentials, msiData: MsiData, pollingInterval: INT ¬ 300 ] ~ { rh: RetrieveHandle ¬ NEW[RetrieveHandleRec]; rh.credentials ¬ credentials; rh.msiData ¬ msiData; rh.mHandle ¬ MailRetrieve.Create[pollingInterval, WatchMailBox]; FOR tail: LIST OF MailUtils.Credentials ¬ rh.credentials, tail.rest WHILE ( tail # NIL ) DO MailRetrieve.NewUser[rh.mHandle, tail.first.rName, tail.first.password]; ENDLOOP; curRH ¬ rh; }; WatchMailBox: ReportChangesProc ~ { SetMH: ENTRY PROC ~ { IF curRH # NIL THEN { curRH.lastStateReported ¬ newState; }; }; status: ROPE ~ SELECT newState FROM badName => "name invalid, please log in", badPwd => "credentials invalid, please log in", cantAuth => "cannot authenticate credentials at this time", userOK => "credentials authenticated", allDown => NIL, someEmpty => NIL, allEmpty => NIL, notEmpty => NIL, unknown => NIL, ENDCASE => "Bad MBXState!"; IF ( newState = unknown ) THEN RETURN; IF ( status # NIL ) THEN Report["\nmailbox status: %g", [rope[status]]]; SetMH[]; }; GetMsgContents: PUBLIC PROC [msInfo: MsgSetInfo, msgH: MsgHandle] RETURNS [contents, formatting: ROPE] ~ { headerPos, textLen: INT; IF ( msgH.formatLen # 0 ) THEN { headerPos ¬ msgH.headersPos-1; textLen ¬ msgH.textLen+1; } ELSE { headerPos ¬ msgH.headersPos; textLen ¬ msgH.textLen }; contents ¬ ReadRope[msInfo.fileData.readStream, headerPos, textLen]; IF ( msgH.formatLen # 0 ) THEN formatting ¬ ReadRope[msInfo.fileData.readStream, msgH.formatPos, msgH.formatLen]; }; ReadRope: PROC [stream: IO.STREAM, start, len: INT] RETURNS [r: ROPE] ~ { rem: INT ¬ IO.GetLength[stream] - start; IF ( rem < len ) THEN len ¬ rem; IF ( len < 2048 ) THEN TRUSTED { nat: NAT ¬ len; text: Rope.Text ¬ Rope.NewText[nat]; stream.SetIndex[start]; [] ¬ IO.GetBlock[stream, LOOPHOLE[text], 0, nat]; RETURN [text]; }; rem ¬ len / 2; len ¬ len - rem; r ¬ ReadRope[stream, start, len]; r ¬ Rope.Concat[r, ReadRope[stream, start+len, rem] ]; }; BlackCherryFlushMsgs: ENTRY Commander.CommandProc ~ { msInfo: MsgSetInfo ~ GetInforForMail[]; msiData: MsiData; IF msInfo = NIL THEN RETURN; msiData ¬ NARROW[msInfo.data]; msiData.okToFlushMail ¬ TRUE; }; BlackCherryDontFlushMsgs: ENTRY Commander.CommandProc ~ { msInfo: MsgSetInfo ~ GetInforForMail[]; msiData: MsiData; IF msInfo = NIL THEN { nextInfoForMailDontFlush ¬ TRUE; RETURN }; msiData ¬ NARROW[msInfo.data]; msiData.okToFlushMail ¬ FALSE; }; msInfoList: LIST OF MsgSetInfo; nextInfoForMailDontFlush: BOOL ¬ FALSE; CheckForNewMailAllowed: ENTRY PROC RETURNS[newMailAllowed: BOOL] ~ { RETURN[GetInforForMail[] = NIL] }; CheckForFlushMsgs: ENTRY PROC[msiData: MsiData] ~ { IF NOT msiData.allowNewMail THEN RETURN; IF NOT nextInfoForMailDontFlush THEN RETURN; msiData.okToFlushMail ¬ FALSE; nextInfoForMailDontFlush ¬ FALSE; }; GetInforForMail: INTERNAL PROC RETURNS[MsgSetInfo] ~ { FOR mL: LIST OF MsgSetInfo ¬ msInfoList, mL.rest UNTIL mL = NIL DO msiData: MsiData ¬ NARROW[mL.first.data]; IF msiData.allowNewMail THEN RETURN[mL.first]; ENDLOOP; RETURN[NIL]; }; AddToMsInfoList: ENTRY PROC[msInfo: MsgSetInfo] = { msInfoList ¬ CONS[msInfo, msInfoList]; }; RemoveFromMsInfoList: ENTRY PROC[msInfo: MsgSetInfo] = { prev: LIST OF MsgSetInfo; IF msInfoList = NIL THEN RETURN; -- error maybe? FOR mL: LIST OF MsgSetInfo ¬ msInfoList, mL.rest UNTIL mL = NIL DO IF mL.first # msInfo THEN { prev ¬ mL; LOOP }; IF prev = NIL THEN msInfoList ¬ msInfoList.rest ELSE prev.rest ¬ mL.rest; RETURN; ENDLOOP; }; Init: PROC ~ { CreateMenus[]; Commander.Register["BlackCherry", Blackie, "Reading mail for later Cherry consumption"]; Commander.Register["BlackCherryFlushMsgs", BlackCherryFlushMsgs, "Tells BlackCherry to flush messages from the servers"]; Commander.Register["BlackCherryDontFlushMsgs", BlackCherryDontFlushMsgs, "Tells BlackCherry not to flush messages from the servers"]; EntryMakeAndSetNewHandle[credentials, NIL]; }; Init[]; END. 2BlackCherryImpl.mesa Copyright Σ 1987, 1988, 1989, 1990, 1991, 1992 by Xerox Corporation. All rights reserved. adapted from BlackWalnut.mesa of 07-Jun-89 18:02:54 PDT Bill Jackson (bj) July 19, 1989 6:38:17 pm PDT Willie-Sue, April 13, 1990 10:26:04 am PDT Willie-s, August 4, 1993 3:49 pm PDT Copied Types Types { OK, communicationFailure, noSuchServer, connectionRejected, badCredentials, didNotRespond, noMailboxes, noServers, unknownFailure, unknownError }; Fundamental constants interesting parameters Global State BFSImpl Logs from other sources Ops for flushing, etc Useful stuff have to be careful calling between BlackCherryImpl and BlackCherryMailImpl user: ROPE _ UserCredentials.Get[].name; -- need Unix name here IF simpleUserNameList # NIL THEN user _ simpleUserNameList.first ELSE user _ UserCredentials.Get[].name; Command(s) Menus displayerMenu dontAllowNewMailMenu opsProcs readOnlyMenu frozenMsgMenu msgMenu Add item to main per-msgSet menu. Add item to main per-msg menu. MenuProc's (frozen/msg) MenuProc's (displayer) Add item to "Ops" menu that invokes the given procedure when buttoned. Note: there is a one-to-one correspondence between items on the popUpFormsList and the opsProcsList. add it to all the menus "Ops" pop-up methods being lazy Custom procedure registration These procedures are called at various times throughout the processing of a message. Register a complete new set of procedures, replacing previous procedures. If procs is NIL then the default procedures are reinstated. Returns the currently registered set of procedures. Errlog/Viewer - MessageWindow equivilent? so one can debug this code TiogaButtons/Viewer check to see whether AllLevels will cause a paint turn off previous selection xxx locked access to currentP Mail polling & new mail Alert Button this gets called via StartBlackCherryRead, which is an entry proc This is called when the condition of the mailbox changes IF ( curRH.msiData.checkStateReporting THEN { newStateRope: ROPE ~ SELECT newState FROM allDown => "allDown", someEmpty => "someEmpty", allEmpty => "allEmpty", notEmpty => "notEmpty", ENDCASE => "weird MBXState"; Report["==> WatchMailBox: newstate = %g, p = %g\n", [rope[newStateRope]], [cardinal[currentP]] ]; }; Generic Rope ops Short enough to make in one piece The rope is too large to make in one piece, so we divide the length in two and recursviely call ourselves. This gets at least 1024 characters in each piece in a balanced binary tree, so the recursion is quite acceptable even for large messages. Do this first to ensure that the evaluation order is OK. Κ2v•NewlineDelimiter –(cedarcode) style˜šœ™Icodešœ ΟeœO™ZKšœ7™7K™.K™*K™$K™—šΟk ˜ Kšœžœ ˜Kšœ žœžœ˜#K˜ K˜K˜Kšœžœ/˜Kšœ žœ˜Kšœ žœ0˜>KšœžœR˜]Kšžœ˜K˜ Kšœžœ ˜Kšœžœ˜"Kšœ˜Kšœ˜K˜ Kšœ žœ˜(Kšœžœ˜1Kšœ žœI˜ZKšœžœ:˜RKšœ žœo˜Kšœ žœ ˜Kšœ žœ˜Kšœ žœr˜€Kšœ žœ˜#Kšœžœ˜1Kšœ žœH˜ZKšœ žœ,˜:Kšœ žœ’˜‘Kšœ žœ˜'Kšœ žœ!˜2Kšœ žœ˜’K˜—šΟbœžœž˜Kšžœ`žœ!žœα˜ξK˜Kšžœ6˜=Kšœžœžœ7˜CK˜Kšžœžœžœ˜Kšžœžœžœžœ˜K˜KšΟn œžœžœ˜headšΟz ™ Kšœžœ˜$Kšœ žœ˜-Kšœžœ˜0Kšœžœ ˜6—š‘™šœžœžœ˜Kšœ”™”——š‘™Kšœžœ˜)—š‘™šœ žœžœ˜K˜—šœ žœ˜K˜˜K˜K˜K˜ —K˜ —K˜—š‘ ™ Kšœ žœ!˜3Kšœ žœ!˜0K˜KšœžœΟc˜3Kšœ žœ’˜/Kšœžœ’˜5Kšœžœ’˜7Kšœžœ’˜3K˜Kšœžœ’˜0Kšœžœ’˜/K˜Kšœ<žœ˜@—š‘™K˜Kš œžœžœžœ˜K˜š  œžœžœžœ˜ K˜—šœžœ˜K˜—š œžœžœžœ˜:K˜K˜——š‘™K˜š œžœžœ žœžœžœžœ˜pKšœRžœ ˜aKšœ"žœ˜'šž˜šœ ˜ K˜(——K˜K˜—KšœžœH˜Z—˜š œžœžœ žœžœžœžœ˜nKšœRžœ ˜aKšœžœ ˜'šž˜šœ ˜ K˜(——K˜K˜—Lš‘™K˜š  œžœžœ žœ˜<šžœ žœžœ˜Kšœ˜K˜*K˜—K˜K˜—š  œžœ˜,Kšœžœ˜%Kšœžœžœžœ˜2Kšœžœ˜'Kšžœžœžœ˜šžœCžœ˜KKšœ0˜0Kšœ/˜/K˜—K˜K˜—š œžœ˜-šžœ!žœžœ˜/Kšœžœ˜'Kšœ0˜0Kšœ$˜$K˜—Kšžœžœžœžœ>žœ%žœ žœ˜¨K˜CK˜K˜—š  œžœžœ˜2Kšœžœ˜%Kšœžœžœžœ˜2šžœ!žœžœ˜/Kšœžœ˜'Kšœ0˜0Kšœ$˜$K˜—Kš žœ žœžœ>žœ%žœ žœ˜¨Kšœ:žœžœ žœ˜ZK˜K˜—š  œžœžœžœ˜=Kšœžœžœžœ˜2Kšžœžœžœ˜Kšœ*˜*K˜$Kšžœ>žœ%žœ žœ˜Kšœ-˜-K˜AK˜——š‘ ™ š  œžœžœžœžœ ˜KK™JK˜—Kš  œžœžœžœžœ ž˜>K˜š  œžœžœžœ žœ˜HKšœžœ ’™@šžœžœžœ ™@Kšžœ#™'—K˜AKšœ˜K˜—š   œžœ žœžœžœ˜2Kšœžœ+˜3——š‘ ™ š œ˜"Kšžœžœžœžœ$˜Yšœ˜Kšœžœ'˜1Kšœ žœ˜Kšœžœžœ˜Kšœ žœžœ˜Kšœžœ ˜#KšœIžœ˜OK˜0Kšœ žœ’˜%K˜š žœžœBžœžœž˜cšžœžœž˜Kšœžœ’ ˜DKšœ0’˜Dšžœ˜ šžœž˜Kšœ*žœ˜1Kšœ$žœ˜,Kšœ$žœ˜+Kšœžœ˜šœ ˜ Kšœ žœ žœžœ˜C—Kšœžœ˜Kšœžœžœ˜;Kšžœžœ˜—K˜——Kšžœ˜—Kšžœžœžœ žœ˜0Kšžœžœ4˜Bšžœžœ˜KšžœCžœ˜L—šžœžœ˜Kšžœ7˜;—šœ˜Kšœžœ˜K˜%šžœžœžœ˜Kšžœ žœ8žœ ˜[Kšœ$˜$K˜—Kšœ)˜)K˜—Kšžœ žœ=žœ˜gKšœ<žœ ˜K˜K˜ šžœ8žœžœ˜DKšœ˜KšœJ˜JKšž˜K˜—K˜—K˜šœ˜Kšœžœ˜(K˜šžœ žœ˜-Kšžœžœžœžœ&˜K—Kšœ˜K˜XKšœLžœ˜SK˜UK˜YK˜TKšœ_žœ˜fK˜PK˜;Kš œ3žœ žœ žœ!žœ ˜€K˜Kšœ˜šž˜Kšœžœ3˜H—Kšœ˜—Kšœ˜—šž˜K˜ —Kšœ˜K˜—š œžœžœ,žœ˜Ušžœ˜šžœ˜ Kš žœ!žœžœ&žœ žœ˜iKš žœ žœžœ%žœ žœ˜gKšžœžœžœ žœ˜EKšœ˜——Kšœ žœ ˜-Kšœžœ+˜6Kšœžœ˜Kšœ ˜ Kšœžœ˜'šœžœ žœ ˜5Kš žœžœžœžœžœ˜O—Kšœžœžœ˜K˜$š œžœžœžœ˜Tšžœžœž˜Kšœžœžœ˜(Kšœžœžœ˜*Kšœžœžœ˜*Kšžœžœ˜K˜——Kšœ žœH˜XK˜%K˜KšœJ˜JKšœB˜Bšžœžœž˜šžœ ˜KšœN˜NKšž˜Kšœ˜—šžœ ˜ Kšœ:žœ ˜FKšžœ˜ K˜—Kšžœ˜K˜K˜/šžœžœžœ˜Kšœ žœ˜K˜K˜K˜—Kšžœ˜—K˜Kšœ9˜9Kšžœžœ˜.Kšœ|žœ0žœžœ˜ΟKšœOžœ˜UKšœIžœB˜KšœM˜MKšœ.˜.Kšœ.˜.Kšœ+˜+K˜Kšžœžœžœžœ˜9Kšžœ žœ˜'Kšœžœ˜K˜*Kšžœžœžœžœ˜KK˜K˜—š  œžœ˜Kšœžœ ˜K˜Kšœ žœ ’ ˜!K˜ Kšœžœ˜Kšœ žœ˜Kš žœ žœžœžœžœžœ˜?Kšžœ5žœ4˜oK˜K•StartOfExpansion[handle: GVRetrieve.Handle]šœI˜Išžœ’-œž˜IKšœ˜K˜ šžœ žœžœ˜Kšžœ žœg˜zKšžœ˜K˜—Kšžœ žœžœžœ1˜[Kšœžœ˜K˜–[handle: GVRetrieve.Handle]šžœž˜–?[button: Buttons.Button, style: ATOM, paint: BOOL _ TRUE]šœ'˜'Kšœ:˜:Kšœ˜—–?[button: Buttons.Button, style: ATOM, paint: BOOL _ TRUE]šœ ˜ Kšœ:˜:K˜—šœ ˜ Kšœ;˜;K˜—šœ ˜ Kšœ;˜;Kšœ˜—Kšœžœ˜Kšžœžœ˜—Kšœ˜Kšžœ˜—Kšžœ žœ[˜nK˜K˜—š  œžœžœ˜Kšœžœ ˜K˜ Kš žœ žœžœžœžœžœ˜?Kšžœ#žœ:˜cK˜#Kšœ;˜;Kšžœžœ˜.K˜——š‘™K˜+K˜/K˜.K˜6K˜/K˜)K˜š  œžœ˜š‘ ™ Kšœ\˜\KšœP˜PKšœN˜NKšœP˜PKšœJ˜JKšœL˜L—K™š‘™Kšœc˜cKšœW˜WKšœU˜UKšœQ˜QKšœS˜S—š‘™K˜Kšœ1˜1Kšœ/˜/Kšœ)˜)Kšœ%˜%Kšœ0˜0Kšœ1˜1Kšœ3˜3Kšœ)˜)Kšœ)˜)K˜+Kšœ5˜5K˜—š‘ ™ Kšœ‘ œ9˜[Kšœ‘ œ-˜OKšœ‘ œ'˜IKšœ‘ œ)˜K—š‘ ™ š žœžœžœOžœ žœž˜uKšœžœ;˜EKšœx˜xKšžœ˜K˜—KšœS˜SKšœQ˜QKšœM˜MK˜NKšœT˜TKšœT˜T—š‘™KšœK˜Kš žœžœžœOžœ žœž˜uKšœžœ;˜EKšœr˜rKšžœ˜K˜—KšœM˜MKšœK˜KKšœG˜GK˜HKšœN˜NKšœN˜N—Kšœ˜K˜—š œž œ žœ˜GK™!KšœH˜HK˜K™—š  œž œ žœ˜AK™KšœB˜BK˜—K˜—š‘™š  œ˜!Kšœžœ ˜K˜?Kšœ˜Kš žœžœ žœžœžœ˜ Kšœ žœ,˜˜IKš œQžœžœžœžœžœžœ˜xK˜!K˜šžœž˜K˜šžœž˜˜Kšžœžœ˜+Kšžœžœžœ%˜CKšžœ%˜)—K˜Kšœ.žœ˜?˜Kšžœžœ)˜?KšžœA˜E—K˜˜Kšžœžœ˜)šžœ˜K˜K˜'K˜(K˜—K˜—˜Kšžœžœ(˜AKšžœ"žœ˜7K˜—K˜J˜Kšžœžœ,˜BKšžœ7˜;K˜—˜Kšžœžœžœ%˜Mšžœ˜K˜K˜!K˜"K˜—K˜—Kšžœžœ˜—Kšžœ˜—Kšœ˜K˜K˜—š  œžœ˜'Kšœžœ ˜K˜@K˜2Kšœ˜Kšœ"˜"K˜-Kšœ#˜#K˜K˜—š œžœžœ;˜YKš œOžœžœžœžœžœžœ˜vK˜$K˜K˜šžœž˜K˜šžœžœ˜#Kšœžœ˜.K˜ K˜!—K˜K˜Kšœžœ˜K˜Kšžœ˜—Kšœ˜K˜——š‘™Kšœ žœ(˜7K˜š œžœ˜+Kšžœžœžœ˜Kšœžœ,˜Gš žœ+žœžœžœž˜EKšžœžœžœžœ˜"Kšœ*˜*Kšœžœ˜Kšžœ˜—Kšžœžœžœžœžœžœžœ˜dK˜K˜—š  œžœ˜%Kšžœžœžœ˜Kšœžœ,˜GKšœ˜Kšžœ žœžœžœ˜ K˜Kš žœžœ žœžœžœ˜9Kšžœžœ'˜DKš žœžœ žœžœžœ1˜bKšœ˜—K˜š  œžœ˜$Kšžœžœžœ˜Kšœžœ,˜GKšœ˜Kšžœ žœžœžœ˜ K˜Kšžœžœžœ/žœ˜RKšœ˜K˜&šžœžœ˜Kšžœžœžœžœ1˜bK˜—Kšœ˜K˜—š  œžœ˜%Kšžœžœžœ˜Kšœžœ,˜GKšœ˜Kšœ˜K˜—K˜š œ˜Kšœžœ ˜"Kšœžœ,˜Gšœžœ ž˜0Kšœ˜Kšœ˜Kšœ˜Kšžœ ˜—Kšžœ žœžœžœ˜ KšžœžœTžœ žœ˜ŒK˜K˜—Kš œžœžœžœžœ˜&Kš œžœžœžœžœ˜%Kšœžœžœ žœ˜'Kšœžœžœ žœ˜&K˜š  œ˜Kšžœžœžœ˜Kšœžœ ˜ Kšœžœ,˜GKš œ žœžœ žœžœžœ˜eKšœžœ˜ Kšœ&žœžœžœ˜tKšžœ žœžœ’˜+šžœžœžœ ž˜Kšžœ žœžœžœ˜K˜Kšžœ˜—šžœ žœž˜Kšœ˜—K˜K˜—š  œžœ žœžœ ˜XKšœ¬™¬Kšžœžœžœ˜šžœžœ˜#šžœžœžœžœ˜8šžœ˜Kšœ žœžœ˜+Kšžœžœžœžœ˜?Kšœžœ˜K˜——šžœžœžœžœ ˜@šžœ˜Kšœžœžœžœ˜$Kšžœ žœžœžœ˜-Kšœ žœ ˜K˜—K˜——K™šžœžœžœžœ˜:šžœ˜Kšœ žœžœ˜,Kšžœžœžœžœ˜?Kšœžœ˜K˜——šžœžœžœžœ ˜Bšžœ˜Kšœžœžœžœ˜%Kšžœ žœžœžœ˜-Kšœ žœ ˜K˜——K˜K˜——š‘™š œžœžœ˜žœ$˜‘Kšœ;žœ˜?šž˜Kš žœžœ;žœ'žœ žœ ˜”šžœ ˜Kšžœ žœ;žœ3žœ˜‰K˜K˜—Kšžœ˜K˜—Kšœužœ ˜„K˜AKšœ˜Kšœg˜gK˜K˜Kšœžœ˜Kšœ˜šžœžœ)˜7Kšžœ#˜'—šž˜Kšœf˜fKšœ žœ˜—K˜K˜—š  œžœžœžœžœžœ žœ˜|Kšžœžœ8˜EK˜Kšœžœ˜'K˜KšœDžœ˜IK˜Kšžœ žœžœ9˜NKšœ9˜9K˜wKšœ˜K˜—š  œžœžœ˜0K˜ K˜7Kšžœžœžœžœ žœžœ$žœžœ˜oKšžœžœ žœ˜2K˜%K˜K˜—š  œžœžœ˜4Kšœžœ&˜2Kšœ/˜/Kšœžœ˜#K˜šžœžœ˜Kšœ˜šžœžœžœ˜K˜6Kšž˜K˜—Kšœžœ,˜:K˜*K˜K˜K˜Kšžœ˜—K˜Kšžœ žœžœ$˜7Kšœ2˜2Kšœ˜K˜K˜—š  œžœžœžœžœ˜1Kšœ ™ Kš œžœžœžœžœžœ˜[KšžœžœD˜MK˜K˜—š  œžœžœ˜-K˜?K˜2Kšœ˜K˜*K˜K˜—š  œžœžœ˜-K˜?K˜2Kšœ˜K˜*K˜——š‘™šœ žœžœ˜&K™T—K™š œžœžœ˜8Kšœ†™†K˜K˜—K™š œžœžœžœ˜>K™3K˜K˜—K™—š‘)™)Kšœ ž œ˜Kšœ žœ˜K˜š  œžœžœžœž œ˜8K˜5Kšœ žœ6žœ˜HKšœ5žœ˜@Kšžœ žœ&žœ˜Gšžœžœžœžœ˜K˜&K˜OKšžœžœ žœ0žœ ˜]K˜—Kš žœžœžœžœžœ˜=Kšœžœ˜K˜Kšœ˜K˜—š œ˜#Kšžœžœ˜2Kšžœžœ’˜'Kšœ˜K˜—š œ˜!K™K˜5šžœ žœ˜KšœAžœžœ ˜oKšœBžœ˜[K˜—K˜Kšžœ žœžœ˜Kšœ+˜+Kšœ˜KšœE˜EKšœžœ˜ Kšœžœ˜K˜K˜K˜—š  œžœžœ žœžœ˜HKšžœžœ žœžœžœžœžœ˜IKšœ$˜$šž˜šœ ˜ K˜ K˜"Kšœ$˜$K˜-K˜——K˜——š‘™š  œžœžœ'žœžœ˜QKšœžœžœ˜-Kšœžœ˜Kšœ˜š žœžœžœžœžœžœž˜(Kšœžœ˜Kšžœ žœ žœžœ˜'Kšœ žœ˜Kšžœ˜Kšžœ˜—K˜Kšœ˜K˜—š œžœ˜-šžœ+žœ žœž˜EKšžœžœžœžœ˜&Kšœ*˜*Kšœžœ˜Kšžœ˜—K˜K˜—š œžœ!žœ˜8Kšœ˜Kšœžœ˜šžœ+žœžœž˜AKšžœžœžœžœ˜&š žœžœžœžœžœžœ˜4˜+Kšœ˜Kš œžœ žœžœžœžœ)žœ ˜vKšœ˜šœžœžœž˜Kšœžœžœžœ˜OKšœ˜Kšžœ˜—Kšœž˜Kšœ˜—K˜K˜—Kšžœ˜—Kšœžœ˜K˜K˜—š œžœ!˜;Kšžœžœžœ˜Kšœžœ ˜%Kšœžœ˜#Kšžœžœžœžœ˜šžœžœž˜šœ žœ ˜Kšœž œ$˜5Kšžœ žœžœžœ˜Kšœžœ˜Kšœ˜Kšœ˜Kšœ˜KšœR˜RK˜—šœ žœžœ ˜Kšžœ$žœžœžœ˜6Kšœ˜Kšœ˜—šœ˜Kšœ˜Kšœ˜—Kšžœžœ˜—K˜K˜—š  œžœžœžœ˜Kšœ˜Kšœ˜K˜——K˜ Kšœ5˜5šžœžœž˜Kšœ0˜0Kšœ(˜(Kšžœžœ˜—K˜Kšœ˜Kšœ˜K˜—š  œžœ˜'šžœžœ˜Kšœž œ$˜5Kšžœ žœžœžœ˜Kšœžœ˜Kšœžœ˜#Kšœ˜Kšœ˜K˜—KšœT˜TK˜K˜—š  œžœ˜)šžœžœžœ˜Kšœž œ$˜5Kšžœ žœžœžœ˜Kšœžœ˜Kšœ˜Kšœ˜Kšœ˜K˜—KšœO˜OK˜K˜—š  œžœžœžœžœ˜FKšœžœ ˜!Kšœ˜šžœ žœžœ˜K˜HKšžœžœ˜K˜—Kšžœ.žœžœ˜žœ$˜‘Kšœ;žœ˜?Kš žœ žœžœžœžœ˜TKšœ<˜K˜TKšœ˜—Kšœ˜K˜—š œžœžœžœ˜-K™Ašžœ žœžœžœ˜2Kšžœ#˜'—Kšœžœ˜ K˜—K˜š  œžœžœžœžœ;žœ ˜„Kšœ?˜?K˜—š œžœžœžœžœžœ;žœ ˜ŠKšœžœ˜-K˜K˜K˜@š žœžœžœ3žœ žœž˜[KšœH˜HKšžœ˜—K˜ K˜K˜—š  œ˜#Kšœ8™8š œžœžœ˜šžœ žœžœ˜K˜#šžœ%žœ™-šœžœžœ ž™)Kšœ™Kšœ™Kšœ™Kšœ™Kšžœ™—Kšœa™aK™—Kšœ˜—˜K˜——šœžœžœ ž˜#Kšœ)˜)Kšœ/˜/Kšœ;˜;Kšœ&˜&Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜Kšžœ˜—Kšžœžœžœ˜&Kšžœ žœžœ0˜HKšœ˜K˜——š‘™š  œžœžœ'žœžœ˜jKšœžœ˜šžœžœ˜ K˜K˜K˜šžœ˜K˜K˜K˜——K˜Dšžœž˜K˜R—K˜K˜—š  œžœ ž œžœžœžœ˜IKšœžœžœ˜(Kšžœžœ ˜ šžœžœžœ˜ Kšœ!™!Kšœžœ˜K˜$Kšœ˜Kšœžœžœ˜1Kšžœ˜K˜—Kšœυ™υK˜K˜˜!Kšœ8™8—K˜6Kšœ˜K˜—š œžœ˜5Kšœ'˜'K˜Kšžœ žœžœžœ˜Kšœ žœ˜Kšœžœ˜K˜K˜—š œžœ˜9Kšœ'˜'K˜Kš žœ žœžœžœžœ˜BKšœ žœ˜Kšœžœ˜K˜K˜—Kšœ žœžœ ˜šœžœžœ˜'K˜—š  œžœžœžœžœ˜BKšœžœžœ˜$K˜—š œžœžœ˜3Kšžœžœžœžœ˜(Kšžœžœžœžœ˜,Kšœžœ˜Kšœžœ˜!K˜K˜—š œžœžœžœ˜6š žœžœžœ"žœžœž˜BKšœžœ˜)Kšžœžœžœ ˜.Kšžœ˜—Kšžœžœ˜ K˜K˜—š œžœžœ˜1Kšœžœ˜+K˜—š œžœžœ˜8Kšœžœžœ ˜Kš žœžœžœžœ’˜1š žœžœžœ"žœžœž˜BKšžœžœžœ˜.šžœžœžœ˜/Kšžœ˜—Kšžœ˜Kšžœ˜—K˜K˜—š œžœ˜Kšœ˜K˜XK˜yK˜…Kšœ&žœ˜+Kšœ˜K˜——K˜Kšžœ˜K˜—K˜—…—΅†ς.