DIRECTORY Atom, EditSpan, InputFocus, Interminal, Menus, MessageWindow, NodeProps, Process, Rope, TEditDocument, TEditDocumentPrivate, TEditMouseImpl, TEditSelection, TextEdit, TextLooks, TextNode, TiogaButtons, TiogaExtraOps, TiogaFileOps, TiogaOps, TIPUser, ViewerClasses, ViewerOps; TiogaButtonsImpl: CEDAR PROGRAM IMPORTS Atom, EditSpan, InputFocus, MessageWindow, NodeProps, Process, Rope, TEditDocumentPrivate, TEditMouseImpl, TEditSelection, TextEdit, TextLooks, TextNode, TiogaButtons, TiogaExtraOps, TiogaFileOps, TiogaOps, ViewerOps EXPORTS TiogaButtons SHARES TEditMouseImpl~ { OPEN TiogaButtons; ROPE: TYPE ~ Rope.ROPE; TiogaButtonAtom: ATOM ~ $TiogaButtonList; TiogaButtonsAtom: ATOM ~ $TiogaButtons; CreateViewer: PUBLIC PROC [info: ViewerClasses.ViewerRec] RETURNS [v: ViewerClasses.Viewer] ~ { v _ ViewerOps.CreateViewer[flavor: TiogaButtonsAtom, info: info]; }; LoadViewer: PUBLIC PROC [viewer: ViewerClasses.Viewer, fileName: ROPE] ~ { name: ROPE; IF viewer = NIL OR viewer.class.flavor # TiogaButtonsAtom THEN ERROR WrongViewerClass; name _ viewer.name; -- DoLoadFile will change this on us [] _ TEditDocumentPrivate.DoLoadFile[parent: viewer, fileName: fileName]; viewer.tipTable _ viewer.class.tipTable; -- DoLoadFile does violence to this too! viewer.name _ name; ViewerOps.PaintViewer[viewer: viewer, hint: caption]; }; CreateButton: PUBLIC PROC [viewer: ViewerClasses.Viewer, rope: ROPE _ NIL, format: ROPE _ NIL, looks: ROPE _ NIL, proc: TiogaButtonProc _ NIL, clientData: REF ANY _ NIL, fork: BOOLEAN _ TRUE] RETURNS [button: TiogaButton] ~ { IF viewer = NIL OR viewer.class.flavor # TiogaButtonsAtom THEN ERROR WrongViewerClass; { root: TiogaOps.Ref _ TiogaOps.ViewerDoc[viewer]; LockedCreateButton: PROC [root: TiogaOps.Ref] ~ { node: TiogaOps.Ref ~ CreateNode[root, rope]; IF NOT format.IsEmpty THEN SetFormat[node, format]; IF NOT looks.IsEmpty THEN FOR i: INT IN [0..looks.Length) DO AddLooks[node, 0, TextNode.MaxLen, looks.Fetch[i], root]; ENDLOOP; button _ NEW[TiogaButtonRec _ [startLoc: [node, NodeItself], endLoc: [node, NodeItself], proc: proc, clientData: clientData, fork: fork]]; IF proc # NIL THEN AddButtonProp[node, button] ELSE RemButtonProp[node]; }; CallWithLock[LockedCreateButton, root] }; }; WrongViewerClass: PUBLIC ERROR = CODE; CreateButtonFromNode: PUBLIC PROC [node: TiogaOps.Ref, start: TiogaOps.Offset _ 0, end: TiogaOps.Offset _ LAST[TiogaOps.Offset], proc: TiogaButtonProc _ NIL, clientData: REF ANY _ NIL, fork: BOOLEAN _ TRUE] RETURNS [button: TiogaButton] ~ { root: TiogaOps.Ref ~ TiogaOps.Root[node]; ref: REF ANY _ TiogaOps.GetProp[root, $Viewer]; viewer: ViewerClasses.Viewer _ IF ref # NIL AND ISTYPE[ref, ViewerClasses.Viewer] THEN NARROW[ref] ELSE NIL; IF viewer = NIL OR viewer.class.flavor # TiogaButtonsAtom THEN ERROR WrongViewerClass; { LockedCreateButtonFromNode: PROC [root: TiogaOps.Ref] ~ { button _ NEW[TiogaButtonRec _ [startLoc: [node, start], endLoc: [node, end], proc: proc, clientData: clientData, fork: fork]]; IF proc # NIL THEN AddButtonProp[node, button] ELSE RemButtonProp[node]; }; IF start = 0 AND end = LAST[TiogaOps.Offset] THEN start _ end _ NodeItself; IF node # NIL THEN CallWithLock[LockedCreateButtonFromNode, root] }; }; AppendToButton: PUBLIC PROC [button: TiogaButton, rope: ROPE _ NIL, looks: ROPE _ NIL, proc: TiogaButtonProc _ NIL, clientData: REF ANY _ NIL, fork: BOOLEAN _ TRUE] RETURNS [TiogaButton] ~ { node: TiogaOps.Ref ~ button.startLoc.node; root: TiogaOps.Ref ~ TiogaOps.Root[node]; LockedAppendToButton: PROC [root: TiogaOps.Ref] ~ { lookVector: TextLooks.Looks ~ TextLooks.RopeToLooks[looks]; start, length: TiogaOps.Offset; [start, length] _ AppendRopeToNode[root, node, rope, lookVector]; button _ NEW[TiogaButtonRec _ [startLoc: [node, start], endLoc: [node, start+length-1], proc: proc, clientData: clientData, fork: fork]]; IF proc # NIL THEN AddButtonProp[node, button]; }; CallWithLock[LockedAppendToButton, root]; RETURN [button]; }; DeleteButton: PUBLIC PROC [button: TiogaButton] ~ { IF button # NIL THEN { root: TiogaOps.Ref ~ TiogaOps.Root[button.startLoc.node]; LockedDeleteButton: PROC [root: TiogaOps.Ref] ~ { EditSpan.Delete[ root: TextNodeRef[root], del: [TextNodeLoc[button.startLoc], TextNodeLoc[button.endLoc]]]; IF button.startLoc.where # NodeItself THEN AdjustButtonProps[button]; }; CallWithLock[LockedDeleteButton, root]; }; }; GetRope: PUBLIC PROC [button: TiogaButton] RETURNS [rope: ROPE] ~ { IF button # NIL THEN { rope _ TiogaOps.GetRope[button.startLoc.node]; IF button.startLoc.where # NodeItself THEN rope _ rope.Substr[button.startLoc.where, button.endLoc.where-button.startLoc.where+1]; }; }; SetStyleFromRope: PUBLIC PROC [v: ViewerClasses.Viewer, styleRope: ROPE] ~ { root: TextNode.Ref ~ TextNodeRef[TiogaOps.ViewerDoc[v]]; TextEdit.PutProp[node: root, name: "StyleDef", value: NodeProps.DoSpecs[$StyleDef, styleRope], root: root]; }; CallWithLock: PROC [proc: PROC [root: TiogaOps.Ref], root: TiogaOps.Ref] ~ { lockedDoc: BOOL _ FALSE; Cleanup: PROC ~ { IF lockedDoc THEN TiogaOps.Unlock[root]; }; { ENABLE UNWIND => Cleanup; TiogaOps.Lock[root]; lockedDoc _ TRUE; proc[root]; }; Cleanup; MarkViewerNotEdited[root]; }; MarkViewerNotEdited: PROC [root: TiogaOps.Ref] ~ { DoOne: PROC [v: ViewerClasses.Viewer] ~ { v.newVersion _ FALSE; ViewerOps.PaintViewer[viewer: v, hint: caption]; }; v: ViewerClasses.Viewer; ref: REF ANY _ TiogaOps.GetProp[root, $Viewer]; IF ref # NIL AND ISTYPE[ref, ViewerClasses.Viewer] THEN v _ NARROW[ref]; IF v # NIL AND v.newVersion THEN { DoOne[v]; IF v.link # NIL THEN FOR x: ViewerClasses.Viewer _ v.link, x.link UNTIL x=v DO DoOne[x]; ENDLOOP; }; }; CreateNode: PROC [root: TiogaOps.Ref, rope: ROPE, prevLast: TiogaOps.Ref _ NIL] RETURNS [node: TiogaOps.Ref] ~ { IF TiogaOps.GetProp[root, $InitialTiogaButtons] # NIL THEN { TiogaExtraOps.RemProp[root, $InitialTiogaButtons]; node _ TiogaOpsRef[TextNodeRef[root].child]; } ELSE { node _ TiogaOpsRef[EditSpan.Split[TextNodeRef[root], [TextNode.LastChild[TextNodeRef[root]], NodeItself]]]; SetFormat[node, ""]; }; [] _ TextEdit.InsertRope[root: TextNodeRef[root], dest: TextNode.NarrowToTextNode[TextNodeRef[node]], rope: rope, destLoc: TextNode.MaxLen]; }; SetFormat: PROC [node: TiogaOps.Ref, format: ROPE] ~ TRUSTED { TiogaFileOps.SetFormat[TiogaFileOpsRef[node], format]; }; AddLooks: PROC [node: TiogaOps.Ref, start, len: INT, look: CHAR ['a..'z], root: TiogaOps.Ref _ NIL] ~ TRUSTED { TiogaFileOps.AddLooks[TiogaFileOpsRef[node], start, len, look, TiogaFileOpsRef[root]]; }; AddButtonProp: PROC [node: TiogaOps.Ref, button: TiogaButton] ~ { list: TiogaButtonList _ NIL; ref: REF ANY _ TiogaOps.GetProp[node, TiogaButtonAtom]; IF ref # NIL AND ISTYPE[ref, TiogaButtonList] THEN list _ NARROW[ref]; IF list = NIL THEN list _ LIST[button] ELSE list _ CONS[button, list]; TiogaOps.PutProp[node, TiogaButtonAtom, list]; }; RemButtonProp: PROC [node: TiogaOps.Ref] ~ { TiogaExtraOps.RemProp[node, TiogaButtonAtom]; }; AdjustButtonProps: PROC [button: TiogaButton] ~ { buttonDelta: INTEGER ~ button.endLoc.where - button.startLoc.where + 1; propListModified: BOOLEAN _ FALSE; propList, list, last: TiogaButtonList; ref: REF ANY ~ TiogaOps.GetProp[button.startLoc.node, TiogaButtonAtom]; IF ref = NIL OR ~ISTYPE[ref, TiogaButtonList] THEN RETURN; propList _ list _ NARROW[ref]; WHILE list # NIL DO t: TiogaButton ~ list.first; IF t.startLoc.node = button.startLoc.node AND t.startLoc.where # NodeItself THEN { t.endLoc.where _ t.endLoc.where+1; IF t.startLoc.where IN [button.startLoc.where .. button.endLoc.where] THEN t.startLoc.where _ button.startLoc.where ELSE IF t.startLoc.where > button.endLoc.where THEN t.startLoc.where _ t.startLoc.where - buttonDelta; IF t.endLoc.where IN [button.startLoc.where .. button.endLoc.where] THEN t.endLoc.where _ button.startLoc.where ELSE IF t.endLoc.where > button.endLoc.where THEN t.endLoc.where _ t.endLoc.where - buttonDelta; IF t.startLoc.where # t.endLoc.where THEN t.endLoc.where _ t.endLoc.where-1 ELSE { IF last = NIL THEN propList _ propList.rest ELSE last.rest _ list.rest; propListModified _ TRUE; }; }; last _ list; list _ list.rest; ENDLOOP; IF propListModified THEN TiogaOps.PutProp[button.startLoc.node, TiogaButtonAtom, propList]; }; AppendRopeToNode: PROC [root: TiogaOps.Ref, node: TiogaOps.Ref, rope: ROPE, lookVector: TextLooks.Looks] RETURNS [start, length: TiogaOps.Offset] ~ TRUSTED { [start, length] _ TextEdit.InsertRope[root: TextNodeRef[root], dest: TextNode.NarrowToTextNode[TextNodeRef[node]], rope: rope, destLoc: TextNode.MaxLen, inherit: FALSE, looks: lookVector]; }; InitViewer: ViewerClasses.InitProc ~ { root: TiogaOps.Ref; textViewerInitProc[self]; self.tipTable _ buttonTIPTable; root _ TiogaOps.ViewerDoc[self]; TiogaOps.PutProp[root, $InitialTiogaButtons, NEW[BOOLEAN _ TRUE]]; }; Notifier: ViewerClasses.NotifyProc ~ { mouseButton: Menus.MouseButton _ red; shift, control: BOOL _ FALSE; mouse: TIPUser.TIPScreenCoords; mouseX, mouseY: INT; -- for archival purposes feedbackSel: TEditDocument.Selection _ TEditSelection.Alloc[]; FOR list: LIST OF REF ANY _ input, list.rest UNTIL list = NIL DO WITH list.first SELECT FROM x: ATOM => SELECT x FROM $Red => mouseButton _ red; $Yellow => mouseButton _ yellow; $Blue => mouseButton _ blue; $Shift => shift _ TRUE; $Control => control _ TRUE; $Mark => { button: TiogaButton; selectedButton: TiogaButton _ SelectedButton[self]; IF selectedButton = NIL THEN InputFocus.CaptureButtons[Notifier, self.class.tipTable, self] -- to track feedback out of buttons ELSE IF NOT MouseInViewer[self, mouse] THEN { InputFocus.ReleaseButtons[]; CancelFeedback[self]; LOOP; }; [] _ TEditMouseImpl.ResolveToChar[ selection: feedbackSel, viewer: self, tdd: NARROW[self.data, TEditDocument.TEditDocumentData], x: mouse.mouseX, y: self.ch - mouse.mouseY]; button _ FindTiogaButton[self, TiogaOpsLoc[feedbackSel.start.pos]]; IF button # NIL THEN IF button = selectedButton THEN LOOP -- optimizing check ELSE EstablishFeedback[self, button] ELSE { InputFocus.ReleaseButtons[]; CancelFeedback[self]; }; }; $Hit => { selectedButton: TiogaButton _ SelectedButton[self]; IF selectedButton # NIL THEN { InputFocus.ReleaseButtons[]; CancelFeedback[self]; IF selectedButton.fork THEN TRUSTED { [] _ Process.Detach[FORK selectedButton.proc[selectedButton, selectedButton.clientData, mouseButton, shift, control]] } ELSE selectedButton.proc[selectedButton, selectedButton.clientData, mouseButton, shift, control]; }; }; ENDCASE => NULL; z: TIPUser.TIPScreenCoords => { mouse _ z; mouseX _ mouse.mouseX; mouseY _ mouse.mouseY; }; ENDCASE => ERROR; ENDLOOP; TEditSelection.Free[feedbackSel]; }; MouseInViewer: PROC [this: ViewerClasses.Viewer, mouse: TIPUser.TIPScreenCoords] RETURNS [BOOLEAN] ~ { viewer: ViewerClasses.Viewer; client: BOOLEAN; [viewer, client] _ ViewerOps.MouseInViewer[mouse]; RETURN [viewer = this AND client]; }; TiogaOpsLoc: PROC [loc: TextNode.Location] RETURNS [TiogaOps.Location] ~ TRUSTED INLINE { RETURN [LOOPHOLE[loc]] }; TextNodeLoc: PROC [loc: TiogaOps.Location] RETURNS [TextNode.Location] ~ TRUSTED INLINE { RETURN [LOOPHOLE[loc]] }; FindTiogaButton: PROC [this: ViewerClasses.Viewer, loc: TiogaOps.Location] RETURNS [button: TiogaButton] ~ { list: TiogaButtonList; ref: REF ANY _ TiogaOps.GetProp[loc.node, TiogaButtonAtom]; IF ref = NIL OR ~ISTYPE[ref, TiogaButtonList] THEN RETURN [NIL]; list _ NARROW[ref]; WHILE list # NIL DO t: TiogaButton ~ list.first; IF t.startLoc.node # loc.node THEN RETURN [NIL]; IF t.startLoc.where = NodeItself THEN RETURN [t]; IF loc.where >= t.startLoc.where AND t.endLoc.where >= loc.where THEN RETURN [t]; list _ list.rest; ENDLOOP; RETURN [NIL]; }; SelectedButton: PROC [this: ViewerClasses.Viewer] RETURNS [button: TiogaButton] ~ { val: REF ANY ~ ViewerOps.FetchProp[viewer: this, prop: $TiogaButtonSelected]; IF val # NIL AND ISTYPE[val, TiogaButton] THEN button _ NARROW[val]; }; EstablishFeedback: PROC [this: ViewerClasses.Viewer, button: TiogaButton] ~ { ViewerOps.AddProp[viewer: this, prop: $TiogaButtonSelected, val: button]; TiogaOps.SetSelection[ viewer: this, start: IF button.startLoc.where = NodeItself THEN [button.startLoc.node, 0] ELSE button.startLoc, end: IF button.endLoc.where = NodeItself THEN [button.endLoc.node, GetRope[button].Length] ELSE button.endLoc, level: char, caretBefore: TRUE, pendingDelete: TRUE, which: feedback]; }; CancelFeedback: PROC [this: ViewerClasses.Viewer] ~ { ViewerOps.AddProp[viewer: this, prop: $TiogaButtonSelected, val: NIL]; TiogaOps.CancelSelection[feedback]; }; Hack: TiogaButtonProc ~ { button: TiogaButton _ NARROW[parent]; MessageWindow.Append[Rope.Cat["button contents ", GetRope[button]], TRUE]; MessageWindow.Blink[]; }; buttonTIPTable: TIPUser.TIPTable _ ViewerOps.FetchViewerClass[$Button].tipTable; tiogaButtonClass: ViewerClasses.ViewerClass _ NEW[ViewerClasses.ViewerClassRec _ ViewerOps.FetchViewerClass[$Text]^]; textViewerInitProc: ViewerClasses.InitProc _ tiogaButtonClass.init; tiogaButtonClass.init _ InitViewer; tiogaButtonClass.save _ NIL; -- avoids saving TiogaButtons properties tiogaButtonClass.notify _ Notifier; tiogaButtonClass.bltContents _ none; -- avoids dependency in ViewerBLT on $Text tiogaButtonClass.cursor _ bullseye; tiogaButtonClass.icon _ tool; tiogaButtonClass.tipTable _ buttonTIPTable; ViewerOps.RegisterViewerClass[TiogaButtonsAtom, tiogaButtonClass]; }. ͺTiogaButtonsImpl.Mesa Copyright c1984 Xerox Corporation. All rights reserved. Created by Rick Beach, September 12, 1984 3:22:48 pm PDT Create TiogaButtons from supplied information. Check that viewer is a $TiogaButtons class viewer. Creates a button as the last child of the document in viewer. Creates a button from the node. Provides the text of the button. Can be used in place of clientData for many purposes. This crock is necessary because TiogaOps.PutProp uses too low an abstraction for adding properties to a node. Aren't we nice, we get all the split viewers too! Create node as sibling of last child of root t is either the deleted button or a proper subset of it, so delete t from the list Put back the modified list Procedures that implement the $TiogaButtons viewer class Mouse coordinates are guaranteed to be in viewer space and we have captured the buttons for future $Marks and $Hits Selection info returned in feedbackSel.start.pos as TextNode.Location Find button within this selection Turn off reverse video Register $TiogaButtons viewer class Some Neat things to add to TiogaButtons in the future: a) box style attributes that Tioga would draw boxes around the run of text with this attribute b) rule style attribute that Tioga would draw horizontal or vertical rules c) background color style attribute d) cursor changes when mouse is over a button e) secondary selection to still work from $TiogaButtons viewers Κΰ˜™Jšœ Οmœ-™8J™8J™—šΟk ˜ J˜J˜ J˜ Jšœ ˜ Jšœ˜Jšœ˜J˜ Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ ˜ Jšœ ˜ Jšœ ˜ J˜ Jšœ˜Jšœ ˜ Jšœ ˜ Jšœ˜Jšœ˜Jšœ ˜ J˜—šΟnœžœž˜JšžœΩ˜ΰJšžœ ˜Jšžœ˜J˜Jšžœ˜Jšžœžœžœ˜J˜JšŸœžœ˜)JšŸœžœ˜'J˜—J˜J™.J™šŸ œž œ!žœ˜_JšœA˜AJ˜J˜—šŸ œž œ*žœ˜JJšœžœ˜ Jš žœ žœžœ(žœžœ˜VJšœΟc$˜9JšœI˜IJšœ) (˜QJ˜J˜5Jšœ˜J™—šŸ œž œ&žœžœ žœžœ žœžœžœžœžœžœžœžœžœ˜αJ™J™2Jš žœ žœžœ(žœžœ˜VJ˜J™=šœ˜J˜0šŸœžœ˜1J˜,Jšžœžœžœ˜3šžœžœžœ˜šžœžœžœž˜"J˜9Jšž˜——Jšœ žœ~˜Ššžœžœžœ˜Jšœ˜—šž˜Jšœ˜—J˜—Jšœ&˜&Jšœ˜—Jšœ˜J˜JšŸœž œžœ˜&—J˜šŸœžœžœIžœ+žœžœžœžœžœžœžœ˜πJ˜)Jšœžœžœ#˜/Jšœžœžœžœžœžœžœžœžœ˜lJš žœ žœžœ(žœžœ˜VJ˜J™šœ˜šŸœžœ˜9Jšœ žœr˜~šžœžœžœ˜Jšœ˜—šž˜Jšœ˜—J˜—šžœ žœžœžœ˜2J˜—Jšžœžœžœ/˜AJšœ˜—Jšœ˜J˜—šŸœž œžœžœ žœžœžœžœžœžœžœžœžœ˜ΎJ˜*J˜)šŸœžœ˜3J˜;J˜ JšœA˜AJšœ žœ}˜‰šžœžœžœ˜Jšœ˜—J˜—Jšœ)˜)Jšžœ ˜Jšœ˜J™—šŸ œž œ˜3šžœ žœžœ˜J˜9šŸœžœ˜1˜Jšœ˜J˜A—šžœ#žœ˜+J˜—J˜—Jšœ'˜'Jšœ˜—J˜J˜—šŸœž œžœžœ˜CJ™Všžœ žœžœ˜Jšœ.˜.šžœ#žœ˜+JšœX˜X—Jšœ˜—J˜J˜—šŸœž œ&žœ˜LJ™mJ˜8J˜kJ˜J˜—šŸ œžœžœ.˜LJšœ žœžœ˜JšŸœžœžœ žœ˜=šœžœžœ ˜Jšœ!žœ˜&J˜ Jšœ˜—J˜J˜J˜J˜—šŸœžœ˜2šŸœžœ˜)Jšœžœ˜J˜0J˜—J˜Jšœžœžœ#˜/Jš žœžœžœžœžœžœ˜Hšžœžœžœžœ˜"Jšœ ˜ J™1šžœ žœž˜Jšžœ*žœžœ žœ˜L—Jšœ˜—J˜J˜—š Ÿ œžœžœžœžœ˜pšžœ0žœžœ˜Jšœ6˜6J˜J˜—š Ÿœžœ"žœžœ žœžœ˜oJšœV˜VJ˜J˜—šŸ œžœ.˜AJšœžœ˜Jšœžœžœ+˜7Jš žœžœžœžœžœžœ˜Fšžœž˜ Jšžœžœ˜Jšžœžœ˜—Jšœ.˜.J˜J˜—šŸ œžœ˜,Jšœ-˜-J˜J˜—šŸœžœ˜1Jšœ žœ3˜GJšœžœžœ˜"Jšœ&˜&Jšœžœžœ;˜GJš žœžœžœžœžœžœ˜:Jšœžœ˜šžœžœž˜Jšœ˜šžœ(žœžœ˜RJ˜"J˜šžœžœ0žœ˜KJšœ(˜(—šžœžœ'žœ˜4Jšœ2˜2J˜—šžœžœ0žœ˜IJšœ&˜&—šžœžœ%žœ˜2Jšœ.˜.J˜—šžœ"ž˜)Jšœ!˜!—šžœ˜J™Ršžœž˜ Jšžœ˜Jšžœ˜—Jšœžœ˜Jšœ˜—Jšœ˜—J˜ J˜Jšž˜—šžœž˜J™JšœB˜B—J˜J˜—š Ÿœžœ0žœžœ$žœ˜Jšœ’žœ˜ΌJ˜J˜—J™J™8J˜šŸ œ˜&J˜J˜Jšœ˜J˜ Jšœ-žœžœžœ˜BJ˜J˜—šŸœ˜&J˜%Jšœžœžœ˜Iprocšœ˜Kšœžœ ˜-Kšœ>˜>šžœžœžœžœžœžœžœž˜@šžœ žœž˜šœžœžœž˜J˜J˜ J˜Jšœžœ˜Jšœžœ˜šœ ˜ J˜J˜3šžœžœžœ˜Jšœ? #˜b—šžœžœžœžœ˜-J˜J˜Jšžœ˜Jšœ˜—J™:J™8šœ"˜"Jšœ˜Jšœ ˜ Jšœžœ-˜8Jšœ˜Jšœ˜—JšœE™EJ™!JšœC˜Cšžœ žœžœ˜Jšžœžœžœ ˜8Jšžœ ˜$—šžœ˜J˜J˜Jšœ˜—Jšœ˜—šœ ˜ J™Jšœ3˜3šžœžœžœ˜J˜Jšœ˜šžœžœžœ˜%Jšœžœ]˜uJšœ˜—Jšžœ]˜aJšœ˜—Jšœ˜—Jšžœžœ˜—šœ˜J˜ Jšœ˜Jšœ˜Jšœ˜—Jšžœžœ˜—Jšžœ˜—J˜!Jšœ˜J˜—šŸ œžœ>žœžœ˜fJ˜Jšœžœ˜Jšœ2˜2Jšžœžœ ˜"J˜J˜—šŸ œžœžœžœ˜YJšžœžœ˜J˜J˜—šŸ œžœžœžœ˜YJšžœžœ˜J˜J˜—šŸœžœ6žœ˜lJšœ˜Jšœžœžœ/˜;Jšžœžœžœžœžœžœžœ˜@Jšœžœ˜šžœžœž˜Jšœ˜Jšžœžœžœžœ˜0Jšžœžœžœ˜1Jšžœžœžœžœ˜QJ˜Jšž˜—Jšžœžœ˜ J˜J˜—šŸœžœžœ˜SJšœžœžœA˜Mš žœžœžœžœž˜.Jšœ žœ˜—J˜J˜—šŸœžœ6˜MJšœI˜I˜Jšœ ˜ šœžœ#˜,Jšžœ˜Jšžœ˜—šœžœ!˜(Jšžœ-˜1Jšžœ˜—J˜ Jšœ žœ˜Jšœžœ˜J˜—J˜J˜—šŸœžœ!˜5JšœAžœ˜FJ˜#J˜J˜—J˜šŸœ˜Jšœžœ ˜%JšœDžœ˜JJ˜J˜—J˜J™#J™JšœP˜P˜-JšžœD˜G—JšœC˜CJšœ#˜#Jšœžœ (˜EJšœ#˜#Jšœ& *˜PJšœ#˜#Jšœ˜Jšœ+˜+JšœB˜BJšœ˜J˜™6J™^J™JJ™#J™-J™?——…—4\Hζ