-- File: WalnutSendControlImpl.mesa -- Contents: Control module for WalnutSend -- Last Edited by: Willie-Sue, January 17, 1984 10:20:02 am PST -- Last Edited by: Donahue, October 7, 1983 9:42 am -- Willie-Sue on: August 23, 1984 12:27:26 pm PDT DIRECTORY Commander USING [CommandProc, Register], DefaultRemoteNames USING [Get], FS USING [ComponentPositions, defaultStreamOptions, Error, OpenFile, StreamOptions, Close, ExpandName, Open, StreamOpen], GVBasics USING [ItemType], GVSend USING [Abort], Icons USING [IconFlavor, NewIconFromFile], InputFocus USING [GetInputFocus], Menus, IO, Process USING [Detach], Rope, TEditSplit USING[ Split ], TiogaMenuOps USING[ tiogaMenu, Empty, Load, Store ], TiogaOps USING [ Ref, Location, CallWithLocks, CancelSelection, Delete, GetSelection, InsertRope, NextPlaceholder], TypeScript USING [Create], ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc], ViewerOps, ViewerIO USING [CreateViewerStreams, GetViewerFromStream], ViewerTools, ViewerClasses, ViewerMenus USING [ Save ], UserCredentials USING [Get], UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc, ListOfTokens, Token], ViewerBLT USING[ ChangeNumberOfLines ], WalnutSendInternal, WalnutSendOps, WalnutVoice USING [AddNuthatchHandleToViewer]; WalnutSendControlImpl: CEDAR MONITOR IMPORTS Commander, DefaultRemoteNames, IO, Process, Rope, FS, GVSend, Icons, InputFocus, Menus, TEditSplit, TiogaMenuOps, TiogaOps, TypeScript, ViewerEvents, ViewerIO, ViewerMenus, ViewerOps, ViewerTools, UserCredentials, UserProfile, ViewerBLT, WalnutSendInternal, WalnutSendOps, WalnutVoice EXPORTS WalnutSendInternal, WalnutSendOps SHARES Menus = BEGIN OPEN WalnutSendOps, WalnutSendInternal; -- Global types & variables userRName: PUBLIC ROPE_ NIL; -- user name with regsitry simpleUserName: PUBLIC ROPE_ NIL; -- user name without registry replyToSelf: PUBLIC BOOL_ FALSE; alwaysOpenSender: BOOL_ FALSE; sendCaption: PUBLIC ROPE_ "Walnut Sender"; destroyAfterSend: BOOL_ FALSE; senderList: LIST OF SenderInfo_ NIL; sendMenu: PUBLIC Menus.Menu_ Menus.CreateMenu[]; formsMenu: PUBLIC Menus.Menu; sendingMenu: PUBLIC Menus.Menu_ Menus.CreateMenu[]; blankMenu: PUBLIC Menus.Menu_ Menus.CreateMenu[]; confirmMenu: PUBLIC Menus.Menu_ Menus.CreateMenu[]; replyToMenu: PUBLIC Menus.Menu_ Menus.CreateMenu[]; sendingSynch: CONDITION; needToAuthenticate: PUBLIC BOOL_ TRUE; iconicSender: BOOL_ TRUE; -- make iconic initially only senderIcon: Icons.IconFlavor_ tool; edittingIcon: Icons.IconFlavor_ tool; reporter: IO.STREAM_ NIL; senderTS: Viewer_ NIL; labelFont: PUBLIC ROPE _ NIL; -- font to use for printing of header labels senderEvent: ViewerEvents.EventRegistration; TiogaCTRL: PUBLIC GVBasics.ItemType; defaultText: PUBLIC ViewerTools.TiogaContents; answerText: PUBLIC ViewerTools.TiogaContents; forwardText: PUBLIC ViewerTools.TiogaContents; standardDefaultText: ViewerTools.TiogaContents = NEW[ ViewerTools.TiogaContentsRec _ [contents: "\nSubject: \001Topic\002\nTo: \001Recipients\002\nCc: \001Copies To\002\n\n\001Message\002\n\n\000", formatting: "\000\000\000\006\000\000\235\312\000<\000\000\002\230\000J\232\002\320bs\007\234\t\230\020J\232\002\235\002\234\016\230\020J\232\002\235\002\234\n\230\017J\230\000J\230\012\227\000\205\227\000\000\000\000\000@\000\000\000\202\000\000"] ]; standardAnswerText: ViewerTools.TiogaContents = NEW[ ViewerTools.TiogaContentsRec _ [contents: "\nSubject: \001Topic\002\nIn-reply-to: \001Message\002\nTo: \001Recipients\002\nCc: \001Copies To\002\n\n\001Message\002\n\n", formatting: "\000\000\000\006\000\000\235\312\000E\000\000\002\230\000J\232\002\320bs\007\234\t\230\020J\232\002\235\013\234\013\230\026J\232\002\235\002\234\016\230\020J\232\002\235\002\234\n\230\017J\230\000J\230\012\227\000\205\227\000\000\000\000\000V\000\000\000\241\000\000"] ]; standardForwardText: ViewerTools.TiogaContents = NEW[ ViewerTools.TiogaContentsRec _ [contents: "\nSubject: \001Topic\002\nTo: \001Recipients\002\nCc: \001Copies To\002\n\n\001CoveringMessage\002\n\n--------------------------------------\nForwardedMessage\n--------------------------------------\n\n\001ClosingComment\002\n", formatting: "\000\000\000\006\000\000\235\312\000W\000\000\002\230\000J\232\002\320bs\007\234\t\230\020J\232\002\235\002\234\016\230\020J\232\002\235\002\234\n\230\017J\230\000J\230\021J\230\000\002\232\001\234&\230&J\230\020\227J\232\001\234&\230&J\230\000J\230\020\227\000\205\227\000\000\000\000\000\270\000\000\001\025\000\000"] ]; defaultForm: WalnutSendOps.Form = NEW[ WalnutSendOps.FormRec _ [ formText: defaultText, fields: LIST[ NIL, NIL, simpleUserName] ] ]; viewerStart: ViewerTools.SelPos = NEW[ViewerTools.SelPosRec_ [0, 0]]; FileStatus: TYPE = {illegalName, local, remote, localNotFound, remoteNotFound, otherError}; -- ************************************************************************ -- Sending messages is independent of CedarDB WalnutSendMail: Commander.CommandProc = { WalnutSendProc[fromExec: TRUE]}; WalnutSendProc: PUBLIC PROC[fromExec: BOOL] = BEGIN senderInfo: SenderInfo; IF fromExec THEN fromExec_ ~alwaysOpenSender; IF ~fromExec THEN iconicSender_ FALSE; senderInfo_ BuildSender[paint: TRUE, iconic: iconicSender, form: defaultForm]; IF ~iconicSender THEN GrabFocus[senderInfo.senderV]; iconicSender_ FALSE; -- iconic only the first time END; GrabFocus: PUBLIC ENTRY PROC[senderV: Viewer] = BEGIN ENABLE UNWIND => NULL; IF senderV.iconic THEN RETURN; ViewerTools.SetSelection[senderV, viewerStart]; [] _ TiogaOps.NextPlaceholder[gotoend: FALSE]; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * BuildSendViewer: PUBLIC PROC[paint, iconic: BOOL, form: WalnutSendOps.Form _ NIL, who: Viewer_ NIL] RETURNS[v: Viewer] = { newForm: WalnutSendOps.Form = IF form # NIL THEN form ELSE defaultForm; RETURN[BuildSender[paint, iconic, newForm, who].senderV] }; BuildSender: ENTRY PROC[paint, iconic: BOOL, form: WalnutSendOps.Form, who: Viewer_ NIL, forceNew: BOOL_ FALSE] RETURNS[senderInfo: SenderInfo] = BEGIN ENABLE UNWIND => IF senderInfo # NIL AND senderInfo.senderV # NIL THEN senderInfo.senderV.inhibitDestroy_ FALSE; senderV: Viewer; IF senderIcon = tool THEN BEGIN ENABLE FS.Error => TRUSTED {GOTO notFound}; senderIcon_ Icons.NewIconFromFile["Walnut.icons", 6]; edittingIcon_ Icons.NewIconFromFile["Walnut.icons", 9]; EXITS notFound => BEGIN ENABLE FS.Error => TRUSTED {CONTINUE}; remote: ROPE_ DefaultRemoteNames.Get[].current; full: ROPE_ Rope.Concat[remote, "Walnut>Walnut.icons"]; senderIcon_ Icons.NewIconFromFile[full, 6]; edittingIcon_ Icons.NewIconFromFile[full, 9]; END; END; IF ~forceNew THEN FOR sL: LIST OF SenderInfo_ senderList, sL.rest UNTIL sL=NIL DO senderInfo_ sL.first; IF ~(senderInfo.senderV.newVersion) AND (senderInfo.sendHandle = NIL) AND ~senderInfo.senderV.destroyed THEN { senderV_ senderInfo.senderV; IF senderV.link # NIL THEN InternalDestroySplits[senderV]; senderV.inhibitDestroy_ TRUE; KillFeedbackSel[senderV]; IF senderV.iconic THEN { ClearOpenEvent[senderInfo]; IF (who # NIL) AND (senderV.column = who.column) THEN { ViewerOps.OpenIcon[icon: senderV, paint: FALSE]; ViewerOps.MoveBelowViewer[altered: senderV, static: who, paint: TRUE] } ELSE ViewerOps.OpenIcon[senderV]; }; IF form # NIL THEN InsertForm[senderInfo, form, TRUE] ELSE TiogaMenuOps.Empty[senderV]; ViewerOps.PaintViewer[senderV, menu]; senderV.inhibitDestroy_ FALSE; RETURN }; ENDLOOP; -- need to create a new sender senderV_ ViewerOps.CreateViewer[ flavor: $Text, info: [ name: sendCaption, column: IF who#NIL THEN who.column ELSE left, iconic: iconic, icon: senderIcon], paint: who = NIL]; IF who # NIL THEN {ViewerOps.MoveBelowViewer[altered: senderV, static: who, paint: FALSE]; ViewerOps.ComputeColumn[column: senderV.column, paint: TRUE] }; senderV.inhibitDestroy_ TRUE; ViewerOps.SetMenu[senderV, sendMenu]; senderInfo_ NEW[SenderInfoObject_ [senderV: senderV]]; ViewerOps.AddProp[senderV, $SenderInfo, senderInfo]; WalnutVoice.AddNuthatchHandleToViewer[senderV]; senderList_ CONS[senderInfo, senderList]; senderInfo.destroyEvent_ ViewerEvents.RegisterEventProc[ proc: DestroySendViewer, event: destroy, filter: senderV, before: TRUE]; senderInfo.closeEvent_ ViewerEvents.RegisterEventProc[ proc: CloseSendViewer, event: close, filter: senderV, before: TRUE]; senderInfo.editEvent_ ViewerEvents.RegisterEventProc[ proc: FirstEditSendViewer, event: edit, filter: senderV, before: TRUE]; IF form # NIL THEN InsertForm[senderInfo, form, TRUE] ELSE TiogaMenuOps.Empty[senderV]; ViewerOps.PaintViewer[senderV, menu]; -- shouldn't be necessary senderV.inhibitDestroy_ FALSE; IF iconic AND ~senderV.iconic THEN ViewerOps.PaintViewer[senderV, all]; END; ClearOpenEvent: PROC[senderInfo: SenderInfo] = BEGIN ra: REF ANY_ ViewerOps.FetchProp[senderInfo.senderV, $WalnutSendOpsForm]; form: WalnutSendOps.Form; IF ra = NIL THEN RETURN; form_ NARROW[ra]; ViewerEvents.UnRegisterEventProc[senderInfo.openEvent, open]; senderInfo.openEvent_ NIL; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * WalnutSendInit: UserProfile.ProfileChangedProc = { uN: ROPE = UserCredentials.Get[].name; pos: INT; defaultFormFile: ROPE = UserProfile.Token[key: "WalnutSend.DefaultForm", default: NIL]; answerFormFile: ROPE = UserProfile.Token[key: "WalnutSend.AnswerForm", default: NIL]; forwardFormFile: ROPE = UserProfile.Token[key: "WalnutSend.ForwardForm", default: NIL]; formContents: ViewerTools.TiogaContents_ NIL; IF (pos_ Rope.SkipTo[s: uN, skip: "."]) = Rope.Length[uN] THEN {simpleUserName_ uN; userRName_ Rope.Cat[uN, ".", defaultRegistry]} ELSE {simpleUserName_ Rope.Substr[uN, 0, pos]; userRName_ uN}; needToAuthenticate_ TRUE; replyToSelf_ UserProfile.Boolean[key: "Walnut.ReplyToSelf", default: FALSE]; destroyAfterSend_ UserProfile.Boolean[key: "Walnut.DestroyAfterSend", default: FALSE]; alwaysOpenSender_ UserProfile.Boolean[key: "WalnutSend.AlwaysOpenSender", default: FALSE]; IF defaultFormFile # NIL THEN formContents _ GetSendForm[defaultFormFile]; IF formContents # NIL THEN { defaultText_ defaultForm.formText_ formContents; defaultForm.fields_ NIL } ELSE { defaultText _ defaultForm.formText _ standardDefaultText; defaultForm.fields_ LIST[NIL, NIL, simpleUserName]}; formContents_ NIL; IF answerFormFile # NIL THEN formContents _ GetSendForm[answerFormFile]; IF formContents # NIL THEN answerText _ formContents ELSE answerText_ standardAnswerText; formContents_ NIL; IF forwardFormFile # NIL THEN formContents _ GetSendForm[forwardFormFile]; IF formContents # NIL THEN forwardText _ formContents ELSE forwardText_ standardForwardText; labelFont _ UserProfile.Token[key: "WalnutSend.labelFont", default: NIL]; SetFormsMenu[]; }; GetSendForm: PROC[ fileName: ROPE ] RETURNS[ text: ViewerTools.TiogaContents ] = BEGIN s: IO.STREAM; so: FS.StreamOptions_ FS.defaultStreamOptions; so[tiogaRead]_ FALSE; s_ FS.StreamOpen[fileName: fileName, streamOptions: so ! FS.Error => { s_ NIL; CONTINUE }]; IF s # NIL THEN { text _ TiogaTextFromStrm[s ! FS.Error => { text _ NIL; CONTINUE }]; s.Close[ ! FS.Error => { s _ NIL; CONTINUE }] } END; DisplayTxtInSender: ENTRY PROC[senderV: Viewer, txt: ROPE] = BEGIN ENABLE UNWIND => NULL; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[senderV, $SenderInfo]]; InternalDisplayTxt[senderInfo, txt]; END; InternalDisplayTxt: INTERNAL PROC[senderInfo: SenderInfo, txt: ROPE] = BEGIN tc: ViewerTools.TiogaContents_ NEW[ViewerTools.TiogaContentsRec]; tc.contents_ txt; InternalDisplayTioga[senderInfo, tc, TRUE]; END; InternalDisplayTioga: INTERNAL PROC[senderInfo: SenderInfo, tc: TiogaContents, grab: BOOL] = BEGIN senderV: Viewer_ senderInfo.senderV; iHadFocus: BOOL_ InputFocus.GetInputFocus[].owner = senderV; IF senderV.link # NIL THEN InternalDestroySplits[senderV]; KillFeedbackSel[senderV]; ViewerTools.SetTiogaContents[senderV, tc]; ViewerOps.PaintViewer[senderV, menu]; -- shouldn't be necessary -- test if I had the focus & no-one else has it now IF grab AND iHadFocus AND InputFocus.GetInputFocus[].owner = NIL THEN { ViewerTools.SetSelection[senderV, viewerStart]; [] _ TiogaOps.NextPlaceholder[gotoend: FALSE] }; UnsetNewVersion[senderV]; senderInfo.successfullySent_ FALSE; END; ClearSendViewer: PUBLIC ENTRY PROC[senderV: Viewer] = { ENABLE UNWIND => NULL; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[senderV, $SenderInfo]]; IF senderInfo = NIL THEN RETURN; -- not a sender InsertForm[senderInfo, defaultForm, TRUE] }; StuffForm: PUBLIC ENTRY PROC[v: Viewer, contents: TiogaContents] = { ENABLE UNWIND => NULL; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[v, $SenderInfo]]; IF senderInfo = NIL THEN RETURN; -- not a sender InternalDisplayTioga[senderInfo, contents, TRUE] }; InsertForm: INTERNAL PROC[ sender: SenderInfo, form: WalnutSendOps.Form, force: BOOL ] = BEGIN senderV: Viewer = sender.senderV; whoHasIt: Viewer; IF senderV.iconic THEN { ViewerOps.AddProp[senderV, $WalnutSendOpsForm, form]; sender.openEvent_ ViewerEvents.RegisterEventProc[ proc: OpenSendViewer, event: open, filter: senderV, before: FALSE]; UnsetNewVersion[senderV]; -- not strictly true but want to reuse RETURN}; -- first stuff the text of the form into the Viewer InternalDisplayTioga[sender, form.formText, FALSE]; IF force OR ((whoHasIt_ InputFocus.GetInputFocus[].owner) = NIL) OR (whoHasIt = senderV) THEN DoPlaceHolders[senderV, form.fields] ELSE { ViewerOps.AddProp[senderV, $WalnutSendOpsFields, form.fields]; sender.focusEvent_ ViewerEvents.RegisterEventProc[ proc: SetFocusInSendViewer, event: setInputFocus, filter: senderV, before: FALSE]; UnsetNewVersion[senderV]; -- not strictly true but want to reuse }; END; DoPlaceHolders: INTERNAL PROC[senderV: Viewer, fieldsList: LIST OF ROPE] = BEGIN AddFields: PROC[ root: TiogaOps.Ref ] = { -- now try to find all of the placeholders in the text and match them with the entries in the fields list FOR rl: LIST OF ROPE _ fieldsList, rl.rest UNTIL rl = NIL DO field: ROPE = rl.first; IF NOT TiogaOps.NextPlaceholder[gotoend: FALSE].found THEN EXIT; IF field = NIL THEN LOOP; TiogaOps.Delete[]; TiogaOps.InsertRope[field] ENDLOOP }; IF senderV.destroyed OR senderV.iconic THEN RETURN; --woops ViewerTools.SetSelection[ senderV, viewerStart]; TiogaOps.CallWithLocks[ AddFields ]; UnsetNewVersion[senderV]; ViewerTools.SetSelection[ senderV, viewerStart]; [] _ TiogaOps.NextPlaceholder[gotoend: FALSE]; END; AddToSendMenu: PUBLIC ENTRY PROC[label: ROPE, proc: Menus.MenuProc] = BEGIN ENABLE UNWIND => NULL; Menus.AppendMenuEntry[sendMenu, Menus.CreateEntry[label, proc]]; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- Menu procedures NewMsgFormProc: ENTRY Menus.MenuProc = BEGIN ENABLE UNWIND => NULL; viewer: Viewer_ NARROW[parent]; NewMsgForm[viewer] END; PrevMsgProc: ENTRY Menus.MenuProc = BEGIN ENABLE UNWIND => NULL; viewer: Viewer_ NARROW[parent]; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; IF viewer.link#NIL THEN InternalDestroySplits[viewer]; ClearFileAssoc[viewer]; IF senderInfo.prevMsg = NIL THEN InsertForm[senderInfo, defaultForm, TRUE] ELSE { KillFeedbackSel[viewer]; ViewerTools.SetTiogaContents[viewer, senderInfo.prevMsg]; ViewerOps.PaintViewer[viewer, menu]; -- shouldn't be necessary UnsetNewVersion[viewer] } END; NewSenderProc: Menus.MenuProc = BEGIN senderInfo: SenderInfo_ BuildSender[paint: TRUE, iconic: FALSE, form: defaultForm, forceNew: TRUE]; GrabFocus[senderInfo.senderV]; END; NewMsgForm: INTERNAL PROC[viewer: Viewer] = BEGIN senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; InsertForm[senderInfo, defaultForm, TRUE]; ClearFileAssoc[viewer]; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * DestroySendViewer: ViewerEvents.EventProc = BEGIN senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; IF senderInfo = NIL THEN RETURN[FALSE]; IF senderInfo.senderV # NIL THEN senderInfo.senderV.file _ NIL; -- make sure that there is no file pointed to so that you don't get something saved in UnSavedDocumentCache IF ViewerOps.FetchProp[viewer, $DestroySenderProc] = NIL THEN RETURN[FALSE]; TRUSTED {Process.Detach[FORK CheckForDestroy[viewer, senderInfo]]}; RETURN[TRUE]; END; OpenSendViewer: ViewerEvents.EventProc = BEGIN OPEN Menus; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; form: WalnutSendOps.Form; ra: REF ANY; IF senderInfo = NIL THEN RETURN[FALSE]; IF (ra_ ViewerOps.FetchProp[viewer, $WalnutSendOpsForm]) = NIL THEN RETURN[FALSE]; form_ NARROW[ra]; ViewerEvents.UnRegisterEventProc[senderInfo.openEvent, open]; senderInfo.openEvent_ NIL; TRUSTED { Process.Detach[FORK EntryInsertForm[senderInfo, form]]}; RETURN[FALSE]; END; EntryInsertForm: ENTRY PROC[senderInfo: SenderInfo, form: WalnutSendOps.Form] = BEGIN ENABLE UNWIND => NULL; InsertForm[senderInfo, form, TRUE]; END; SetFocusInSendViewer: ViewerEvents.EventProc = BEGIN OPEN Menus; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; fieldsList: LIST OF ROPE; ra: REF ANY; IF senderInfo = NIL THEN RETURN[FALSE]; IF (ra_ ViewerOps.FetchProp[viewer, $WalnutSendOpsFields]) = NIL THEN RETURN[FALSE]; fieldsList_ NARROW[ra]; ViewerEvents.UnRegisterEventProc[senderInfo.focusEvent, setInputFocus]; senderInfo.focusEvent_ NIL; TRUSTED { Process.Detach[FORK EntryPlaceHolders[senderInfo, fieldsList]]}; RETURN[FALSE]; END; EntryPlaceHolders: ENTRY PROC[senderInfo: SenderInfo, fieldsList: LIST OF ROPE] = BEGIN ENABLE UNWIND => NULL; DoPlaceHolders[senderInfo.senderV, fieldsList]; END; CloseSendViewer: ViewerEvents.EventProc = BEGIN senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; IF senderInfo = NIL THEN RETURN[FALSE]; IF senderInfo.dontClose THEN { SenderReport[" **** Sender cannot be closed right now\n", TRUE]; RETURN[TRUE] }; viewer.icon_ IF viewer.newVersion THEN edittingIcon ELSE senderIcon; RETURN[FALSE]; END; FirstEditSendViewer: ViewerEvents.EventProc = BEGIN OPEN Menus; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; IF senderInfo = NIL THEN RETURN[FALSE]; SenderNewVersion[viewer]; -- show as guarded RETURN[FALSE]; END; CheckForDestroy: ENTRY PROC[senderV: Viewer, senderInfo: SenderInfo] = BEGIN ENABLE UNWIND => NULL; okToDestroy: BOOL_ TRUE; IF senderV.destroyed THEN RETURN; -- woops IF senderV.newVersion THEN {InternalReport["\nConfirm quitting with last message unsent"]; IF okToDestroy_ SendingConfirm[senderInfo] THEN senderV.newVersion_ FALSE; }; IF ~okToDestroy THEN RETURN; IF senderInfo.destroyEvent # NIL THEN ViewerEvents.UnRegisterEventProc[senderInfo.destroyEvent, destroy]; IF senderInfo.closeEvent # NIL THEN ViewerEvents.UnRegisterEventProc[senderInfo.closeEvent, close]; IF senderInfo.editEvent # NIL THEN ViewerEvents.UnRegisterEventProc[senderInfo.editEvent, edit]; IF senderInfo.openEvent # NIL THEN ViewerEvents.UnRegisterEventProc[senderInfo.openEvent, open]; IF senderInfo.focusEvent # NIL THEN ViewerEvents.UnRegisterEventProc[senderInfo.focusEvent, setInputFocus]; senderInfo.destroyEvent_ NIL; senderInfo.closeEvent_ NIL; senderInfo.editEvent_ NIL; senderInfo.openEvent_ NIL; senderInfo.focusEvent_ NIL; TRUSTED {Process.Detach[FORK ViewerOps.DestroyViewer[senderV]]}; END; LockViewerOpen: PUBLIC ENTRY PROC[viewer: Viewer] = BEGIN ENABLE UNWIND => NULL; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; IF senderInfo = NIL THEN RETURN; senderInfo.dontClose_ TRUE; END; ReleaseOpenViewer: PUBLIC ENTRY PROC[viewer: Viewer] = BEGIN ENABLE UNWIND => NULL; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; IF senderInfo = NIL THEN RETURN; senderInfo.dontClose_ FALSE; END; SenderReport: PUBLIC ENTRY PROC[msg: ROPE, blink: BOOL_ FALSE] = BEGIN ENABLE UNWIND => NULL; InternalReport[msg, blink]; END; InternalReport: INTERNAL PROC[msg: ROPE, blink: BOOL_ FALSE] = BEGIN IF reporter = NIL THEN { senderTS_ TypeScript.Create[ info: [name: "WalnutSend", iconic: FALSE, column: right, openHeight: 64]]; reporter_ ViewerIO.CreateViewerStreams[NIL, senderTS].out; senderEvent_ ViewerEvents.RegisterEventProc[ proc: DestroyReporter, event: destroy, filter: senderTS, before: TRUE]; }; IF blink THEN { viewer: Viewer = ViewerIO.GetViewerFromStream[reporter]; -- NIL for $Dribble class IF viewer # NIL THEN ViewerOps.BlinkIcon[viewer]; }; reporter.PutRope[msg]; END; DestroyReporter: ViewerEvents.EventProc = BEGIN IF senderEvent # NIL THEN ViewerEvents.UnRegisterEventProc[senderEvent, destroy]; senderEvent_ NIL; senderTS_ NIL; reporter_ NIL; RETURN[FALSE]; END; RegisterReporter: PUBLIC ENTRY PROC[strm: IO.STREAM] = BEGIN ENABLE UNWIND => NULL; IF senderTS # NIL THEN { reporter.Close[]; ViewerEvents.UnRegisterEventProc[senderEvent, destroy]; senderEvent_ NIL; ViewerOps.DestroyViewer[senderTS]; senderTS_ NIL; }; reporter_ strm; END; UnregisterReporter: PUBLIC ENTRY PROC[strm: IO.STREAM] = BEGIN ENABLE UNWIND => NULL; IF reporter = strm THEN reporter_ NIL; END; DenySendProc: ENTRY Menus.MenuProc = BEGIN ENABLE UNWIND => NULL; viewer: Viewer_ NARROW[parent]; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; senderInfo.confirmed_ FALSE; senderInfo.userResponded_ TRUE; BROADCAST sendingSynch; END; ConfirmSendProc: ENTRY Menus.MenuProc = BEGIN ENABLE UNWIND => NULL; viewer: Viewer_ NARROW[parent]; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; senderInfo.confirmed_ TRUE; senderInfo.userResponded_ TRUE; BROADCAST sendingSynch; END; AbortSendProc: ENTRY Menus.MenuProc = BEGIN ENABLE UNWIND => NULL; viewer: Viewer_ NARROW[parent]; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; ViewerOps.SetMenu[viewer, blankMenu]; senderInfo.aborted_ TRUE; END; ReplyToSelfProc: ENTRY Menus.MenuProc = BEGIN ENABLE UNWIND => NULL; viewer: Viewer_ NARROW[parent]; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; senderInfo.replyToResponse_ self; senderInfo.userResponded_ TRUE; BROADCAST sendingSynch; END; ReplyToAllProc: ENTRY Menus.MenuProc = BEGIN ENABLE UNWIND => NULL; viewer: Viewer_ NARROW[parent]; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; senderInfo.replyToResponse_ all; senderInfo.userResponded_ TRUE; BROADCAST sendingSynch; END; ReplyToCancelProc: ENTRY Menus.MenuProc = BEGIN ENABLE UNWIND => NULL; viewer: Viewer_ NARROW[parent]; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; senderInfo.replyToResponse_ cancel; senderInfo.userResponded_ TRUE; BROADCAST sendingSynch; END; ---------------------------- ReplyToResponse: PUBLIC ENTRY PROC[senderInfo: SenderInfo] RETURNS [HowToReplyTo] = BEGIN ENABLE UNWIND => NULL; senderV: Viewer_ senderInfo.senderV; ViewerOps.BlinkIcon[senderV, IF senderV.iconic THEN 0 ELSE 1]; UNTIL senderInfo.userResponded DO WAIT sendingSynch; ENDLOOP; senderInfo.userResponded_ FALSE; RETURN[senderInfo.replyToResponse] END; CheckForAbortSend: PUBLIC ENTRY PROC[senderInfo: SenderInfo] RETURNS[BOOL] = BEGIN ENABLE UNWIND => NULL; IF senderInfo.aborted THEN { InternalReport[" Aborting delivery ...\n"]; IF senderInfo.sendHandle#NIL THEN GVSend.Abort[senderInfo.sendHandle]; IF senderInfo.aborted THEN senderInfo.senderV.inhibitDestroy_ FALSE; }; RETURN[senderInfo.aborted]; END; Confirmation: PUBLIC ENTRY PROC[senderInfo: SenderInfo] RETURNS [BOOL] = { ENABLE UNWIND => NULL; RETURN[SendingConfirm[senderInfo]]}; SendingConfirm: INTERNAL PROC[senderInfo: SenderInfo] RETURNS [BOOL] = BEGIN senderV: Viewer_ senderInfo.senderV; oldM: Menus.Menu_ ChangeMenu[senderV, confirmMenu]; ViewerOps.BlinkIcon[senderV, IF senderV.iconic THEN 0 ELSE 1]; UNTIL senderInfo.userResponded DO WAIT sendingSynch; ENDLOOP; ViewerOps.SetMenu[senderV, oldM]; senderInfo.userResponded_ FALSE; RETURN[senderInfo.confirmed]; END; ChangeMenu: PROC[v: Viewer, menu: Menus.Menu] RETURNS [oldM: Menus.Menu] = BEGIN oldM_ v.menu; ViewerOps.SetMenu[v, menu]; END; MessageSendProc: PUBLIC Menus.MenuProc = BEGIN self: Viewer_ NARROW[parent]; iHadFocus: BOOL_ InputFocus.GetInputFocus[].owner = self; ViewerTools.InhibitUserEdits[self]; IF self.link#NIL THEN DestroySplits[self]; TRUSTED {Process.Detach[FORK SendProc[self, mouseButton, iHadFocus]]}; END; SendProc: PROC[viewer: Viewer, mouseButton: Menus.MouseButton, iHadFocus: BOOL] = BEGIN OPEN ViewerOps; sendOK: BOOL; senderInfo: SenderInfo = NARROW[ViewerOps.FetchProp[viewer, $SenderInfo]]; viewer.newVersion_ TRUE; viewer.name_ "Sending ..."; PaintViewer[viewer, caption]; viewer.file_ NIL; sendOK_ WalnutSendOps.Send[v: viewer, doClose: mouseButton=blue]; IF ~sendOK THEN SenderNewVersion[viewer]; viewer.name_ sendCaption; PaintViewer[viewer, caption]; viewer.icon_ senderIcon; ViewerTools.EnableUserEdits[viewer]; IF sendOK THEN { AfterSendInsert: ENTRY PROC = BEGIN ENABLE UNWIND => NULL; InsertForm[senderInfo, defaultForm, FALSE]; END; IF mouseButton=blue AND destroyAfterSend THEN {DestroyViewer[viewer]; RETURN}; UnsetNewVersion[viewer]; AfterSendInsert[]; }; END; SenderSplitProc: Menus.MenuProc = BEGIN self: Viewer = NARROW[parent]; newV: Viewer; nsI, senderInfo: SenderInfo; TEditSplit.Split[self]; -- now find the newest link in the chain to copy properties FOR newV _ self.link, newV.link UNTIL newV.link = self DO ENDLOOP; senderInfo_ NARROW[ViewerOps.FetchProp[self, $SenderInfo]]; ViewerOps.AddProp[newV, $SenderInfo, nsI_ NEW[SenderInfoObject_ senderInfo^]]; nsI.senderV_ newV; nsI.destroyEvent_ ViewerEvents.RegisterEventProc[ proc: DestroySendViewer, event: destroy, filter: newV, before: TRUE]; nsI.closeEvent_ ViewerEvents.RegisterEventProc[ proc: CloseSendViewer, event: close, filter: newV, before: TRUE]; nsI.editEvent_ ViewerEvents.RegisterEventProc[ proc: FirstEditSendViewer, event: edit, filter: newV, before: TRUE]; newV.icon _ self.icon END; DestroySplits: ENTRY PROC[keepThisOne: Viewer] = BEGIN ENABLE UNWIND => NULL; InternalDestroySplits[keepThisOne]; END; InternalDestroySplits: INTERNAL PROC[keepThisOne: Viewer] = BEGIN next: Viewer_ keepThisOne.link; next2: Viewer; sI: SenderInfo; DO IF next = keepThisOne THEN EXIT; sI_ NARROW[ViewerOps.FetchProp[next, $SenderInfo]]; IF sI # NIL THEN { IF sI.destroyEvent # NIL THEN ViewerEvents.UnRegisterEventProc[sI.destroyEvent, destroy]; IF sI.closeEvent # NIL THEN ViewerEvents.UnRegisterEventProc[sI.closeEvent, close]; IF sI.editEvent # NIL THEN ViewerEvents.UnRegisterEventProc[sI.editEvent, edit]; sI.destroyEvent_ NIL; sI.closeEvent_ NIL; sI.editEvent_ NIL; }; next2_ next.link; IF sI # NIL THEN ViewerOps.DestroyViewer[next]; -- DON'T FORK here next_ next2; ENDLOOP; -- make sure this sender is on the list of senders FOR sL: LIST OF SenderInfo_ senderList, sL.rest UNTIL sL=NIL DO IF sL.first.senderV = keepThisOne THEN RETURN; ENDLOOP; sI_ NARROW[ViewerOps.FetchProp[keepThisOne, $SenderInfo]]; IF sI = NIL THEN RETURN; senderList_ CONS[sI, senderList]; END; SenderPlacesProc: Menus.MenuProc = Menus.CopyEntry[ Menus.FindEntry[ TiogaMenuOps.tiogaMenu, "Places" ] ].proc; SenderLevelsProc: Menus.MenuProc = Menus.CopyEntry[ Menus.FindEntry[ TiogaMenuOps.tiogaMenu, "Levels" ] ].proc; SenderGetFormProc: ENTRY Menus.MenuProc = BEGIN ENABLE UNWIND => NULL; self: Viewer = NARROW[parent]; filename: ROPE _ NARROW[clientData]; fileStatus: FileStatus; exp, fName: ROPE; IF filename.Length[] = 0 THEN RETURN; [fileStatus, exp, fName]_ CheckForFile[filename]; IF fileStatus = local OR fileStatus = remote THEN { tc: ViewerTools.TiogaContents_ GetSendForm[fName]; senderInfo: SenderInfo_ NARROW[ViewerOps.FetchProp[self, $SenderInfo]]; InternalDisplayTioga[senderInfo, tc, TRUE]; ClearFileAssoc[self]; } ELSE {InternalReport["\n", TRUE]; InternalReport[exp]; InternalReport["\n"]}; END; SenderGetFileProc: Menus.MenuProc = BEGIN self: Viewer = NARROW[parent]; filename: ROPE _ ViewerTools.GetSelectionContents[]; IF filename.Length[] = 0 THEN RETURN; LoadFile[self, filename]; END; SenderFormsProc: Menus.MenuProc = BEGIN viewer: Viewer = NARROW[parent]; menu: Menus.Menu _ viewer.menu; found: BOOLEAN _ FALSE; firstForm: Menus.MenuEntry = Menus.GetLine[formsMenu, 0]; numLines: Menus.MenuLine = Menus.GetNumberOfLines[menu]; FOR i: Menus.MenuLine IN [1..numLines) DO -- see if already showing the submenu IF firstForm = NIL OR (Menus.GetLine[menu,i] # NIL AND Rope.Equal[Menus.GetLine[menu,i].name, firstForm.name]) THEN { -- yes, so remove it FOR j: Menus.MenuLine IN (i..numLines) DO Menus.SetLine[menu, j-1, Menus.GetLine[menu, j]]; ENDLOOP; menu.linesUsed _ menu.linesUsed-1; found _ TRUE; EXIT }; ENDLOOP; IF ~found THEN { Menus.SetLine[menu, numLines, Menus.GetLine[Menus.CopyMenu[formsMenu], 0]] }; ViewerBLT.ChangeNumberOfLines[viewer, menu.linesUsed]; IF ~found AND viewer.newVersion THEN SenderNewVersion[viewer]; END; SenderStoreProc: Menus.MenuProc = BEGIN self: Viewer_ NARROW[parent]; fileName: Rope.ROPE = ViewerTools.GetSelectionContents[]; status: FileStatus; exp: Rope.ROPE; [status, exp]_ CheckForFile[fileName]; SELECT status FROM local, localNotFound => { TiogaMenuOps.Store[self, fileName]; IF ~self.newVersion THEN UnsetNewVersion[self] }; ENDCASE => BadSaveOrStore[status, exp, fileName]; END; SenderSaveProc: Menus.MenuProc = BEGIN self: Viewer_ NARROW[parent]; status: FileStatus; exp: Rope.ROPE; IF self.file = NIL THEN {SenderReport["\nCan't save - no file name\n"]; RETURN}; [status, exp]_ CheckForFile[self.name]; SELECT status FROM local, localNotFound => { ViewerMenus.Save[ self ]; IF ~self.newVersion THEN UnsetNewVersion[self] }; ENDCASE => BadSaveOrStore[status, exp, self.name]; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * LoadFile: PROC[self: Viewer, filename: ROPE, noName: BOOL_ FALSE] = BEGIN status: FileStatus; s, fName: ROPE; [status, s, fName]_ CheckForFile[filename]; IF status = local OR status = remote THEN { self.file _ NIL; -- make sure that no one else thinks you're saving something TiogaMenuOps.Load[ self, fName ]; ViewerTools.EnableUserEdits[ self ]; -- in case remote file specified IF noName THEN { self.name_ sendCaption; ViewerOps.PaintViewer[self, caption]; self.file_ NIL; -- don't want any file association }; GrabFocus[self]; UnsetNewVersion[self]; } ELSE ErrorReport[s]; END; CheckForFile: PROC[name: ROPE] RETURNS[fileStatus: FileStatus, explanation, fName: ROPE] = BEGIN of: FS.OpenFile; cp: FS.ComponentPositions; explanation_ NIL; fName_ name; BEGIN ENABLE FS.Error => { IF error.group = user THEN { SELECT error.code FROM $illegalName => fileStatus_ illegalName; $unknownFile => fileStatus_ IF fileStatus = local THEN localNotFound ELSE remoteNotFound; ENDCASE => fileStatus_ otherError} ELSE fileStatus_ otherError; explanation_ error.explanation; CONTINUE }; cp_ FS.ExpandName[name, NIL].cp; IF explanation # NIL THEN RETURN; fName_ IF cp.ext.length = 0 THEN name.Concat[".form"] ELSE name; IF cp.server.length = 0 THEN fileStatus_ local ELSE fileStatus_ remote; of_ FS.Open[fName]; IF of # NIL THEN FS.Close[of]; END; END; ErrorReport: ENTRY PROC[exp: ROPE] = { ENABLE UNWIND => NULL; InternalReport["\n", TRUE]; InternalReport[exp]; InternalReport["\n"]; }; BadSaveOrStore: PROC[status: FileStatus, exp, name: ROPE] = BEGIN SELECT status FROM remote, remoteNotFound => ErrorReport[Rope.Cat["Can't store on remote file: \"", name, "\""]]; illegalName, otherError => ErrorReport[exp]; ENDCASE => ERROR; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- bug in SetTiogaContents necessitates this KillFeedbackSel: PROC[v: Viewer] = BEGIN OPEN TiogaOps; who: Viewer_ GetSelection[feedback].viewer; IF who = v THEN CancelSelection[feedback]; END; -- read the user profile line for message forms and add one menu entry for each SetFormsMenu: PROC = BEGIN OPEN Menus; forms: LIST OF ROPE = UserProfile.ListOfTokens[key: "Walnut.MsgForms", default: NIL]; formsMenu_ Menus.CreateMenu[]; FOR fl: LIST OF ROPE _ forms, fl.rest UNTIL fl = NIL DO form: ROPE _ fl.first; shortName, fullName: ROPE; cp: FS.ComponentPositions; [fullName, cp, ]_ FS.ExpandName[form, NIL]; -- if the file is a remote one, then strip off the leading server/directory info shortName_ fullName.Substr[cp.base.start, cp.base.length]; -- add ".form" if one does not exist IF cp.ext.length = 0 THEN form_ form.Concat[".form"]; Menus.AppendMenuEntry[formsMenu, CreateEntry[shortName, SenderGetFormProc, form]] ENDLOOP; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- Start code: create menus BEGIN OPEN Menus; AppendMenuEntry[sendMenu, CreateEntry[name: "Send", proc: MessageSendProc, fork: FALSE]]; AppendMenuEntry[sendMenu, CreateEntry["Get", SenderGetFileProc]]; AppendMenuEntry[sendMenu, CreateEntry[name: "Store", proc: SenderStoreProc, guarded: TRUE]]; AppendMenuEntry[sendMenu, CreateEntry["Save", SenderSaveProc]]; AppendMenuEntry[sendMenu, CreateEntry["Forms", SenderFormsProc]]; AppendMenuEntry[sendMenu, CreateEntry["New", NewSenderProc]]; AppendMenuEntry[sendMenu, CreateEntry["Default", NewMsgFormProc]]; AppendMenuEntry[sendMenu, CreateEntry["PrevMsg", PrevMsgProc]]; AppendMenuEntry[sendMenu, CreateEntry["Split", SenderSplitProc]]; AppendMenuEntry[sendMenu, CreateEntry["Places", SenderPlacesProc]]; AppendMenuEntry[sendMenu, CreateEntry["Levels", SenderLevelsProc]]; AppendMenuEntry[sendingMenu, CreateEntry["StopSending!", AbortSendProc]]; AppendMenuEntry[confirmMenu, CreateEntry["Confirm", ConfirmSendProc]]; AppendMenuEntry[confirmMenu, CreateEntry["Deny", DenySendProc]]; AppendMenuEntry[replyToMenu, CreateEntry["Self", ReplyToSelfProc]]; AppendMenuEntry[replyToMenu, CreateEntry["All", ReplyToAllProc]]; AppendMenuEntry[replyToMenu, CreateEntry["Cancel", ReplyToCancelProc]]; END; ---------------------------- -- start code; make menu and register ourselves TRUSTED { TiogaCTRL _ LOOPHOLE[1013B, GVBasics.ItemType] }; Commander.Register["WalnutSend", WalnutSendMail, "Mail sending tool"]; UserProfile.CallWhenProfileChanges[WalnutSendInit]; END. Ê(+˜J•StartOfExpansion[]šÏc#˜#Jš*˜*J˜Jš?˜?Jš3˜3Jš3˜3J˜šÏk ˜ Jšœ žœ˜(Jšœžœ˜Jšžœžœx˜€Jšœ žœ ˜Jšœžœ ˜Jšœžœ˜*Jšœ žœ˜!Jšœ˜Jšžœ˜Jšœžœ ˜Jšœ˜Jšœ žœ ˜Jšœ žœ"˜4Jšœ žœj˜xJšœ žœ ˜Jšœ žœH˜ZJšœ ˜ Jšœ žœ,˜:Jšœ ˜ Jšœ˜Jšœ žœ ˜Jšœžœ˜Jšœ žœR˜cJšœ žœ˜'Jšœ˜Jšœ˜Jšœ žœ˜.—J˜šœžœž˜$šž˜Jšœžœžœ˜5Jšœ˜Jšœ˜Jšœ/˜/Jšœ<˜˜IJšœ˜Jšžœžœžœžœ˜Jšœžœ˜Jšœ=˜=Jšœžœ˜—Jšžœ˜—J˜JšX˜XJ˜šŸœ"˜0šœžœ˜)Jšœžœ˜ Jšœžœ=žœ˜WJšœžœ<žœ˜UJšœžœ=žœ˜WJšœ)žœ˜-J˜šžœ7˜9JšžœD˜HJšžœ:˜>—Jšœžœ˜JšœEžœ˜LJšœOžœ˜Všœ˜JšœAžœ˜I—Jšžœžœžœ-˜Jšžœžœž˜JšœGžœ˜LJšž˜šœ;˜;Jšœžœžœžœ˜4——J˜Jšœžœ˜Jšžœžœžœ,˜Hšžœžœžœ˜4Jšžœ ˜$—J˜Jšœžœ˜Jšžœžœžœ-˜Jšžœžœžœ˜5Jšžœ"˜&—J˜JšœDžœ˜IJšœ˜—Jšœ˜—J˜šŸ œžœ žœžœ%˜Pšž˜Jšœžœžœ˜ Jšœ.˜.Jšœžœ˜Jšœ9žœžœžœ˜[šžœžœž˜šœžœžœžœ˜EJšœ žœžœžœ˜/———Jšžœ˜—J˜šŸœžœžœžœ˜<šžœžœžœžœ˜Jšœžœ,˜JJšœ$˜$—Jšžœ˜—J˜šŸœžœžœžœ˜Fšž˜Jšœžœ˜AJšœ˜Jšœ%žœ˜+—Jšžœ˜—J˜šŸœžœžœ2žœ˜\šž˜Jšœ$˜$Jšœ žœ-˜šž˜šžœ žœž˜šœ˜Jšœ$žœ"˜KJšœ'žœ˜:šœ,˜,JšœBžœ˜H——Jšœ˜—šžœž˜ šœ;˜TJšžœ žœžœ˜1—Jšœ˜—Jšœ˜—Jšžœ˜—J˜šŸœ˜)šž˜Jšžœžœžœ8˜QJšœ žœ˜Jšœ žœ˜Jšœ žœ˜Jšžœžœ˜—Jšžœ˜—J˜š Ÿœžœžœžœžœžœ˜6šžœžœžœžœ˜šžœ žœž˜šœ˜Jšœ7˜7Jšœ žœ˜Jšœ"˜"Jšœ žœ˜—Jšœ˜—Jšœ˜—Jšžœ˜—J˜š Ÿœžœžœžœžœžœ˜8šžœžœžœžœ˜Jšžœžœ žœ˜&—Jšžœ˜—J˜šŸ œžœ˜$šžœžœžœžœ˜Jšœžœ ˜Jšœžœ+˜IJšœžœ˜Jšœžœ˜Jšž œ˜—Jšžœ˜—J˜šŸœžœ˜'šžœžœžœžœ˜Jšœžœ ˜Jšœžœ+˜IJšœžœ˜Jšœžœ˜Jšž œ˜—Jšžœ˜—J˜šŸ œžœ˜%šžœžœžœžœ˜Jšœžœ ˜Jšœžœ+˜IJšœ%˜%Jšœžœ˜—Jšžœ˜—J˜šŸœžœ˜'šžœžœžœžœ˜Jšœžœ ˜Jšœžœ+˜IJšœ!˜!Jšœžœ˜Jšž œ˜—Jšžœ˜—J˜šŸœžœ˜&šžœžœžœžœ˜Jšœžœ ˜Jšœžœ+˜IJšœ ˜ Jšœžœ˜Jšž œ˜—Jšžœ˜—J˜šŸœžœ˜)šžœžœžœžœ˜Jšœžœ ˜Jšœžœ+˜IJšœ#˜#Jšœžœ˜Jšž œ˜—Jšžœ˜—J˜Jš˜š Ÿœžœžœžœžœ˜Sšžœžœžœžœ˜Jšœ$˜$Jšœžœžœžœ˜>Jšžœžœžœžœ˜=Jšœžœ˜ Jšžœ žœ˜*——J˜š Ÿœžœžœžœžœžœ˜Lšžœžœžœžœ˜šžœž˜šœ-˜-Jšžœžœžœ%˜FJšžœžœ$žœ˜D—Jšœ˜—Jšžœ˜—Jšžœ˜—J˜š Ÿ œžœžœžœžœžœ˜HJš œžœžœžœžœ˜>—J˜š Ÿœžœžœžœžœ˜Fšž˜Jšœ$˜$Jšœ3˜3Jšœžœžœžœ˜>Jšžœžœžœžœ˜=Jšœ!˜!Jšœžœ˜ Jšžœ˜—Jšžœ˜—J˜šŸ œžœžœ˜Jšž˜Jšœ ˜ Jšœ˜—Jšžœ˜—J˜šŸœžœ˜(šž˜Jšœžœ ˜Jšœ žœ*˜9Jšœ#˜#Jšžœ žœžœ˜*Jšžœžœ*˜F—Jšžœ˜—J˜šŸœžœ<žœ˜Qšžœžœ ˜Jšœžœ˜ Jšœžœ+˜JJšœžœ˜Jšœ˜Jšœ˜Jšœ žœ˜J˜JšœA˜AJšžœ žœ˜)J˜Jšœ˜Jšœ˜Jšœ˜Jšœ$˜$šžœž˜šœŸœžœžœ˜šžœžœžœžœ˜Jšœ$žœ˜+—Jšžœ˜Jšžœžœžœžœ˜NJšœ˜Jšœ˜—Jšœ˜——Jšžœ˜—J˜šŸœ˜!šž˜Jšœžœ ˜Jšœ ˜ Jšœ˜Jšœ˜—š;˜;Jšžœžœžœžœ˜BJ˜Jšœ žœ)˜;Jšœ*žœ!˜NJ˜Jšœ˜šœ1˜1Jšœ@žœ˜F—šœ/˜/Jšœ<žœ˜B—šœ.˜.Jšœ?žœ˜E—Jšœ˜—Jšžœ˜—J˜šŸ œžœžœ˜0šžœžœžœžœ˜Jšœ#˜#—Jšžœ˜—J˜šŸœžœžœ˜;šž˜Jšœ˜Jšœ˜Jšœ˜šž˜Jšžœžœžœ˜ Jšœžœ)˜3šžœžœž˜šœžœžœž˜Jšœ<˜—Jšžœ˜—J˜šŸœ˜!šž˜Jšœžœ ˜Jšœžœ&˜9Jšœ˜Jšœ žœ˜Jšœ&˜&šžœž˜šœ˜šœ%˜%Jšžœžœ˜1———Jšžœ*˜1—Jšžœ˜—J˜šŸœ˜ šž˜Jšœžœ ˜Jšœ˜Jšœ žœ˜Jšžœ žœžœ1žœ˜PJ˜Jšœ'˜'šžœž˜šœ˜Jšœžœžœ˜M——Jšžœ+˜2—Jšžœ˜—J˜JšH˜HJ˜š Ÿœžœžœ žœžœ˜Cšž˜Jšœ˜Jšœ žœ˜J˜Jšœ+˜+šžœžœž˜)šœžœ<˜OJšœ!˜!Jšœ& ˜Fšžœž˜šœ˜Jšœ%˜%Jšœ žœ"˜3—Jšœ˜—Jšœ˜Jšœ˜—Jšœ˜—Jšžœ˜—Jšžœ˜—J˜š Ÿ œžœžœžœ-žœ˜Zšž˜Jšœžœ ˜Jšœžœ˜Jšœ žœ˜Jšœ ˜ šžœžœžœ ˜šœžœž˜šœžœ ž˜Jšœ(˜(šœ˜Jšœ žœžœžœ˜I—Jšžœ˜"—Jšžœ˜Jšœ˜Jšž˜—Jšœ˜—J˜Jšœžœžœ˜ Jšžœžœžœžœ˜!J˜Jšœžœžœžœ˜@Jšžœžœžœ˜GJšœžœ ˜Jšžœžœžœžœ ˜Jšžœ˜—Jšžœ˜—J˜šŸ œžœžœžœ˜$šœžœžœžœ˜Jšœžœ˜Jšœ˜Jšœ˜—Jšœ˜—J˜šŸœžœ žœ˜;šž˜šžœž˜šœ˜JšœD˜D—Jšœ,˜,—Jšžœžœ˜—Jšžœ˜J˜—JšH˜HJš-˜-J˜šŸœžœ ˜"šžœžœ ˜Jšœ+˜+Jšžœ žœ˜*—Jšžœ˜—J˜JšO˜OJ˜šŸ œžœ˜šžœžœ˜Jš œžœžœžœ=žœ˜UJšœ˜J˜š žœžœžœžœžœžœž˜7Jšœžœ ˜Jšœžœ˜Jšœžœ˜J˜Jšœžœžœ˜+—šP˜PJšœ:˜:—š$˜$Jšžœžœ˜5J˜JšœQ˜Q—Jšžœ˜—Jšžœ˜—J˜JšH˜HJš˜J˜šž˜Jšžœ˜ J˜JšœSžœ˜[JšœA˜AJšœWžœ˜^Jšœ?˜?JšœA˜AJšœ=˜=JšœB˜BJšœ?˜?JšœA˜AJšœC˜CJšœC˜CJ˜JšœI˜IJ˜JšœF˜FJšœ@˜@J˜JšœC˜CJšœA˜AJšœG˜G—Jšžœ˜J˜Jš˜Jš0˜0J˜Jšžœžœ˜;J˜JšœF˜FJšœ3˜3J˜Jšžœ˜—…—…έÿ