DIRECTORY Atom USING [GetPName], Buttons USING [Create], Containers USING [Container, Create, ChildXBound, ChildYBound], Convert USING [RealFromRope], Graphics USING [Context, Mark, Save, white, GetBounds, SetColor, DrawBox, Restore, Translate], InputFocus USING [ReleaseButtons], Menus USING [AppendMenuEntry, ClickProc, CreateEntry, CreateMenu, Menu, MenuProc], PathEditor USING [SetGlobal, SetLocal, SetSimplify, StoreBetas], PEViewer, Process USING [Detach], Real USING [FixI], Rope USING [ROPE], TIPUser USING [InstantiateNewTIPTable,TIPScreenCoords], ViewerClasses USING [Viewer, ViewerClass, ViewerClassRec, NotifyProc, PaintProc, DestroyProc, ScrollProc], ViewerOps USING [CreateViewer, PaintViewer, RegisterViewerClass], ViewerTools USING [GetContents, MakeNewTextViewer, SetContents, SetSelection], WindowManager USING [RestoreCursor]; PEViewerImpl: CEDAR MONITOR IMPORTS Atom, Buttons, Containers, Convert, Graphics, InputFocus, Menus, PathEditor, Process, Real, TIPUser, ViewerOps, ViewerTools, WindowManager EXPORTS PEViewer = BEGIN OPEN PEViewer; entryHeight: CARDINAL = 12; -- height of a line of items in a menu entryVSpace: CARDINAL = 3; -- vertical leading between lines entryHSpace: CARDINAL = 8; -- horizontal space between items on a line PathViewerData: TYPE = REF PathViewerDataRec; PathViewerDataRec: TYPE = RECORD [ outer: Containers.Container _ NIL, viewer: ViewerClasses.Viewer, biasviewer: ViewerClasses.Viewer, tensionviewer: ViewerClasses.Viewer, deltaviewer: ViewerClasses.Viewer, showbiasviewer: ViewerClasses.Viewer, showtensionviewer: ViewerClasses.Viewer, doitviewer: ViewerClasses.Viewer, showdeltaviewer: ViewerClasses.Viewer, xTranslation, yTranslation: REAL _ 0., xLeft, xRight, yBottom, yTop: REAL _ 0., buttonPasserState: ButtonPasserState _ alive, inputEvent: ATOM, newEventArrival: CONDITION, gotANewHit: BOOLEAN _ FALSE, controlPointX, controlPointY: REAL _ 0., redrawProc: RedrawProc, -- name of procedure for redrawing on window resizing, etc. buttonProc: ButtonProc, -- name of procedure for acting on button pushes quitProc: QuitProc, -- name of procedure for cleaning up on exit clientData: REF ANY ]; ButtonPasserState: TYPE = {alive, dying, dead}; MenuData: TYPE = REF MenuDataRec; MenuDataRec: TYPE = RECORD [ menuButton: ATOM, pathViewerData: PathViewerData ]; BuildViewer: PUBLIC PROCEDURE [name: Rope.ROPE, menuLabels: LIST OF MenuLabelRec, clientData: REF ANY, redrawProc: RedrawProc, quitProc: QuitProc, buttonProc: ButtonProc] RETURNS [pathViewer: ViewerClasses.Viewer, biasViewer: ViewerClasses.Viewer, tensionViewer: ViewerClasses.Viewer] = { menu: Menus.Menu _ Menus.CreateMenu[lines: 2]; pathViewerData: PathViewerData _ NEW[PathViewerDataRec _ [redrawProc: redrawProc, buttonProc: buttonProc, quitProc: quitProc, clientData: clientData]]; Menus.AppendMenuEntry[ -- enter "erase" button menu: menu, entry: Menus.CreateEntry[ name: "Erase", proc: Erase, clientData: pathViewerData, documentation: "Erase the viewer" ] ]; Menus.AppendMenuEntry[ -- enter "<" button menu: menu, entry: Menus.CreateEntry[ name: "<", proc: RollLeft, clientData: pathViewerData, documentation: "Roll image to left" ] ]; Menus.AppendMenuEntry[ -- enter ">" button menu: menu, entry: Menus.CreateEntry[ name: ">", proc: RollRight, clientData: pathViewerData, documentation: "Roll image to right" ] ]; Menus.AppendMenuEntry[ -- enter "simplify" button menu: menu, entry: Menus.CreateEntry[ name: "Simplify", proc: CallSimplify, clientData: pathViewerData, documentation: "Reduces number of control points by one" ], line: 1 ]; Menus.AppendMenuEntry[ -- enter "local" button menu: menu, entry: Menus.CreateEntry[ name: "Local", proc: MakeLocal, clientData: pathViewerData, documentation: "Set bias and tension locally" ], line: 1 ]; Menus.AppendMenuEntry[ -- enter "global" button menu: menu, entry: Menus.CreateEntry[ name: "Global", proc: MakeGlobal, clientData: pathViewerData, documentation: "Set bias and tension globally" ], line: 1 ]; FOR menuLabels _ menuLabels, menuLabels.rest UNTIL menuLabels = NIL DO Menus.AppendMenuEntry[ -- enter menu buttons menu: menu, entry: Menus.CreateEntry[ name: Atom.GetPName[menuLabels.first.label], proc: MenuHit, clientData: NEW[MenuDataRec _ [ menuButton: menuLabels.first.label, pathViewerData: pathViewerData ] ], documentation: "", guarded: menuLabels.first.guarded ] ]; ENDLOOP; pathViewerData.outer _ Containers.Create[ [ name: name, menu: menu, iconic: TRUE, column: left, scrollable: FALSE ] ]; pathViewerData.viewer _ ViewerOps.CreateViewer[ flavor: $PEViewer, info: [ parent: pathViewerData.outer, wx: 0, wy: entryHeight + entryVSpace, -- position WRT parent ww: pathViewerData.outer.ww, -- CHildXBound below wh: pathViewerData.outer.wh, -- CHildYBound below data: pathViewerData, -- describes the current scene scrollable: TRUE ] ]; pathViewerData.biasviewer _ Buttons.Create[ info: [ parent: pathViewerData.outer, wx: 10, wy: 0, ww: 35, wh: entryHeight + entryVSpace, data: pathViewerData, -- describes the current scene scrollable: FALSE, border: FALSE, name: "Bias: " ], proc: ClickBias, clientData: pathViewerData ]; pathViewerData.showbiasviewer _ ViewerTools.MakeNewTextViewer[ info: [ parent: pathViewerData.outer, wx: 45, wy: 0, ww: 55, wh: entryHeight + entryVSpace, scrollable: FALSE, border: FALSE ] ]; pathViewerData.tensionviewer _ Buttons.Create[ info: [ parent: pathViewerData.outer, wx: 100, wy: 0, ww: 60, wh: entryHeight + entryVSpace, data: pathViewerData, -- describes the current scene scrollable: FALSE, border: FALSE, name: "Tension: " ], proc: ClickTension, clientData: pathViewerData ]; pathViewerData.showtensionviewer _ ViewerTools.MakeNewTextViewer[ info: [ parent: pathViewerData.outer, wx: 160, wy: 0, ww: 55, wh: entryHeight + entryVSpace, scrollable: FALSE, border: FALSE ] ]; pathViewerData.doitviewer _ Buttons.Create[ info: [ parent: pathViewerData.outer, wx: 215, wy: 0, ww: 100, -- CHildXBound below wh: entryHeight + entryVSpace, data: pathViewerData, -- describes the current scene scrollable: FALSE, border: FALSE, name: "Doit" ], proc: ClickDoit, clientData: pathViewerData ]; pathViewerData.deltaviewer _ Buttons.Create[ info: [ parent: pathViewerData.outer, wx: 315, wy: 0, ww: 60, wh: entryHeight + entryVSpace, data: pathViewerData, -- describes the current scene scrollable: FALSE, border: FALSE, name: "|| Delta: " ], proc: ClickDelta, clientData: pathViewerData ]; pathViewerData.showdeltaviewer _ ViewerTools.MakeNewTextViewer[ info: [ parent: pathViewerData.outer, wx: 375, wy: 0, ww: 55, wh: entryHeight + entryVSpace, scrollable: FALSE, border: FALSE ] ]; Containers.ChildXBound[pathViewerData.outer, pathViewerData.viewer]; Containers.ChildYBound[pathViewerData.outer, pathViewerData.viewer]; Containers.ChildXBound[pathViewerData.outer, pathViewerData.showdeltaviewer]; ViewerTools.SetContents[pathViewerData.showdeltaviewer, "1.0"]; ViewerOps.PaintViewer[pathViewerData.outer, all]; pathViewerData.xTranslation _ pathViewerData.yTranslation _ 0.; pathViewerData.xLeft _ pathViewerData.yBottom _ 0.; pathViewerData.xRight _ pathViewerData.viewer.ww; pathViewerData.yTop _ pathViewerData.viewer.wh; TRUSTED {Process.Detach[FORK ButtonPasser[pathViewerData]];}; RETURN [pathViewerData.viewer, pathViewerData.showbiasviewer, pathViewerData.showtensionviewer]; }; DrawInViewer: PUBLIC PROCEDURE [pathViewer: ViewerClasses.Viewer, drawProc: DrawProc] = { doDrawProc: REF DrawProc _ NIL; IF pathViewer # NIL THEN { TRUSTED {doDrawProc _ NEW[DrawProc _ drawProc];}; ViewerOps.PaintViewer[ viewer: pathViewer, hint: client, whatChanged: doDrawProc, clearClient: FALSE ]; }; }; ClickBias: Menus.ClickProc = { pathViewerData: PathViewerData _ NARROW[clientData]; IF mouseButton = blue THEN ViewerTools.SetContents[pathViewerData.showbiasviewer, ""]; ViewerTools.SetSelection[pathViewerData.showbiasviewer]; }; ClickTension: Menus.ClickProc = { pathViewerData: PathViewerData _ NARROW[clientData]; IF mouseButton = blue THEN ViewerTools.SetContents[pathViewerData.showtensionviewer, ""]; ViewerTools.SetSelection[pathViewerData.showtensionviewer]; }; ClickDoit: Menus.ClickProc = { bias, tension: REAL; pathViewerData: PathViewerData _ NARROW[clientData]; bias _ Convert.RealFromRope[ViewerTools.GetContents[pathViewerData.showbiasviewer]]; tension _ Convert.RealFromRope[ViewerTools.GetContents[pathViewerData.showtensionviewer]]; PathEditor.StoreBetas[NARROW[clientData, PathViewerData].clientData, bias, tension]; }; ClickDelta: Menus.ClickProc = { pathViewerData: PathViewerData _ NARROW[clientData]; IF mouseButton = blue THEN ViewerTools.SetContents[pathViewerData.showdeltaviewer, ""]; ViewerTools.SetSelection[pathViewerData.showdeltaviewer]; }; PaintProc: ViewerClasses.PaintProc = { pathViewerData: PathViewerData _ NARROW[self.data]; x,y: REAL; x _ pathViewerData.xTranslation; y _ pathViewerData.yTranslation; IF whatChanged = NIL THEN { Graphics.Translate[context, x, y]; pathViewerData.redrawProc[pathViewerData.clientData]; } ELSE { Graphics.Translate[context, x, y]; NARROW[whatChanged, REF DrawProc]^[context]; }; }; MakeGlobal: Menus.MenuProc = { PathEditor.SetGlobal[NARROW[clientData, PathViewerData].clientData]; }; MakeLocal: Menus.MenuProc = { PathEditor.SetLocal[NARROW[clientData, PathViewerData].clientData]; }; CallSimplify: Menus.MenuProc = { delta: REAL; pathViewerData: PathViewerData _ NARROW[clientData]; delta _ Convert.RealFromRope[ViewerTools.GetContents[pathViewerData.showdeltaviewer]]; PathEditor.SetSimplify[NARROW[clientData, PathViewerData].clientData, delta]; }; Erase: Menus.MenuProc = { DoErase: PROC [context: Graphics.Context] = { mark: Graphics.Mark _ Graphics.Save[context]; Graphics.SetColor[context, Graphics.white]; Graphics.DrawBox[context, Graphics.GetBounds[context]]; Graphics.Restore[context,mark]; }; pathViewerData: PathViewerData _ NARROW[clientData]; DrawInViewer[pathViewerData.viewer, DoErase]; }; RollLeft: Menus.MenuProc = { pathViewerData: PathViewerData _ NARROW[clientData]; pathViewerData.xTranslation _ pathViewerData.xTranslation - 64; pathViewerData.redrawProc[clientData: pathViewerData.clientData]; }; RollRight: Menus.MenuProc = { pathViewerData: PathViewerData _ NARROW[clientData]; pathViewerData.xTranslation _ pathViewerData.xTranslation + 64; pathViewerData.redrawProc[clientData: pathViewerData.clientData]; }; MenuHit: Menus.MenuProc = { menuData: MenuData _ NARROW[clientData]; ButtonMonitor[menuData.pathViewerData, menuData.menuButton]; }; ButtonMonitor: ENTRY PROCEDURE [pathViewerData: PathViewerData, event: ATOM] = { pathViewerData.inputEvent _ event; pathViewerData.gotANewHit _ TRUE; NOTIFY pathViewerData.newEventArrival; }; ButtonPasser: PROCEDURE [pathViewerData: PathViewerData] = { WHILE pathViewerData.buttonPasserState = alive DO IF GotAButtonHit[pathViewerData] THEN pathViewerData.buttonProc[pathViewerData.clientData, pathViewerData.inputEvent, pathViewerData.controlPointX, pathViewerData.controlPointY]; ENDLOOP; pathViewerData.buttonPasserState _ dead; }; GotAButtonHit: ENTRY PROCEDURE [pathViewerData: PathViewerData] RETURNS [gotAHit: BOOLEAN] = { IF ~pathViewerData.gotANewHit THEN WAIT pathViewerData.newEventArrival; gotAHit _ pathViewerData.gotANewHit; pathViewerData.gotANewHit _ FALSE; }; NotifyProc: ViewerClasses.NotifyProc = { ENABLE UNWIND => { InputFocus.ReleaseButtons[]; WindowManager.RestoreCursor[]; }; pathViewerData: PathViewerData _ NARROW[self.data]; FOR list: LIST OF REF ANY _ input, list.rest UNTIL list = NIL DO WITH list.first SELECT FROM x: ATOM => ButtonMonitor[pathViewerData, x]; z: TIPUser.TIPScreenCoords => { OPEN pathViewerData; controlPointX _ z.mouseX - xTranslation; controlPointY _ z.mouseY - yTranslation; IF controlPointX > xRight THEN xRight _ controlPointX ELSE IF controlPointX < xLeft THEN xLeft _ controlPointX; IF controlPointY > yTop THEN yTop _ controlPointY ELSE IF controlPointY < yBottom THEN yBottom _ controlPointY; }; ENDCASE => ERROR; ENDLOOP; }; DestroyProc: ViewerClasses.DestroyProc = { pathViewerData: PathViewerData _ NARROW[self.data]; DestroyButtonPasser[pathViewerData]; IF pathViewerData.quitProc # NIL THEN pathViewerData.quitProc[pathViewerData.clientData]; pathViewerData.viewer _ NIL; pathViewerData.outer _ NIL; }; DestroyButtonPasser: ENTRY PROCEDURE [pathViewerData: PathViewerData] = { pathViewerData.buttonPasserState _ dying; NOTIFY pathViewerData.newEventArrival; }; ScrollProc: ViewerClasses.ScrollProc = { pathViewerData: PathViewerData _ NARROW[self.data]; SELECT op FROM up => { pathViewerData.yTranslation _ pathViewerData.yTranslation + amount; pathViewerData.redrawProc[clientData: pathViewerData.clientData]; }; down => { pathViewerData.yTranslation _ pathViewerData.yTranslation - amount; pathViewerData.redrawProc[clientData: pathViewerData.clientData]; }; thumb => { pathViewerData.yTranslation _ - (pathViewerData.yBottom + (1. - amount/100.) * (pathViewerData.yTop - pathViewerData.yBottom)); pathViewerData.redrawProc[clientData: pathViewerData.clientData]; }; query => { OPEN pathViewerData; RETURN [Real.FixI[100. - (-yTranslation + self.ch - yBottom) * 100. / (yTop - yBottom)], Real.FixI[100. - (-yTranslation - yBottom) * 100. / (yTop - yBottom)]]; }; ENDCASE; }; Init: PROCEDURE [] = { pathViewerClass: ViewerClasses.ViewerClass; pathViewerClass _ NEW[ViewerClasses.ViewerClassRec _ [ paint: PaintProc, notify: NotifyProc, destroy: DestroyProc, scroll: ScrollProc, tipTable: TIPUser.InstantiateNewTIPTable["PEViewer.TIP"], icon: document] ]; ViewerOps.RegisterViewerClass[$PEViewer, pathViewerClass]; }; Init[]; END. ®PEViewerImpl.mesa Copyright (C) 1984 by Xerox Corporation. All rights reserved. Last Edited by Plebon, August 23, 1983 4:17 pm Routines to manage the Path Editor viewer. These routines are essentially Frank Crow's Quick Viewer with a few improvements. Last Edited by: Tso, July 12, 1984 1:25:00 pm PDT Convert USING [Error, RealFromRope], -- to be used in the future to catch real no. format bugs This routine makes a viewer with the supplied name and menu labels. The supplied redrawProc is called when the screen must be redrawn (because the viewer changes size, etc.). The supplied quitProc is called to clean up on program termination (which occurs when the viewer is destroyed. ww: pathViewerData.outer.ww/3, wx: pathViewerData.outer.ww/3, wy: 0, ww: pathViewerData.outer.ww/3, wx: pathViewerData.outer.ww/3, wy: 0, ww: pathViewerData.outer.ww/3, wx: (2*pathViewerData.outer.ww)/3, wy: 0, ww: pathViewerData.outer.ww/3, -- CHildXBound below Constrain the graphics area to lie in viewer space left over after menu, etc. are drawn. This routine calls the PaintProc to draw in the viewer. This routine processes a hit on the bias button This routine processes a hit on the tension button This routine updates the active vertex to the current values of bias and tension. bias _ Convert.RealFromRope[ViewerTools.GetContents[pathViewerData.showbiasviewer] ! Convert.Error => {RETURN;}]; tension _ Convert.RealFromRope[ViewerTools.GetContents[pathViewerData.showtensionviewer] ! Convert.Error => {RETURN;}]; This routine processes a hit on the tension button This routine repaints the screen with updates. This routine changes the bias and tension on a global basis for the active trajectory. This routine changes the bias and tension on a local basis for the active trajectory for continuously shaped Beta-splines. This routine reduces the number of control points of the active trajectory by one and the resulting retains the basic shape of the original spline. This routine erases the contents the the graphics viewer. This routine moves the image to the left in the graphics viewer. This routine moves the image to the right in the graphics viewer. This routine handles client menu item hits. This routine stores a button event and notifies the button passing process. This process monitors button events. This routine waits for notification of an event, and determines if the event is a button hit. This routine responds to input events (from the TIP table). Expand work area if clicked outside existing bounds. This routine cleans up when the viewer is destroyed. This routine causes the button passer process to kill itself. This routine handles scrollbar mouse hits. This routine starts up the viewer. Ê ì˜Jšœ™J™>J™.J™J™}J™1J˜šÏk ˜ Jšœœ ˜Jšœœ ˜Jšœ œ/˜?JšœœR™_Jšœœ˜Jšœ œP˜^Jšœ œ˜"JšœœG˜R—šœœ0˜CJšœ ˜ Jšœœ ˜Jšœœ˜Jšœœœ˜Jšœœ*˜7JšœœW˜jJšœ œ2˜AJšœ œ=˜NJšœœ˜$J˜J˜—šœ ˜Jšœ‹˜’Jšœ ˜J˜—Jšœœ ˜J˜Jšœ œ Ïc&˜DJšœ œž!˜>Jšœ œ ž+˜JJ˜Jšœœœ˜-šœœœ˜%Jšœœ˜"Jšœ˜J˜!Jšœ$˜$Jšœ"˜"J˜%Jšœ(˜(J˜!Jšœ&˜&Jšœœ˜&Jšœœ˜(Jšœ-˜-Jšœ œ˜Jšœ œ˜Jšœ œœ˜Jšœœ˜(Jšœž<˜UJšœž0˜JJšœž,˜CJšœ œ˜Jšœ˜—J˜Jšœœ˜/J˜Jšœ œœ ˜!šœ œœ˜Jšœ œ˜Jšœ˜Jšœ˜—J˜šÏn œœ œ œœœœœFœn˜ Jšœ ™ Jšœ.˜.Jšœ!œs˜—šœž˜2Jšœ ˜ šœ˜J˜J˜ Jšœ˜J˜!J˜—J˜—šœž˜.Jšœ ˜ šœ˜J˜ J˜Jšœ˜J˜#J˜—J˜—šœž˜.Jšœ ˜ šœ˜J˜ J˜Jšœ˜J˜$J˜—Jšœ˜—šœž˜5Jšœ ˜ šœ˜J˜J˜Jšœ˜J˜8—J˜J˜—Jšœ˜šœž˜2Jšœ ˜ šœ˜J˜J˜Jšœ˜J˜-—J˜J˜—Jšœ˜šœž˜3Jšœ ˜ šœ˜J˜J˜Jšœ˜J˜.—J˜J˜—Jšœ˜J˜šœ*œœ˜Fšœžœ˜1Jšœ ˜ šœ˜Jšœ,˜,Jšœ˜šœ œ˜Jšœ$˜$Jšœ˜Jšœ˜—Jšœ˜Jšœ!˜!Jšœ˜—J˜—Jš˜—šœ+˜+Jšœ ˜ J˜ Jšœœ˜ J˜ Jšœ ˜Jšœ˜—šœ/˜/Jšœ˜šœ˜Jšœ˜Jšœ'ž˜=Jšœž˜2Jšœž˜3Jšœž#˜8Jšœ ˜J˜—Jšœ˜—˜+˜J˜J˜Jšœ™Jšœ ˜ Jšœ˜Jšœž#˜8Jšœ œ˜Jšœ˜J˜J˜J˜Jšœ˜—J˜—˜>˜J˜J˜Jšœ ˜ Jšœ˜Jšœ œ˜Jšœ˜ J˜—J˜—˜.˜J˜J™%Jšœ ™ J˜Jšœ ˜ Jšœ˜Jšœž#˜8Jšœ œ˜Jšœ˜J˜—J˜J˜Jšœ˜—J˜˜A˜J˜J™%Jšœ ™ J˜Jšœ ˜ Jšœ˜Jšœ œ˜Jšœ˜ —J˜—J˜˜+˜J˜J™)Jšœ ž™4J˜Jšœ ž˜Jšœ˜Jšœž#˜8Jšœ œ˜Jšœ˜J˜ —J˜J˜Jšœ˜—J˜˜,˜J˜J˜Jšœ ˜ Jšœ˜Jšœž#˜8Jšœ œ˜Jšœ˜J˜—J˜J˜Jšœ˜—J˜˜?˜J˜J˜Jšœ ˜ Jšœ˜Jšœ œ˜Jšœ˜ —J˜—J˜J˜J˜Jšœ\™\JšœE˜EJšœD˜DJšœN˜NJšœ?˜?J™Jšœ1˜1Jšœ?˜?Jšœ3˜3Jšœ1˜1Jšœ1˜1Jšœœ!˜=JšœZ˜`J˜—J˜šŸ œœ œ;˜YJ™7Jšœ œ œ˜šœœœ˜Jšœœ˜1šœ˜Jšœ˜Jšœ ˜ Jšœ˜Jšœ ˜Jšœ˜—J˜—J˜—J˜šÏb œ˜Jšœ/™/Jšœ!œ ˜4Jšœœ<˜VJ˜8Jšœ˜—š  œ˜!Jšœ2™2Jšœ!œ ˜4Jšœœ?˜YJ˜;—Jšœ˜J˜š  œ˜J™QJšœœ˜Jšœ!œ˜5JšœT˜TJšœZ˜ZJšœgœ™qJšœmœ™wJšœœ8˜T—Jšœ˜š  œ˜Jšœ2™2Jšœ!œ ˜4Jšœœ=˜WJšœ9˜9—Jšœ˜J˜š  œ˜&Jšœ.™.Jšœ!œ ˜3Jšœœ˜ Jšœ ˜ Jšœ ˜ šœœœ˜Jšœ"˜"Jšœ5˜5Jšœ˜—šœ˜Jšœ"˜"Jšœœ˜,Jšœ˜—J˜—J˜š  œ˜J™VJšœœ)˜DJ˜—š  œ˜J™zJšœœ)˜CJ˜—š  œ˜ J™“Jšœœ˜ Jšœ!œ ˜4JšœV˜VJšœœ0˜MJ˜—šŸœ˜J™9šŸœœ ˜-Jšœ-˜-Jšœ+˜+Jšœ7˜7Jšœ˜J˜—Jšœ!œ ˜4Jšœ-˜-J˜—J˜šŸœ˜JšœA™AJšœ!œ ˜4Jšœ?˜?JšœA˜AJ˜—J˜šŸ œ˜JšœB™BJšœ!œ ˜4Jšœ?˜?JšœA˜AJ˜J˜—šŸœ˜J™+Jšœœ ˜(Jšœ<˜