DIRECTORY Ascii, Char, Commander, CommanderOps, ConvertUnsafe, FanoutStream, ForkOps, Identification, Imager, ImagerBackdoor, ImagerBox, ImagerColor, ImagerFont, ImagerPath, ImagerSample, IO, KeySymsSun, KeySymsOSF, KeySymsHP, KeySymsKB, PropList, Real, Rope, RuntimeError, SF, SharedCell, SlateControls, SlateIO, Slate, Xl, XlAscii, XlBitmap, XlCutBuffers, XTk, XTkBitmapScroller, XTkBitmapWidgetsExtras, XTkSharedCell, XTkPopUps, XTkLabels, XTkWidgets; SlateWidgetImpl: CEDAR MONITOR IMPORTS Char, Commander, CommanderOps, ConvertUnsafe, FanoutStream, ForkOps, Identification, Imager, ImagerBackdoor, ImagerColor, ImagerFont, ImagerPath, ImagerSample, IO, PropList, Real, Rope, RuntimeError, SharedCell, SlateControls, Slate, SlateIO, Xl, XlAscii, XlBitmap, XlCutBuffers, XTk, XTkBitmapScroller, XTkBitmapWidgetsExtras, XTkSharedCell, XTkLabels, XTkPopUps, XTkWidgets EXPORTS Slate ~ BEGIN OPEN Slate; Widget: TYPE = XTk.Widget; varyingFlag: XTk.WidgetFlagKey ~ wf6; Data: TYPE = REF DataRec; DataRec: TYPE = RECORD [ page: Page _ NIL, scrollPos: Xl.Point ¬ [0, 0], followTextCell: SharedCell.Cell ¬ NIL, followMode: REF FollowSpec ¬ NIL, motionType: INT ¬ 1, motionT: Xl.TimeStamp ¬ Xl.currentTime, motionP: Xl.Point ¬ [0, 0], instance: Instance ¬ NIL, controlPanel: ControlPanel ¬ NIL, tq: Xl.TQ ¬ NIL, log: IO.STREAM, context: Imager.Context ¬ NIL, currentFontData: REF FontData ¬ NIL, buttonMode: ATOM ¬ $normal, buttonModeFancy: BOOL ¬ FALSE, hasStart: BOOL ¬ FALSE, startP: Xl.Point ¬ [0, 0], directPaintGC: Xl.GContext ¬ NIL, directEraseGC: Xl.GContext ¬ NIL, directErasePixmap: Xl.Pixmap ¬ Xl.nullPixmap, delayMore: BOOL ¬ FALSE, delayedPaintList: LIST OF DelayedPaintRec ¬ NIL, bitmapSize: Xl.Size ¬ [0, 0], eraserWidth: INT ¬ 3, eraseMinimum: REAL ¬ 2.5, eraseTrajectory: Imager.Trajectory ¬ NIL, mode: ATOM ¬ NIL, sname, usedPageIndicator, bitmapScroller, realBitmapWidget, pagesCont, shellWidget, fonts, colorWidget, eraserWidthWidget, invertMouseWidget: Widget, lineHeight: REAL ¬ 10, textOfLine: Rope.ROPE ¬ NIL, inputfocusX: REAL ¬ 0, inputfocusY: REAL ¬ 0, imageMode: ImageModeRep ¬ [] ]; DelayedPaintRec: TYPE = RECORD [ p1, p2: Xl.Point ]; FontData: TYPE = RECORD [ presentName: Rope.ROPE, realName: Rope.ROPE, scale: REAL ]; ColorSpec: TYPE = RECORD [ color: Imager.Color, name: Rope.ROPE ]; ImageModeRep: TYPE = RECORD [ color: REF ColorSpec ¬ NIL, font: Imager.Font ¬ NIL, thickness: INT ¬ 1, requiredLineHeight: INT ¬ 20 ]; FollowSpec: TYPE = RECORD [ follow: BOOL, name: Rope.ROPE ]; keyForPage: REF INT ~ NEW [INT]; eraseColor: Imager.Color ~ Imager.MakeGray[0.125]; fontChoiceList: XTkPopUps.ChoiceList ¬ NIL; colorChoiceList: XTkPopUps.ChoiceList ¬ NIL; followChoiceList: XTkPopUps.ChoiceList ¬ NIL; defaultFontData: REF FontData; defaultColorData: REF ColorSpec; defaultFollowData: REF FollowSpec; nowhereBM: XlBitmap.Bitmap ¬ XlBitmap.Create[[1, 1], 1, FALSE]; nowhereContext: Imager.Context ¬ XlBitmap.CreateContext[nowhereBM]; ResetBM: PROC [d: Data, newBM: XlBitmap.Bitmap] = { bm: XlBitmap.Bitmap ¬ IF newBM=NIL THEN nowhereBM ELSE newBM; box: SF.Box ~ XlBitmap.GetBox[bm]; sz: Xl.Size ~ [box.max.f-box.min.f, box.max.s-box.min.s]; context: Imager.Context; XTkBitmapScroller.SetBitmap[d.bitmapScroller, bm]; IF ImagerSample.GetBitsPerSample[XlBitmap.GetSM[bm]]>1 THEN { implWidget: Widget ¬ XTkBitmapScroller.GetImplWidget[d.bitmapScroller]; XTkBitmapWidgetsExtras.FollowColors[implWidget, bm]; }; context ¬ XlBitmap.CreateContext[bm]; Imager.ClipRectangle[context, [1, 1, sz.width-2, sz.height-2]]; Imager.SetStrokeEnd[context, round]; Imager.SetStrokeJoint[context, round]; d.bitmapSize ¬ sz; d.context ¬ context; UseThickness[d]; UseFont[d]; UseColor[d]; }; InitFonts: PROC [] = { currentList: XTkPopUps.ChoiceList ¬ NIL; FontEntry: PROC [realName: Rope.ROPE, scale: REAL, short: Rope.ROPE] RETURNS [fd: REF FontData] = { fd ¬ NEW[FontData ¬ [ presentName: short, realName: realName, scale: scale ]]; currentList ¬ CONS[ [short, fd, NIL, "Font on type in", SetFontButtonHit], currentList ]; }; FontSubMenu: PROC [familyName: Rope.ROPE] = { fontChoiceList ¬ CONS[ [familyName, NIL, currentList, "Use this font family"], fontChoiceList ]; currentList ¬ NIL; }; defaultFontData ¬ NEW[FontData ¬ [ presentName: "Tioga10 ", realName: "Xerox/TiogaFonts/Tioga10", scale: 1.0 ]]; [] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 1.0, "Tioga10"]; [] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 1.5, "Tioga10 x 1.5"]; [] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 2.0, "Tioga10 x 2"]; [] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 3.0, "Tioga10 x 3"]; [] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 4.0, "Tioga10 x 4"]; [] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 6.0, "Tioga10 x 6"]; [] ¬ FontEntry["Xerox/TiogaFonts/Tioga10", 8.0, "Tioga10 x 8"]; FontSubMenu["Tioga"]; [] ¬ FontEntry["Xerox/TiogaFonts/Gacha10", 1.0, "Gacha10"]; [] ¬ FontEntry["Xerox/TiogaFonts/Gacha10", 2.0, "Gacha10 x 2"]; [] ¬ FontEntry["Xerox/TiogaFonts/Gacha10", 3.0, "Gacha10 x 3"]; [] ¬ FontEntry["Xerox/TiogaFonts/Gacha10", 4.0, "Gacha10 x 4"]; [] ¬ FontEntry["Xerox/TiogaFonts/Gacha10", 6.0, "Gacha10 x 6"]; [] ¬ FontEntry["Xerox/TiogaFonts/Gacha10", 8.0, "Gacha10 x 8"]; FontSubMenu["Gacha"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica10", 1.0, "Helvetica10"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica14", 1.0, "Helvetica14"]; defaultFontData ¬ FontEntry["Xerox/TiogaFonts/Helvetica18", 1.0, "Helvetica18"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18", 1.5, "Helvetica18 x 1.5"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18", 2.0, "Helvetica18 x 2.0"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18", 3.0, "Helvetica18 x 3.0"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18", 4.0, "Helvetica18 x 4.0"]; FontSubMenu["Helvetica"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica10i", 1.0, "Helvetica10 italic"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica14i", 1.0, "Helvetica14 italic"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18i", 1.0, "Helvetica18 italic"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18i", 1.5, "Helvetica18 italic x 1.5"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18i", 2.0, "Helvetica18 italic x 2.0"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18i", 3.0, "Helvetica18 italic x 3.0"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18i", 4.0, "Helvetica18 italic x 4.0"]; FontSubMenu["Helvetica italic"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica10b", 1.0, "Helvetica10 bold"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica14b", 1.0, "Helvetica14 bold"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18b", 1.0, "Helvetica18 bold"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18b", 1.5, "Helvetica18 bold x 1.5"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18b", 2.0, "Helvetica18 bold x 2.0"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18b", 3.0, "Helvetica18 bold x 3.0"]; [] ¬ FontEntry["Xerox/TiogaFonts/Helvetica18b", 4.0, "Helvetica18 bold x 4.0"]; FontSubMenu["Helvetica bold"]; [] ¬ FontEntry["Xerox/TiogaFonts/oldenglish10", 1.0, "Oldenglish10"]; [] ¬ FontEntry["Xerox/TiogaFonts/math10", 1.0, "Math10"]; [] ¬ FontEntry["Xerox/TiogaFonts/timesroman10", 1.0, "TimesRoman10"]; [] ¬ FontEntry["Xerox/TiogaFonts/hippo10", 1.0, "Hippo10"]; [] ¬ FontEntry["Xerox/TiogaFonts/hebrew14", 1.0, "Hebrew14"]; [] ¬ FontEntry["Xerox/TiogaFonts/cyrillic12", 1.0, "Cyrillic12"]; [] ¬ FontEntry["Xerox/TiogaFonts/cream12", 1.0, "Cream12"]; FontSubMenu["Others"]; }; InitColors: PROC [] = { ColorEntry: PROC [name: Rope.ROPE, color: Imager.Color] RETURNS [cs: REF ColorSpec] = { cs ¬ NEW[ColorSpec ¬ [ color: color, name: name ]]; colorChoiceList ¬ CONS[ [name, cs, NIL, "Color for painting", SetColorButtonHit], colorChoiceList ]; }; [] ¬ ColorEntry["invert", ImagerBackdoor.invert]; [] ¬ ColorEntry["H", ImagerColor.ColorFromHighlight[[0.5, 0.5]]]; [] ¬ ColorEntry["B", ImagerColor.ColorFromRGB[[0.0, 0.0, 1.0]]]; [] ¬ ColorEntry["G", ImagerColor.ColorFromRGB[[0.0, 1.0, 0.0]]]; [] ¬ ColorEntry["R", ImagerColor.ColorFromRGB[[1.0, 0.0, 0.0]]]; [] ¬ ColorEntry["white", Imager.white]; [] ¬ ColorEntry["1/8", Imager.MakeGray[0.125]]; [] ¬ ColorEntry["2/8", Imager.MakeGray[0.25]]; [] ¬ ColorEntry["3/8", Imager.MakeGray[0.375]]; [] ¬ ColorEntry["4/8", Imager.MakeGray[0.5]]; [] ¬ ColorEntry["5/8", Imager.MakeGray[0.625]]; [] ¬ ColorEntry["6/8", Imager.MakeGray[0.75]]; [] ¬ ColorEntry["7/8", Imager.MakeGray[0.875]]; defaultColorData ¬ ColorEntry["black", Imager.black]; }; InitFollows: PROC [] = { FollowEntry: PROC [name: Rope.ROPE, follow: BOOL, help: Rope.ROPE] RETURNS [f: REF FollowSpec] = { f _ NEW[FollowSpec _ [follow: follow, name: name]]; followChoiceList ¬ CONS[ [image: name, key: f, impl: NIL, help: help, notify: SetFollowButtonHit], followChoiceList ]; }; defaultFollowData ¬ FollowEntry["Follow: independent", FALSE, "Everyone can see a different page."]; [] ¬ FollowEntry["Follow: synchronous", TRUE, "Everyone sees the page I see."]; }; ControlPanel: TYPE = REF ControlPanelRec; ControlPanelRec: TYPE = RECORD [ d: Data, controlShell: Widget ¬ NIL ]; SwapControlPanel: ENTRY PROC [d: Data, c: ControlPanel] RETURNS [old: ControlPanel ¬ NIL] = { IF d#NIL THEN { old ¬ d.controlPanel; d.controlPanel ¬ c }; }; SetControlPanel: PROC [d: Data, c: ControlPanel] = { c ¬ SwapControlPanel[d, c]; IF c#NIL THEN { controlShell: Widget ¬ c.controlShell; IF controlShell#NIL THEN XTkWidgets.DestroyShell[controlShell] }; }; MotionHistoryHit: XTkWidgets.ButtonHitProcType = { d: Data ~ NARROW[registerData]; IF d.motionType=0 THEN { d.motionType ¬ 1; XTkWidgets.SetText[widget, "Motion: events "]; } ELSE { d.motionType ¬ 0; XTkWidgets.SetText[widget, "Motion: history "]; }; }; RenameSessionHit: XTkWidgets.ButtonHitProcType = { instance: Slate.Instance ~ NARROW[registerData]; session: Session ¬ instance.session; nameWidget: Widget ¬ NARROW[callData]; name: Rope.ROPE ¬ XTkWidgets.GetText[nameWidget]; IF session#NIL THEN SetSessionName[session, name]; }; SetFollowButtonHit: XTkWidgets.ButtonHitProcType = { instance: Slate.Instance ~ NARROW[registerData]; f: REF FollowSpec ~ NARROW[callData]; d: Data ~ NARROW[instance.uiPrivate]; ChooseFollow[d, f]; }; CreateControlPanelWidget: PROC [d: Data] = { c: ControlPanel ¬ NEW[ControlPanelRec ¬ [d: d]]; shellWidget: Widget ¬ c.controlShell ¬ XTkWidgets.CreateShell[ rootTQ: d.tq, className: $SlateControl, windowHeader: "Slate Control", packageName: "SlateControl", shortName: "Slate Control" ]; logWidget: Widget ¬ XTkWidgets.CreateStreamWidget[widgetSpec: [geometry: XTk.G[-1, 60]]]; newPageS: Widget ~ XTkWidgets.CreateButton[ text: "NewPage", hitProc: NewPageHit, registerData: d, tq: d.tq ]; pageSizeBut: Widget ~ XTkWidgets.CreateToggle[ choices: LIST[["(large)", $large], ["(small)", $small]] , hitProc: PageSizeHit, registerData: d, tq: d.tq ]; clearPage: Widget ~ XTkWidgets.CreateButton[ text: "ClearPage ", hitProc: ClearPageButtonHit, registerData: d, tq: d.tq ]; deletePage: Widget ~ XTkWidgets.CreateButton[ text: "DeletePage ", hitProc: DeletePageButtonHit, registerData: d, tq: d.tq ]; deleteAllPages: Widget ~ XTkWidgets.CreateButton[ text: "DeleteAllPages ", hitProc: DeleteAllPagesButtonHit, registerData: d, tq: d.tq ]; name: Widget ¬ XTkWidgets.CreateLabel[]; topControlsCont: Widget ~ XTkWidgets.CreateXStack[[], LIST[newPageS, pageSizeBut, clearPage, deletePage, deleteAllPages, name] ]; fancyControlsCont: Widget ~ SlateControls.MakeRowWidget[d.instance, $Main]; createColorMenu: XTkPopUps.WidgetCreateClosure ¬ XTkPopUps.WidgetCreateClosureFromChoiceList[list: colorChoiceList, defaultNotify: SetColorButtonHit, notifyTQ: d.tq]; colorChoiceWidget: Widget ¬ d.colorWidget ¬ XTkPopUps.CreatePopUpButton[ text: " ...color... ", createMenu: createColorMenu, registerData: d ]; createFontMenu: XTkPopUps.WidgetCreateClosure ¬ XTkPopUps.WidgetCreateClosureFromChoiceList[list: fontChoiceList, defaultNotify: SetFontButtonHit, notifyTQ: d.tq]; fonts: Widget ¬ d.fonts ¬ XTkPopUps.CreatePopUpButton[ text: " ...font... ", createMenu: createFontMenu, registerData: d ]; motion: Widget ~ XTkWidgets.CreateButton[ text: "Motion: events ", hitProc: MotionHistoryHit, registerData: d ]; controls1Cont: Widget ¬ XTkWidgets.CreateXStack[[], LIST[colorChoiceWidget, fonts, motion] ]; topYCont: Widget _ XTkWidgets.CreateYStack[[], LIST[XTkWidgets.HRule[], logWidget, XTkWidgets.HRule[], topControlsCont, controls1Cont, fancyControlsCont] ]; XTkSharedCell.BindText[d.instance.session.nameCell, name]; XTk.SetWidgetFlag[logWidget, varyingFlag]; XTkWidgets.SetShellChild[c.controlShell, topYCont]; XTkWidgets.BindScreenShell[c.controlShell, d.shellWidget.connection ! Xl.connectionNotCreated => { IO.PutRope[d.instance.log, Rope.Concat["Failed: ", why.reason]]; GOTO oops }]; XTkWidgets.BindStream[logWidget, d.instance.log]; SetControlPanel[d, c]; TrackColor[d]; TrackThickness[d]; TrackPageSize[d, pageSizeBut]; XTkWidgets.RealizeShell[c.controlShell]; EXITS oops => {}; }; CreateControlPanelButtonHit: XTkWidgets.ButtonHitProcType = { d: Data ~ NARROW[registerData]; CreateControlPanelWidget[d]; }; CreateSlateWidget: PROC [] RETURNS [Data] = { d: Data ¬ NEW[DataRec ¬ [ context: nowhereContext, followTextCell: SharedCell.Create[initial: defaultFollowData.name] ]]; tq: Xl.TQ ¬ d.tq ¬ Xl.CreateTQ[order: XTk.rootLockingOrder]; shell: Widget ¬ d.shellWidget ¬ XTkWidgets.CreateShell[ rootTQ: tq, className: $Slate, windowHeader: Rope.Concat["Slate on ", Identification.Self[]], packageName: "Slate", shortName: "Slate" ]; bitmapScroller: Widget ¬ d.bitmapScroller ¬ XTkBitmapScroller.CreateBitmapScroller[scrollTQ: tq, scrollData: d, scrolledCallBack: ScrolledCallBack]; widthLabelWidget: Widget ~ XTkWidgets.CreateLabel[[], "W:"]; widthChoiceWidget: Widget ~ XTkWidgets.CreateChoices[ choices: LIST [ ["1", NEW[INT ¬ 1]], ["2", NEW[INT ¬ 2]], ["3", NEW[INT ¬ 3]], ["4", NEW[INT ¬ 4]], ["6", NEW[INT ¬ 6]], ["10", NEW[INT ¬ 10]], ["15", NEW[INT ¬ 15]], ["20", NEW[INT ¬ 20]], ["25", NEW[INT ¬ 25]], ["30", NEW[INT ¬ 30]] ], hitProc: ThicknessButtonHit, registerData: d ]; sname: Widget ¬ d.sname ¬ XTkWidgets.CreateLabel[]; eraserWidthWidget: Widget ¬ d.eraserWidthWidget ¬ XTkWidgets.CreateLabel[[], EraserExplanation[d]]; invertMouseWidget: Widget ¬ d.invertMouseWidget ¬ XTkWidgets.CreateButton[ text: "...", hitProc: InvertButtonsButtonHit, registerData: d, tq: tq ]; createControlPanel: Widget ~ XTkWidgets.CreateButton[ text: "Panel ", hitProc: CreateControlPanelButtonHit, registerData: d, tq: tq ]; newPage: Widget ~ XTkWidgets.CreateButton[ text: "Page", hitProc: NewPageHit, registerData: d, tq: tq ]; safe: Widget ~ XTkWidgets.CreateButton[ text: "Safe", hitProc: SafeHit, registerData: d, tq: tq ]; controls1Cont: Widget ¬ XTkWidgets.CreateXStack[[], LIST[createControlPanel, widthLabelWidget, widthChoiceWidget, eraserWidthWidget, invertMouseWidget, newPage, safe, sname] ]; pagesCont: Widget ¬ d.pagesCont ¬ XTkWidgets.CreateYStack[[geometry: [size: [width: 20, height: Xl.dontUse]]], NIL]; mainAreaCont: Widget ¬ XTkWidgets.CreateXStack[[], LIST[pagesCont, XTkWidgets.VRule[], bitmapScroller] ]; topYCont: Widget _ XTkWidgets.CreateYStack[[geometry: [pos: [1, 1], size: [600, 400], borderWidth: Xl.dontUse]], LIST[XTkWidgets.HRule[], controls1Cont, XTkWidgets.HRule[], mainAreaCont] ]; bmw: Widget _ d.realBitmapWidget ¬ XTkBitmapScroller.GetImplWidget[bitmapScroller]; XTk.PutWidgetProp[bmw, $BitmapBorder, NEW[INT ¬ 1]]; XTkWidgets.SetShellChild[shell, topYCont]; XTk.AddPermanentMatch[bmw, [proc: TipEvent, handles: Xl.CreateEventFilter[keyPress, buttonPress, buttonRelease, motionNotify], tq: tq, data: d], [buttonMotion: TRUE, buttonRelease: TRUE, buttonPress: TRUE, keyRelease: TRUE, keyPress: TRUE]]; XTk.AddPermanentMatch[bmw, [proc: ClientMessageEvent, handles: Xl.CreateEventFilter[clientMessage], tq: tq, data: d]]; XTk.RegisterNotifier[shell, XTk.postWidgetDestructionKey, ShellDestroyed, d]; XTkWidgets.SetFocusMethod[shell: shell, focusProtocol: true, inputHint: false]; --Globally active XTk.RegisterNotifier[shell, XTk.preWindowCreationKey, HackWindowCreation, d]; TrackInvertButtons[d]; ForkOps.ForkPeriodically[501, PeriodicalFork, d]; RETURN [d]; }; HackWindowCreation: XTk.WidgetNotifyProc = { d: Data ~ NARROW[registerData]; c: Xl.Connection ¬ widget.connection; screen: Xl.Screen ¬ widget.screenDepth.screen; pGC, eGC: Xl.GContext; d.directPaintGC ¬ pGC ¬ Xl.MakeGContext[c, widget.window]; Xl.SetGCForeground[pGC, screen.blackPixel]; Xl.SetGCBackground[pGC, screen.whitePixel]; Xl.SetGCFunction[pGC, copy]; Xl.SetGCCapStyle[pGC, round]; Xl.SetGCJoinStyle[pGC, round]; d.directEraseGC ¬ eGC ¬ Xl.MakeGContext[c, widget.window]; Xl.SetGCFunction[gc: eGC, function: copy]; Xl.SetGCForeground[eGC, screen.blackPixel]; Xl.SetGCBackground[eGC, screen.whitePixel]; d.directErasePixmap ¬ Xl.CreatePixmap[c, screen.root.drawable, [4, 4], widget.screenDepth.depth]; TRUSTED { Xl.PutImage[c: c, drawable: d.directErasePixmap.drawable, gc: eGC, size: [4, 4], dest: [0, 0], base: LOOPHOLE[@stippleSpace[0]], offx: 0, offy: 0, scanLineBytes: 4, bitsPerPixel: 1]; }; Xl.SetGCTile[eGC, d.directErasePixmap]; Xl.SetGCFillStyle[eGC, tiled]; Xl.SetGCLineWidth[eGC, d.eraserWidth]; Xl.SetGCCapStyle[eGC, round]; Xl.SetGCJoinStyle[eGC, round]; }; stippleSpace: REF ARRAY [0..4) OF CARD32 = NEW[ARRAY [0..4) OF CARD32 ¬ [088888888H, 0, 022222222H, 0]]; PeriodicalFork: PROC [data: REF] = { d: Data ~ NARROW[data]; IF ~d.delayMore THEN {Xl.Enqueue[d.tq, TQPaintDelayed, d, NIL]; d.delayMore ¬ TRUE} ELSE d.delayMore ¬ d.delayedPaintList=NIL; }; TQPaintDelayed: Xl.EventProcType = { d: Data ~ NARROW[clientData]; PaintDelayed[d]; }; PaintDelayed: <> PROC [d: Data] = { hl: LIST OF DelayedPaintRec ¬ d.delayedPaintList; d.delayedPaintList ¬ NIL; FOR l: LIST OF DelayedPaintRec ¬ hl, l.rest WHILE l#NIL DO Imager.MaskVector[d.context, [Cx[d, l.first.p1.x], Cy[d, l.first.p1.y]], [Cx[d, l.first.p2.x], Cy[d, l.first.p2.y]] ]; ENDLOOP; }; ShellDestroyed: XTk.WidgetNotifyProc = { d: Data ~ NARROW[registerData]; SetControlPanel[d, NIL]; DestroyInstance[d.instance]; --is idempotent ForkOps.Stop[PeriodicalFork, d] }; ContinuePaint: <> PROC [d: Data, p: Xl.Point] = INLINE { IF d.mode=$paint THEN { IF ~d.hasStart THEN SetStart[d, p]; --also paints the first null vector to draw a nice point IF d.imageMode.color.color=Imager.black THEN { d.delayMore ¬ TRUE; d.delayedPaintList ¬ CONS[[d.startP, p], d.delayedPaintList]; Xl.DrawLine[d.realBitmapWidget.connection, d.realBitmapWidget.window, d.startP, p, d.directPaintGC]; Xl.Flush[d.realBitmapWidget.connection]; } ELSE { Imager.MaskVector[d.context, [Cx[d, d.startP.x], Cy[d, d.startP.y]], [Cx[d, p.x], Cy[d, p.y]] ]; }; d.startP ¬ p; }; }; ContinueErase: <> PROC [d: Data, p: Xl.Point] = INLINE { IF d.mode=$erase AND d.eraseTrajectory#NIL THEN { IF ~d.hasStart THEN SetStart[d, p]; d.eraseTrajectory ¬ ImagerPath.LineTo[d.eraseTrajectory, [x: Cx[d, p.x], y: Cy[d, p.y]]]; --also paints the first null vector to draw a nice circle on button down Xl.DrawLine[d.realBitmapWidget.connection, d.realBitmapWidget.window, d.startP, p, d.directEraseGC]; Xl.Flush[d.realBitmapWidget.connection]; d.startP ¬ p; }; }; ContinueAny: <> PROC [d: Data, p: Xl.Point] = { SELECT d.mode FROM $paint => ContinuePaint[d, p]; $erase => ContinueErase[d, p]; ENDCASE => {}; }; EndPaint: <> PROC [d: Data, p: Xl.Point] = INLINE { IF d.hasStart AND d.mode=$paint THEN ContinuePaint[d, p]; d.hasStart ¬ FALSE; d.mode ¬ NIL; }; EndErase: <> PROC [d: Data, p: Xl.Point] = { IF d.hasStart AND d.mode=$erase THEN ContinueErase[d, p]; IF d.eraseTrajectory#NIL THEN { action: PROC [] = { Imager.SetColor[d.context, Imager.white]; Imager.SetStrokeWidth[d.context, d.eraserWidth]; Imager.MaskStrokeTrajectory[d.context, d.eraseTrajectory]; }; Imager.DoSave[d.context, action]; d.eraseTrajectory ¬ NIL; }; IF d.mode=$erase THEN { Imager.SetStrokeWidth[d.context, d.imageMode.thickness]; Imager.SetColor[d.context, d.imageMode.color.color]; }; d.mode ¬ NIL; d.hasStart ¬ FALSE; }; EndAny: <> PROC [d: Data, p: Xl.Point] = { SELECT d.mode FROM $paint => EndPaint[d, p]; $erase => EndErase[d, p]; ENDCASE => {}; d.mode ¬ NIL; }; PaintSetStart: <> PROC [d: Data, p: Xl.Point] = INLINE { IF d.mode=$erase THEN EndErase[d, p]; d.mode ¬ $paint; SetStart[d, p]; }; EraseSetStart: <> PROC [d: Data, p: Xl.Point] = INLINE { PaintDelayed[d]; IF d.mode#$erase AND ~d.hasStart THEN { SetStart[d, p]; d.mode ¬ $erase; d.eraseTrajectory ¬ ImagerPath.MoveTo[[x: Cx[d, p.x], y: Cy[d, p.y]]]; Imager.SetStrokeWidth[d.context, d.eraserWidth]; Imager.SetColor[d.context, eraseColor]; ContinueErase[d, p]; }; }; SetStart: PROC [d: Data, p: Xl.Point] = INLINE { d.startP ¬ p; d.hasStart ¬ TRUE; }; GridAndSetFocus: <> PROC [d: Data] = { d.textOfLine ¬ NIL; d.inputfocusY ¬ d.inputfocusY/4*4; --gridding y to make alignment easier Imager.SetXY[d.context, [d.inputfocusX, d.inputfocusY]]; }; AdvanceLine: <> PROC [d: Data, leftX: BOOL ¬ FALSE] = { IF leftX THEN d.inputfocusX ¬ 5; d.inputfocusY ¬ d.inputfocusY - d.lineHeight; GridAndSetFocus[d]; }; PaintRope: <> PROC [d: Data, r: Rope.ROPE] = { ENABLE RuntimeError.UNCAUGHT => GOTO Oops; BackSpaceChar: PROC [d: Data] = { leng: INT ~ Rope.Length[d.textOfLine]; IF leng>0 THEN { BackWardsProc: ImagerFont.XStringProc = { charAction[Char.Make[set: 0, code: ORD[ch]]]; }; ch: CHAR ~ Rope.Fetch[d.textOfLine, leng-1]; d.textOfLine ¬ Rope.Substr[d.textOfLine, 0, leng-1]; Imager.SetColor[d.context, Imager.white]; Imager.ShowBackward[d.context, BackWardsProc]; Imager.SetColor[d.context, d.imageMode.color.color]; }; }; length: INT ~ Rope.Length[r]; nextPos: INT ¬ 0; d.hasStart ¬ FALSE; IF d.mode=$erase THEN EndErase[d, d.startP]; d.lineHeight ¬ d.imageMode.requiredLineHeight; Imager.SetFont[d.context, d.imageMode.font]; IF length=1 THEN { SELECT Rope.Fetch[r, 0] FROM Ascii.BS, Ascii.DEL => {BackSpaceChar[d]; RETURN}; ENDCASE => {} }; DO crPos: INT ~ Rope.SkipTo[s: r, pos: nextPos, skip: "\l\r"]; IF crPos>nextPos THEN { piece: Rope.ROPE ¬ Rope.Substr[r, nextPos, crPos-nextPos]; Imager.ShowRope[d.context, piece]; d.textOfLine ¬ Rope.Concat[d.textOfLine, piece]; }; nextPos ¬ crPos+1; IF nextPos>=length THEN RETURN; AdvanceLine[d, FALSE]; ENDLOOP; EXITS Oops => {}; }; SetInputFocus: <> PROC [d: Data, x, y: REAL] = { i: Instance ¬ d.instance; XTkWidgets.SetFocus[shell: i.shell, time: Xl.currentTime, child: i.surface]; d.inputfocusX ¬ x; d.inputfocusY ¬ y; GridAndSetFocus[d]; }; ScrolledCallBack: XTkBitmapScroller.ScrolledCallBackProc = { d: Data ¬ NARROW[data]; PaintDelayed[d]; d.scrollPos ¬ pos; }; SetFontButtonHit: XTkWidgets.ButtonHitProcType = { d: Data ~ NARROW[registerData]; fd: REF FontData ~ NARROW[callData]; ChooseFont[d, fd]; }; SetColorButtonHit: XTkWidgets.ButtonHitProcType = { d: Data ~ NARROW[registerData]; cs: REF ColorSpec ~ NARROW[callData]; ChooseColor[d, cs]; }; ChooseColor: PROC [d: Data, cs: REF ColorSpec] = { d.imageMode.color ¬ cs; IF d.mode#$erase AND d.context#NIL THEN Imager.SetColor[d.context, d.imageMode.color.color]; TrackColor[d]; }; UseColor: PROC [d: Data] = { IF d.mode#$erase THEN Imager.SetColor[d.context, d.imageMode.color.color]; }; TrackColor: PROC [d: Data] = { colorWidget: Widget ¬ d.colorWidget; cs: REF ColorSpec ¬ d.imageMode.color; IF colorWidget#NIL AND cs#NIL THEN XTkWidgets.SetText[colorWidget, Rope.Cat["Color: ", cs.name, " "]]; }; ChooseFollow: PROC [d: Data, f: REF FollowSpec] = { page: Slate.Page ¬ Slate.ActivePage[d.instance.session]; d.followMode _ f; SharedCell.SetContents[d.followTextCell, f.name]; IF d.followMode.follow AND LocalActivePage[d]#page AND page#NIL THEN LocalActivatePage[d, page] }; MessageHit: XTkWidgets.ButtonHitProcType = { instance: Slate.Instance ~ NARROW[registerData]; tField: Widget ~ NARROW[callData]; message: Rope.ROPE ¬ XTkWidgets.GetText[tField]; CallGlobalForAllInstancesOfSession[session: instance.session, event: $Message, callData: Rope.Cat[Xl.ServerName[widget.connection], "> ", message, "\n"]] }; IsColorWidget: PROC [w: Widget] RETURNS [BOOL] = { shell: Widget ¬ XTk.RootWidget[w]; RETURN [XTk.GetWidgetProp[shell, $Color]#NIL] }; ClientMessageEvent: Xl.EventProcType = { d: Data ~ NARROW[clientData]; SELECT event.type FROM Xl.EventCode.clientMessage => { cme: Xl.ClientMessageEvent ~ NARROW[event]; a: Xl.XAtom ¬ Xl.MakeAtom[cme.connection, "PARC_Handwriting"]; IF cme.typeAtom=a AND cme.w[0]=Xl.AtomId[a] THEN { i: INT32 ~ LOOPHOLE[cme.w[1]]; stuffKey: INT32 ~ -1; nextKey: INT32 ~ -3; SELECT i FROM stuffKey => { text: Rope.ROPE ¬ XlCutBuffers.Get[cme.connection]; PaintRope[d, text]; }; nextKey => { AdvanceLine[d, TRUE] }; ENDCASE => {}; }; }; ENDCASE => {}; }; Cx: PROC [d: Data, x: INT] RETURNS [INT] = INLINE { RETURN [x-d.scrollPos.x] }; Cy: PROC [d: Data, y: INT] RETURNS [INT] = INLINE { RETURN [d.bitmapSize.height-y+d.scrollPos.y] }; listOfSpecials: LIST OF Xl.KeySym ~ LIST[KeySymsSun.Paste, KeySymsOSF.Paste, KeySymsHP.Paste]; TipEvent: Xl.EventProcType = { d: Data ~ NARROW[clientData]; Motion: PROC [d: Data, stopT: Xl.TimeStamp, stopP: Xl.Point, stop: BOOL] = { i: Instance ~ d.instance; cnt: NAT ¬ 0; tc: REF Xl.TimeCoords ¬ NIL; IF d.motionType=0 THEN { tc ¬ Xl.GetMotionEvents[c: i.surface.connection, window: i.surface.window, start: d.motionT, stop: stopT]; cnt ¬ tc.size }; IF cnt=0 THEN { IF stop THEN EndAny[d, stopP] ELSE ContinueAny[d, stopP]; } ELSE { FOR i: NAT IN [0..cnt-1) DO ContinueAny[d, tc[i].p]; ENDLOOP; BEGIN l: NAT ~ cnt-1; IF stop THEN EndAny[d, tc[l].p] ELSE ContinueAny[d, tc[l].p]; END; }; d.motionT ¬ stopT; d.motionP ¬ stopP; }; SELECT event.type FROM Xl.EventCode.buttonPress => { e: Xl.ButtonPressEvent ~ NARROW[event]; paint: BOOL ¬ e.button=1; IF e.button=2 THEN { SetInputFocus[d, Cx[d, e.pos.x], Cy[d, e.pos.y]]; RETURN }; IF (e.state.button1 OR e.state.button2 OR e.state.button3) AND d.buttonMode#$focus THEN { RETURN; }; SELECT d.buttonMode FROM $normal => {}; $inverted => paint ¬ NOT paint; $focus => { IF (e.state.button1 OR e.state.button2 OR e.state.button3) THEN { insert: Rope.ROPE ¬ XlCutBuffers.Get[e.connection]; PaintRope[d, insert]; } ELSE SetInputFocus[d, Cx[d, e.pos.x], Cy[d, e.pos.y]]; RETURN }; ENDCASE => RETURN; d.motionT ¬ e.timeStamp; d.motionP ¬ e.pos; IF paint THEN PaintSetStart[d, e.pos] ELSE EraseSetStart[d, e.pos]; }; Xl.EventCode.buttonRelease => { e: Xl.ButtonReleaseEvent ~ NARROW[event]; cnt: INT ¬ ORD[e.state.button1] + ORD[e.state.button2] + ORD[e.state.button3]; stop: BOOL ¬ cnt<=1; --only stop when last button is released Motion[d, e.timeStamp, e.pos, stop]; IF stop THEN { d.buttonModeFancy ¬ FALSE; IF d.buttonMode=$focus THEN { d.buttonMode ¬ $normal; TrackInvertButtons[d]; }; }; }; Xl.EventCode.motionNotify => { e: Xl.MotionNotifyEvent ~ NARROW[event]; Motion[d, e.timeStamp, e.pos, FALSE]; }; Xl.EventCode.keyPress => { char: CHAR; keysym: Xl.KeySym; matched: Xl.KeySym; isModifier: BOOL; e: Xl.KeyPressEvent ~ NARROW[event]; [char: char, keysym: keysym, matched: matched, isModifier: isModifier] ¬ XlAscii.Convert[e.connection, e.keyCode, e.state, listOfSpecials]; IF isModifier THEN RETURN; SELECT matched FROM KeySymsSun.Paste, KeySymsOSF.Paste, KeySymsHP.Paste => { insert: Rope.ROPE ¬ XlCutBuffers.Get[e.connection]; PaintRope[d, insert]; }; ENDCASE => { SELECT char FROM '\l => AdvanceLine[d, TRUE]; '\r => AdvanceLine[d, FALSE]; ENDCASE => PaintRope[d, Rope.FromChar[char]]; }; }; ENDCASE => {}; }; InstallBitmap: <> PROC [d: Data, bitmap: XlBitmap.Bitmap] = { ChooseThickness[d, 1]; ChooseFont[d, defaultFontData]; ChooseColor[d, defaultColorData]; ChooseFollow[d, defaultFollowData]; ResetBM[d, bitmap]; d.inputfocusY ¬ d.bitmapSize.height; AdvanceLine[d, TRUE]; }; InvertButtonsButtonHit: XTkWidgets.ButtonHitProcType = { d: Data ~ NARROW[registerData]; SELECT d.buttonMode FROM $normal => d.buttonMode ¬ IF d.buttonModeFancy THEN $focus ELSE $inverted; $inverted => d.buttonMode ¬ IF d.buttonModeFancy THEN $focus ELSE $normal; ENDCASE => d.buttonMode ¬ $normal; d.buttonModeFancy ¬ NOT d.buttonModeFancy; TrackInvertButtons[d]; }; TrackInvertButtons: PROC [d: Data] = { invertMouseWidget: Widget ¬ d.invertMouseWidget; IF invertMouseWidget#NIL THEN { text: Rope.ROPE; SELECT d.buttonMode FROM $normal => text ¬ "painting"; $inverted => text ¬ "erasing"; $focus => text ¬ "focus"; ENDCASE => {}; XTkWidgets.SetText[invertMouseWidget, text] }; }; ThicknessButtonHit: XTkWidgets.ButtonHitProcType = { d: Data ~ NARROW[registerData]; width: INT ~ NARROW[callData, REF INT]­; ChooseThickness[d, width]; }; ClearPageButtonHit: <> XTkWidgets.ButtonHitProcType = { d: Data ~ NARROW[registerData]; page: Page ¬ LocalActivePage[d]; IF page=NIL THEN IO.PutRope[d.log, "No active page\n"] ELSE { ImagerSample.Clear[XlBitmap.GetSM[page.activeArea]]; XlBitmap.Refresh[page.activeArea]; } }; Bind: PROC [d: Data, i: Instance] = { d.instance ¬ i; i.uiPrivate ¬ d; d.log ¬ i.log; i.shell ¬ d.shellWidget; i.surface ¬ d.realBitmapWidget; InstallBitmap[d, i.session.activePage.activeArea]; InitPages[d]; XTkSharedCell.BindText[d.instance.session.nameCell, d.sname]; Slate.CallGlobalEvent[i, i.session, $UICreated]; PageHasBeenActivated[instance: i, session: i.session, callData: NIL, registerData: NIL, event: NIL]; }; DoSharedTool: PROC [session: Session, server: REF, log: IO.STREAM] = { instance: Instance ¬ NewInstance[session]; d: Data _ CreateSlateWidget[]; XTkWidgets.BindScreenShell[d.shellWidget, server ! Xl.connectionNotCreated => { IO.PutRope[log, Rope.Concat["Failed: ", why.reason]]; GOTO oops }]; Bind[d, instance]; XTkWidgets.RealizeShell[d.shellWidget]; IO.PutRope[log, " Slate tool created\n"]; EXITS oops => {}; }; GetTQ: PROC [instance: Instance] RETURNS [Xl.TQ] = { RETURN [NARROW[instance.uiPrivate, Data].tq] }; NewInstanceTool: PUBLIC PROC [session: Session, server: REF] RETURNS [instance: Instance] = BEGIN d: Data _ CreateSlateWidget[]; XTkWidgets.BindScreenShell[d.shellWidget, server ! Xl.connectionNotCreated => { IO.PutRope[session.debugLog, Rope.Concat["Failed: ", why.reason]]; GOTO oops }]; instance ¬ NewInstance[session]; Bind[d, instance]; XTkWidgets.RealizeShell[d.shellWidget]; IO.PutRope[session.debugLog, " Slate tool created\n"]; EXITS oops => {}; END; SelectSessionButtonHit: XTkWidgets.ButtonHitProcType = { instance: Slate.Instance ~ NARROW[registerData]; session: Session ¬ instance.session; globalLastSession ¬ session; }; DeleteAllPagesButtonHit: XTkWidgets.ButtonHitProcType = { d: Data ~ NARROW[registerData]; bpp: NAT ¬ IF IsColorWidget[d.shellWidget] THEN 8 ELSE 1; session: Session ¬ d.instance.session; newPage: Page ¬ NewPage[session: session, bpp: bpp]; EachPage: PageProc = { IF page#newPage AND page#NIL THEN RemovePage[page]; }; ActivatePage[newPage]; [] ¬ EnumeratePages[session, EachPage]; }; DeletePageButtonHit: XTkWidgets.ButtonHitProcType = { d: Data ~ NARROW[registerData]; session: Session ¬ d.instance.session; delete: Page ¬ LocalActivePage[d]; pages: LIST OF Page ¬ CurrentListOfPages[session]; IF pages#NIL THEN { nextPage: Page ¬ pages.first; IF pages.rest=NIL THEN { IO.PutRope[d.log, "must keep one page\n"]; RETURN }; FOR list: LIST OF Page ¬ pages, list.rest WHILE list#NIL DO IF list.first=delete THEN { IF list.rest#NIL THEN {nextPage ¬ list.rest.first}; EXIT; }; ENDLOOP; IF nextPage=delete THEN nextPage ¬ NIL; ActivatePage[nextPage]; IF delete#NIL THEN RemovePage[delete]; }; }; EraserExplanation: PROC [d: Data] RETURNS [Rope.ROPE] = { RETURN [IO.PutFR1[" (%4g)", IO.int[-d.eraserWidth]]] }; CreateSharedToolMenu: XTkPopUps.CreateWidgetProc = { in: REF InstName ~ NARROW[closureData]; menu: Widget ~ XTkWidgets.CreateYStack[]; OneChild: PROC [text: Rope.ROPE, key: REF ¬ NIL] = { child: Widget ¬ XTkWidgets.CreateButton[text: text, hitProc: SharedToolButtonHit, registerData: in, callData: key]; XTkWidgets.AppendChild[menu, child]; }; OneChild["Same X server", NIL]; OneChild["Using name field", $sharedWidget]; RETURN [menu]; }; SharedToolButtonHit: XTkWidgets.ButtonHitProcType = { server: REF ANY; in: REF InstName ~ NARROW[registerData]; WITH callData SELECT FROM a: ATOM =>{ server ¬ XTkWidgets.GetText[in.name]; }; ENDCASE => server ¬ widget.connection; DoSharedTool[in.instance.session, server, in.instance.log]; }; ChooseThickness: PROC [d: Data, width: INT] = { d.imageMode.thickness ¬ width ¬ MIN[MAX[width, 1], 200]; d.eraserWidth ¬ MAX[width, 3]*3 + 3; d.eraseMinimum ¬ d.eraserWidth/10+1.5; UseThickness[d]; TrackThickness[d]; }; UseThickness: PROC [d: Data] = { IF d.context#NIL THEN { Imager.SetStrokeWidth[d.context, d.imageMode.thickness]; }; IF d.directPaintGC#NIL THEN { Xl.SetGCLineWidth[d.directPaintGC, d.imageMode.thickness]; Xl.SetGCLineWidth[d.directEraseGC, d.eraserWidth]; }; }; TrackThickness: PROC [d: Data] = { eraserWidthWidget: Widget ¬ d.eraserWidthWidget; IF eraserWidthWidget#NIL THEN XTkWidgets.SetText[eraserWidthWidget, EraserExplanation[d]] }; ChooseFont: PROC [d: Data, fd: REF FontData] = { ENABLE Imager.Error => GOTO Oops; font: Imager.Font; e: ImagerFont.Extents; d.currentFontData ¬ fd; font ¬ ImagerFont.Scale[ImagerFont.Find[fd.realName, substituteQuietly], fd.scale]; e ¬ ImagerFont.FontBoundingBox[font]; d.imageMode.font _ font; d.imageMode.requiredLineHeight ¬ Real.Ceiling[e.descent + e.ascent + e.ascent / 5]; TrackFont[d]; EXITS Oops => {}; }; UseFont: PROC [d: Data] = { }; TrackFont: PROC [d: Data] = { fd: REF FontData ¬ d.currentFontData; fonts: Widget ¬ d.fonts; IF fonts#NIL THEN XTkWidgets.SetText[fonts, Rope.Cat["Font: ", fd.presentName, " "]]; }; ParticipantsButtonHit: XTkWidgets.ButtonHitProcType = { instance: Slate.Instance ~ NARROW[registerData]; log: IO.STREAM ~ instance.log; Each: InstanceProc = { WITH instance.uiPrivate SELECT FROM d: Data => { c: Xl.Connection _ d.shellWidget.connection; IF Xl.Alive[c] THEN { IO.PutRope[log, Xl.ServerName[c]]; IO.PutRope[log, " "] } ELSE {IO.PutRope[log, "dead-server "]} }; ENDCASE => {IO.PutRope[log, "unknown-server "]} }; [] _ EnumerateInstances[instance.session, Each]; IO.PutRope[log, "\n"]; }; ActivatePageButtonHit: XTkWidgets.ButtonHitProcType = { d: Data ~ NARROW[registerData]; page: Page ~ NARROW[callData]; IF page#NIL THEN IF d.followMode.follow THEN ActivatePage[page] ELSE LocalActivatePage[d, page]; }; NewPageWithSize: PROC [d: Data, sz: SF.Vec ¬ [0, 0]] = { bpp: NAT ¬ IF IsColorWidget[d.shellWidget] THEN 8 ELSE 1; page: Page ¬ Slate.NewPage[session: d.instance.session, bpp: bpp, sz: sz]; IF d.followMode.follow THEN ActivatePage[page] ELSE LocalActivatePage[d, page]; }; PageSizeHit: XTkWidgets.ButtonHitProcType = { d: Data ~ NARROW[registerData]; [] ¬ PropList.PutProp[d.instance.properties, $PageSize, callData] }; GetPageSizeKey: PROC [instance: Slate.Instance] RETURNS [REF ANY] = { x: REF ANY ¬ PropList.GetProp[instance.properties, $PageSize]; IF x=NIL THEN x ¬ $small; RETURN [x] }; NewPageHit: XTkWidgets.ButtonHitProcType = { largeSz: SF.Vec ¬ [768, 1024]; sz: SF.Vec ¬ [0, 0]; d: Data ~ NARROW[registerData]; SELECT GetPageSizeKey[d.instance] FROM $small => sz ¬ [0, 0]; $large => sz ¬ largeSz; ENDCASE => {}; NewPageWithSize[d, sz]; }; SafeHit: XTkWidgets.ButtonHitProcType = { d: Data ~ NARROW[registerData]; instance: Slate.Instance ~ d.instance; fileName: Rope.ROPE; WITH PropList.GetProp[instance.properties, $SafeName] SELECT FROM r: Rope.ROPE => fileName ¬ r; ENDCASE => fileName ¬ "/tmp/temp.slate"; SlateIO.WriteSlateFile[pages: Slate.CurrentListOfPages[instance.session], fileName: fileName, log: instance.log, server: widget.connection] }; TrackPageSize: PROC [d: Data, w: XTk.Widget] = { XTkWidgets.Choose[w, [callData: GetPageSizeKey[d.instance]]] }; globalLastSession: Session ¬ NIL; MakeOrGetSession: PROC [get: BOOL ¬ FALSE, bpp: INT ¬ 1, name: Rope.ROPE] RETURNS [session: Session ¬ NIL] = { stream: IO.STREAM ¬ debugStream; IF get THEN session ¬ globalLastSession; IF session=NIL THEN { page: Page; IF name#NIL THEN { parameter: REF REF ANY ¬ NEW[REF ANY ¬ name]; Slate.CallGlobalEvent[event: $FindSession, callData: parameter]; WITH parameter­ SELECT FROM s: Session => {globalLastSession ¬ s; RETURN [s]}; ENDCASE => {}; }; session ¬ NewSession[]; page ¬ NewPage[session: session, bpp: bpp]; ActivatePage[page]; IF stream#NIL THEN { FanoutStream.AddSlave[session.debugLog, stream]; }; IF name#NIL THEN Slate.SetSessionName[session, name]; globalLastSession ¬ session; }; }; debugStream: IO.STREAM ¬ NIL; SlateCommand: Commander.CommandProc = { instance: Instance; session: Session; d: Data; n: INT _ 0; bpp: INT _ 1; color: BOOL _ FALSE; createInstance: BOOL _ TRUE; display: Rope.ROPE _ NIL; sessionName: Rope.ROPE _ NIL; DO arg: Rope.ROPE _ CommanderOps.NextArgument[cmd]; MatchPrefix: PROC [s: LONG STRING] RETURNS [BOOL] ~ { run: INT ~ Rope.Run[s1: arg, s2: ConvertUnsafe.ToRope[s], case: FALSE]; RETURN [run = Rope.Size[arg] AND run > 1]; }; BadSwitch: PROC = { CommanderOps.Failed[Rope.Cat["Unknown or ambiguous argument: " , arg, helpMessage]] }; IF arg=NIL THEN EXIT; SELECT TRUE FROM MatchPrefix["-color"] => { color _ TRUE; bpp _ 8 }; MatchPrefix["-noInstance"] => { createInstance _ FALSE }; MatchPrefix["-debug"] => { debugStream _ cmd.out }; MatchPrefix["-session"] => { sessionName _ CommanderOps.NextArgument[cmd] }; MatchPrefix["-display"] => { display _ CommanderOps.NextArgument[cmd] }; Rope.Match["-*", arg] => { BadSwitch[] }; ENDCASE => BadSwitch[]; n _ n+1; ENDLOOP; session ¬ MakeOrGetSession[bpp: bpp, name: sessionName]; IF createInstance THEN { d _ CreateSlateWidget[]; IF color THEN { XTk.PutWidgetProp[d.shellWidget, $Color, $Color]; }; XTkWidgets.BindScreenShell[d.shellWidget, display ! Xl.connectionNotCreated => CommanderOps.Failed[Rope.Concat["Failed: " , why.reason]] ]; instance ¬ NewInstance[session]; Bind[d, instance]; XTkWidgets.RealizeShell[d.shellWidget, [pos: [2, 2]]]; }; }; InitPages: PROC [d: Data] = { EachPage: PageProc = { IF page#NIL THEN CreatePageIndicator[d, page]; }; [] ¬ EnumeratePages[d.instance.session, EachPage]; LocalIndicate[d, d.instance.session.activePage]; }; FindPageWidget: PROC [page: Page, d: Data] RETURNS [pageIndicator: Widget ¬ NIL] = { Each: XTk.EachChild = { IF XTk.GetWidgetProp[child, keyForPage]=page THEN { pageIndicator ¬ child; stop ¬ TRUE }; }; IF page#NIL THEN XTk.ShallowInternalEnumerateChildren[d.pagesCont, Each, page]; }; CreatePageIndicator: PROC [d: Data, page: Page] = { IF page#NIL THEN { pw: Widget; numberText: Rope.ROPE; WITH PropList.GetProp[page.properties, $PageNumber] SELECT FROM ri: REF INT => numberText ¬ IO.PutFR1["%2g", IO.int[ri­]]; ENDCASE => numberText ¬ "xx"; pw ¬ XTkWidgets.CreateButton[text: numberText, hitProc: ActivatePageButtonHit, registerData: d, callData: page ]; XTk.PutWidgetProp[pw, keyForPage, page]; XTkWidgets.AppendChild[d.pagesCont, pw]; }; }; DestroyPageIndicator: PROC [instance: Instance, page: Page] = { WITH instance.uiPrivate SELECT FROM d: Data => { pageIndicator: Widget ¬ FindPageWidget[page, d]; IF pageIndicator#NIL THEN XTk.DestroyWidget[pageIndicator] }; ENDCASE => {}; }; NewPageHasBeenCreated: EventProc = { IF instance#NIL THEN WITH instance.uiPrivate SELECT FROM d: Data => {CreatePageIndicator[d, NARROW[callData]]}; ENDCASE => {}; }; pageNumberKey: REF INT ¬ NEW[INT]; InitPageNumber: PropList.InitializeProcType = { RETURN [NEW[INT¬0]] }; NextPageNum: PROC [session: Session] RETURNS [i: INT ¬ 0] = { x: REF ¬ PropList.GetPropOrInit[session.properties, pageNumberKey, InitPageNumber, session].val; rNum: REF INT ~ NARROW[x]; Monitored: PROC [rNum: REF INT] RETURNS [INT] = { rNum­ ¬ rNum­+1; RETURN [rNum­]; }; IF rNum#NIL THEN i ¬ Monitored[rNum]; }; NewPageHasBeenCreated0: Slate.EventProc = { page: Page ~ NARROW[callData]; IF page#NIL THEN { num: INT ¬ NextPageNum[page.session]; [] ¬ PropList.PutProp[page.properties, $PageNumber, NEW[INT ¬ num]]; }; }; PageHasBeenRemoved: Slate.EventProc = { page: Page ~ NARROW[callData]; IF page#NIL THEN WITH instance.uiPrivate SELECT FROM d: Data => { IF ~d.followMode.follow AND LocalActivePage[d]=page THEN { new: Page ¬ ActivePage[d.instance.session]; IF new=page THEN new ¬ NIL; LocalActivatePage[d, new]; }; DestroyIndicator[d, page]; }; ENDCASE => {}; }; DestroyIndicator: PROC [d: Data, page: Page] = { pageIndicator: Widget ¬ FindPageWidget[page, d]; IF pageIndicator#NIL THEN XTk.DestroyWidget[pageIndicator]; }; LocalIndicate: PROC [d: Data, page: Page] = { oldIndicator: Widget ¬ d.usedPageIndicator; newIndicator: Widget ¬ FindPageWidget[page, d]; IF oldIndicator#NIL AND oldIndicator#newIndicator THEN XTkLabels.SetStyleKey[oldIndicator, NIL]; d.usedPageIndicator ¬ newIndicator; IF newIndicator#NIL THEN XTkLabels.SetStyleKey[newIndicator, $Gray2] }; LocalActivePage: PROC [d: Data] RETURNS [page: Page] = { RETURN [d.page]; }; LocalActivatePage: PROC [d: Data, page: Page] = { IF d.page#page THEN { d.page ¬ page; d.hasStart ¬ FALSE; d.mode ¬ NIL; IF page=NIL THEN ResetBM[d, nowhereBM] ELSE ResetBM[d, page.activeArea]; LocalIndicate[d, page]; }; }; PageHasBeenActivated: Slate.EventProc = { IF instance#NIL THEN WITH instance.uiPrivate SELECT FROM d: Data => IF d.followMode.follow OR d.page=NIL THEN { LocalActivatePage[d, ActivePage[d.instance.session]]; }; ENDCASE => {}; }; ReceiveMessage: Slate.EventProc = { IF instance#NIL THEN { message: Rope.ROPE ¬ NARROW[callData]; IO.PutRope[instance.log, message]; }; }; InstName: TYPE = RECORD [instance: Slate.Instance, name: Widget]; CreateSharingWidget: Slate.EventProc = { shell: Widget ¬ XTkWidgets.CreateShell[ className: $SlateControl, windowHeader: "Slate Multi-User Ops", packageName: "SlateControl", shortName: "Slate Multi" ]; sname: Widget ¬ XTkWidgets.CreateLabel[]; tField: Widget ¬ XTkWidgets.CreateLabeledField[label: "name:", init: "???"]; in: REF InstName ~ NEW[InstName ¬ [instance, tField]]; tq: Xl.TQ ~ GetTQ[instance]; createFollowMenu: XTkPopUps.WidgetCreateClosure ¬ XTkPopUps.WidgetCreateClosureFromChoiceList[list: followChoiceList, defaultNotify: SetFollowButtonHit, notifyTQ: tq]; createSharedToolClosure: XTkPopUps.WidgetCreateClosure ¬ NEW[XTkPopUps.WidgetCreateClosureRec ¬ [CreateSharedToolMenu, in]] ; sharedTool: Widget ~ XTkPopUps.CreatePopUpButton[ text: "SharedSlate", createMenu: createSharedToolClosure ]; participants: Widget ~ XTkWidgets.CreateButton[ text: "Participants", hitProc: ParticipantsButtonHit, registerData: instance ]; msg: Widget ~ XTkWidgets.CreateButton[ text: "Msg", hitProc: MessageHit, registerData: instance, tq: tq, callData: tField ]; windowFollow: Widget _ XTkPopUps.CreatePopUpButton[ text: "WindowFollowing", createMenu: createFollowMenu, registerData: instance ]; selectSession: Widget ~ XTkWidgets.CreateButton[ text: "SelectSession", hitProc: SelectSessionButtonHit, registerData: instance ]; nameSession: Widget ~ XTkWidgets.CreateButton[ text: "RenameSession", hitProc: RenameSessionHit, registerData: instance, callData: tField ]; xCont: Widget ~ XTkWidgets.CreateXStack[[], LIST[sharedTool, participants, msg, selectSession, nameSession] ]; xCont2: Widget ~ XTkWidgets.CreateXStack[[], LIST[windowFollow, sname] ]; topYCont: Widget _ XTkWidgets.CreateYStack[[], LIST[tField, xCont, xCont2]]; XTkSharedCell.BindText[ NARROW[instance.uiPrivate, Data].followTextCell, windowFollow ]; XTkSharedCell.BindText[instance.session.nameCell, sname]; XTkWidgets.SetShellChild[shell, topYCont]; XTkWidgets.BindScreenShell[shell, callData ! Xl.connectionNotCreated => { IO.PutRope[instance.log, Rope.Concat["Failed: ", why.reason]]; GOTO oops }]; SlateControls.LimitLifetime[shell, instance]; XTkWidgets.RealizeShell[shell]; EXITS oops => {} }; helpMessage: Rope.ROPE ~ " Usage: {argument}* argument: -color -debug -noInstance -display x-server-name -session session-name "; Slate.RegisterGlobalEventProc[$Message, ReceiveMessage, NIL]; Slate.RegisterGlobalEventProc[$AddPage, NewPageHasBeenCreated, NIL]; Slate.RegisterGlobalEventProc[$AddPage0, NewPageHasBeenCreated0, NIL]; Slate.RegisterGlobalEventProc[$RemovePage, PageHasBeenRemoved, NIL]; Slate.RegisterGlobalEventProc[$ActivatePage, PageHasBeenActivated, NIL]; InitFonts[]; InitColors[]; InitFollows[]; SlateControls.RegisterButton[$Main, "Sharing", $CreateSharing]; Slate.RegisterGlobalEventProc[$CreateSharing, CreateSharingWidget]; Commander.Register["Slate", SlateCommand, Rope.Concat["Shared drawing tool", helpMessage]]; END. Z SlateWidgetImpl.mesa Copyright Ó 1990, 1992, 1993 by Xerox Corporation. All rights reserved. Created by Christian Jacobi, November 2, 1990 1:04:32 pm PST Weiser, September 3, 1992 8:10 pm PDT Theimer, December 17, 1992 3:15 pm PST Christian Jacobi, August 24, 1993 5:13 pm PDT This used to be a real simple small demo program to show the power of BitmapWidgets. User whishes and MPad performance hacks made it not quite simple anymore. XTkTIP.ChangePseudoHeight[d.instance.surface, sz.height]; Look at /imagerfonts/xerox/tiogafonts/ to find fonts -- --log -- --pages -- -- -- -- XTk.PutWidgetProp[bmw, $OverheadCost, NEW[INT ¬ 1]]; XTk.PutWidgetProp[bmw, $ServerQueueLimit, NEW[INT ¬ 4]]; --direct paining --direct erasing --This is much faster for slow connections XTkTIP.ChangeScrollPos[d.istance.surface, [pos.x, pos.y]]; Given an X window coordinate, returns an Imager coordinate Given an X window coordinate, returns an Imager coordinate Only the first button is looked at... --Now we either paint, or, erase <> No back pointing properties on page: so instances are completely garbage collectable --Called once per instance when new page is created Page numbering is not a global public feature because the file format does not keep track of permanent page numbers --Called once per session when new page is created Ê*n•NewlineDelimiter ™™J™HIcodešœ=™=K™%K™&J™-J™J™ŸJ™—šÏk ˜ Kšœ˜Kšœ˜K˜ Kšœ ˜ Kšœ˜Kšœ ˜ K˜K˜K˜Kšœ˜Kšœ ˜ K˜ K˜ Kšœ ˜ Kšœ ˜ K˜K˜ K˜ K˜ K˜ K˜ K˜K˜K˜ K˜K˜ K˜K˜K˜K˜K˜K˜ K˜ K˜K˜K˜K˜Kšœ ˜ K˜ K˜ —J˜šÏnœœ˜Kšœø˜ÿKšœ˜—Kšœœ˜K˜K˜K˜&Kšœœœ ˜šœ œœ˜Kšœ œ˜K˜Kšœ"œ˜&Kšœ œ œ˜!Kšœ œ˜K˜'K˜Kšœœ˜Kšœœ˜!Kšœœœ˜Kšœœœ˜Kšœœ˜Kšœœ œ˜$Kšœ œ ˜Kšœœ ˜Kšœ œœ˜K˜Kšœœ˜!Kšœœ˜!K˜-Kšœ œœ˜Kšœœœœ˜0K˜Kšœ œ˜Kšœœ˜Kšœ%œ˜)Kšœœœ˜K˜–Kšœ œ˜Kšœœœ˜Kšœ œ˜Kšœ œ˜K˜K˜—K˜šœœœ˜ K˜Kšœ˜K˜—šœ œœ˜Kšœœ˜Kšœœ˜Kšœ˜ Kšœ˜K˜—šœ œœ˜Kšœ˜Kšœ ˜Kšœ˜K˜—šœœœ˜Kšœœ œ˜Kšœœ˜Kšœ œ˜Kšœœ˜K˜K˜—šœ œœ˜Kšœœ˜ Kšœ ˜K˜K˜—Kš œ œœœœ˜ K˜2Kšœ'œ˜+Kšœ(œ˜,Kšœ)œ˜-Kšœœ ˜Kšœœ ˜ Kšœœ ˜"Kšœ8œ˜?K˜CK˜šžœœ&˜3Kš œœœœ œ˜>Kšœœ˜"Kšœ9˜9K˜K˜2šœ5œ˜=K˜GK˜4K˜—K™9K˜%K˜?K˜$K˜&K˜K˜K˜K˜ K˜ K˜—K˜šž œœ˜Jšœ4™4Kšœ$œ˜(šž œœœ œœœœ˜cšœœ ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ˜—šœœ˜Kšœ œ3˜BKšœ˜—Kšœ˜—šž œœœ˜-šœœ˜Kšœ œ6˜FKšœ˜—Kšœœ˜Kšœ˜—šœœ ˜"K˜&K˜%Kšœ ˜ Kšœ˜—K˜;K˜AK˜?K˜?K˜?K˜?K˜?Kšœ˜K˜;K˜?K˜?K˜?K˜?K˜?Kšœ˜K˜CK˜CK˜PK˜IK˜IK˜IK˜IKšœ˜K˜KK˜KK˜KK˜QK˜QK˜QK˜QKšœ ˜ K˜IK˜IK˜IK˜OK˜OK˜OK˜OKšœ˜K˜EK˜9K˜EK˜;K˜=K˜AK˜;Kšœ˜K˜K˜—šž œœ˜š ž œœ œœœ˜Wšœœ˜Kšœ ˜ Kšœ ˜ Kšœ˜—šœœ˜Kšœ œ;˜IKšœ˜—Kšœ˜—K˜1K˜AK˜@K˜@K˜@K˜'K˜/K˜.K˜/K˜-K˜/K˜.K˜/K˜5K˜K˜—šž œœ˜šž œœ œ œ œœœ˜bKšœœ,˜3šœœ˜Kšœœ;˜ZKšœ˜—Kšœ˜—Kšœ7œ(˜dKšœ(œ#˜OK˜K˜—Kšœœœ˜)šœœœ˜ K˜Kšœ˜K˜K˜—š žœœœœœ˜]šœœœ˜K˜K˜K˜—K˜K˜—šžœœ˜4K˜šœœœ˜K˜&Kšœœœ&˜>K˜—K˜—K˜šžœ"˜2Kšœ œ˜šœ˜šœ˜K˜K˜.K˜—šœ˜K˜K˜/K˜——K˜K˜—šžœ"˜2Kšœœ˜0K˜$Kšœœ ˜&Kšœ œ"˜1Kšœ œœ˜2K˜K˜—šžœ"˜4Kšœœ˜0Kšœœœ ˜%Kšœ œ˜%K˜Kšœ˜K˜—šžœœ˜,Kšœœ˜0˜>K˜K˜K˜K˜K˜Kšœ˜—K™K™K˜YK™K™˜+K˜?K˜—˜.Kšœ œ,˜9K˜/K˜—˜,K˜JK˜—˜-K˜LK˜—˜1K˜TK˜—K˜(˜6KšœD˜HK˜—K™K˜KK™K˜¦˜HK˜TK˜—K˜£˜6K˜LK˜—˜)K˜CK˜—˜4Kšœ"˜&K˜—K™K™˜.Kšœf˜jK˜—K˜:K˜*K˜3˜bKšœ>˜@Kšœ˜ K˜—K˜1K˜K˜K˜K˜K˜(Kšœ ˜K˜K˜—šžœ"˜=Kšœ œ˜K˜K˜—K˜šžœœœ ˜-šœ œ ˜K˜K˜BK˜—Kšœœ3˜<˜7Kšœ ˜ K˜K˜>K˜K˜Kšœ˜—K˜”K˜<šœ5˜5šœ œ˜Kšœœœ˜Kšœœœ˜Kšœœœ˜Kšœœœ˜Kšœœœ˜Kšœœœ ˜Kšœœœ˜Kšœœœ ˜Kšœœœ ˜Kšœœœ˜Kšœ˜—K˜,Kšœ˜—K˜3K˜d˜KK˜EK˜—˜5K˜MK˜—˜*K˜:K˜—˜'K˜7K˜—˜5Kšœu˜yK˜—Kšœoœ˜t˜3Kšœ/˜3K˜—˜qKšœE˜IK˜—K˜SKšœ&œœ˜4KšÏbœŸÐbkŸ Ÿ™4KšŸœŸ Ÿ Ÿ™8K˜*Kš œ œœœœ œ˜ñK˜vK˜MKšœPÏc˜aK˜MK˜K˜1Kšœ˜ K˜K˜—šžœ˜,Kšœ œ˜J˜%J˜.J˜J™J˜:K˜+K˜+K˜K˜K˜J™K˜:K˜*K˜+K˜+K˜ašœ˜ KšœeœI˜¶K˜—K˜'K˜K˜&K˜Kšœ¡˜ K˜K˜—š œœœœœ˜+Kšœœœœ$˜>K˜—šžœœœ˜$Jšœ œ˜šœ˜Jšœ&œœ˜CJšœ"œ˜*—K˜—K˜šžœ˜$Kšœ œ ˜K˜K˜K˜—šž œ œ˜)Kšœœœ'˜2Kšœœ˜š œœœœœ˜:˜K˜,K˜*K˜—Kšœ˜—K˜—K˜šžœ˜(Kšœ œ˜Kšœœ˜Kšœ¡˜,K˜K˜—K˜šž œœœ˜Fšœœ˜Kšœ œ˜$Kš¡8˜8šœ&˜(šœ˜K™*Kšœœ˜Kšœœ$˜=K˜dK˜(K˜—šœ˜˜K˜(K˜K˜—K˜——K˜ K˜—Kšœ˜K˜—šž œœœ˜Fšœœœœ˜1Kšœ œ˜$K˜YKš¡H˜HK˜dK˜(K˜ K˜—Kšœ˜K˜—šž œœ˜=šœ˜K˜K˜Kšœ˜—Kšœ˜K˜—šžœœœ˜AKšœ œœ˜:Kšœ œ˜Kšœ œ˜ Kšœ˜K˜—šžœœ˜:Kšœ œœ˜9šœœœ˜šœœ˜K˜)Kšœ0˜0Kšœ:˜:K˜—Kšœ"˜"Kšœœ˜Kšœ˜—šœœ˜K˜8K˜4Kšœ˜—Kšœ œ˜ Kšœ œ˜Kšœ˜K˜—šžœœ˜8šœ˜K˜K˜Kšœ˜—Kšœ œ˜ Kšœ˜K˜K˜—šž œœœ˜FKšœœ˜%K˜K˜Kšœ˜K˜—šž œœœ˜FJ˜šœœ œ˜'K˜K˜K˜FKšœ0˜0K˜'K˜K˜—Kšœ˜K˜—šžœœœ˜0K˜Kšœ œ˜Kšœ˜—K˜šžœœ˜4Kšœœ˜Kšœ$¡%˜IKšœ8˜8K˜K˜—šž œœœœ˜EKšœœ˜ K˜-Kšœ˜K˜K˜—šž œœœ˜K˜K˜LK˜K˜Kšœ˜Kšœ˜K˜—šžœ,˜šœœœ˜2Kšœœœ ˜Kšœ œ˜Kšœ œ˜šœ˜ ˜Kšœ œ$˜3K˜K˜—˜ Kšœœ˜K˜—Kšœ˜—K˜—Kšœ˜—Kšœ˜—K˜K˜—š žœœœœœœ˜3K™:Kšœ˜K˜K˜—š žœœœœœœ˜3K™:Kšœ&˜,K˜—K˜Kšœœœ œ6˜^K˜šžœ˜Kšœ œ ˜šžœœ7œ˜LK˜Kšœœ˜ Kšœœœ˜šœœ˜K˜jK˜ K˜—šœ˜ šœ˜šœ˜Kšœ˜Kšœ˜—K˜—šœ˜šœœœ ˜K˜Kš˜—š˜Kšœœ ˜šœ˜Kšœ˜Kšœ˜—Kšœ˜—K˜——J˜K˜K˜—šœ ˜˜Kšœœ˜'Kšœœ˜šœ œ˜K˜1Jš˜J˜—š œœœœ œœ˜YJ™%Jšœ˜J˜—šœ˜J˜Jšœœ˜˜ šœœœ˜;šœ˜Kšœ œ"˜3K˜K˜—Kšœ2˜6—Jš˜J˜—Jšœœ˜—J˜K˜J™ šœ˜Jšœ˜Jšœ˜—K˜—˜Kšœœ˜)Kš œœœœœ˜NKšœœ ¡(˜=K˜$šœœ˜Jšœœ˜šœœ˜J˜K˜J˜—J˜—K˜—˜Kšœœ˜(Kšœœ˜%K˜—˜Kšœœ5œ˜DKšœœ˜$K˜‹Kšœ œœ˜šœ ˜˜8Kšœ œ"˜3K˜K˜—šœ˜ šœ˜Kšœœ˜Kšœœ˜Kšœ&˜-—K˜——K˜—Kšœ˜—˜K˜——šž œ)œ'˜aK™%K˜Kšœ˜K˜!K˜#K˜K˜$Kšœœ˜Kšœ˜K˜—šžœ"˜8Kšœ œ˜šœ˜Kšœœœœ ˜LKšœœœœ ˜JKšœ˜"—Kšœœ˜*K˜Kšœ˜K˜—šžœœ˜&K˜0šœœœ˜Kšœ œ˜šœ˜K˜K˜K˜Kšœ˜—K˜+K˜—K˜—K˜šžœ"˜4Kšœ œ˜Kš œœœ œœ˜(K˜Kšœ˜K˜—šžœ)˜;Kšœ œ˜K˜ šœœ˜ Kšœœ#˜*šœ˜K˜4K˜"K˜——K˜K˜—šžœœ˜%K˜K˜K˜K˜K˜J˜2K˜ K˜=K˜0Kšœ@œœ œ˜dK˜—K˜š ž œœœœœ˜FK˜*K˜˜OKšœ3˜5Kšœ˜ K˜—J˜K˜'Kšœ'˜)Kšœ ˜K˜K˜—šžœœœœ˜4Kšœœ˜,K˜—K˜šžœ œœœ˜[Kš˜K˜˜OKšœ@˜BKšœ˜ K˜—K˜ K˜K˜'Kšœ4˜6Kšœ ˜Kšœ˜—K˜šžœ"˜8Kšœœ˜0K˜$K˜K˜K˜—šžœ"˜9Kšœ œ˜Kš œœœœœ˜9K˜&K˜4šžœ˜Kšœœœœ˜3K˜—J˜K˜'K˜K˜—šžœ"˜5Kšœ œ˜K˜&J˜"Kšœœœ$˜2šœœœ˜J˜šœ œœ˜Kšœ(˜*Kš˜K˜—š œœœœ˜;šœœ˜Jšœ œœ˜3Jšœ˜J˜—Jš˜—Kšœœ œ˜'J˜Kšœœœ˜&K˜—K˜K˜—šžœœ œœ˜9Kšœœœ˜4K˜K˜—šžœ ˜4Kšœœ œ˜'K˜)š žœœ œœœ˜4K˜sKšœ$˜$K˜—Kšœœ˜K˜,Kšœ˜Kšœ˜K˜—šžœ"˜5Kšœœœ˜Kšœœ œ˜(šœ œ˜šœœ˜ K˜%K˜—Kšœ ˜'—K˜;Kšœ˜K˜—šžœœœ˜/Kšœ œœ˜8Kšœœ˜$K˜&K˜K˜Kšœ˜K˜—šž œœ˜ šœ œœ˜K˜8K˜—šœœœ˜K˜:K˜2K˜—Kšœ˜K˜—šžœœ˜"K˜0šœœœ˜K˜;—Kšœ˜K˜—šž œœœ˜0Kšœœ˜!K˜)K˜K˜SK˜%K˜K˜SK˜ Kšœ ˜K˜K˜—šžœœ˜K˜K˜—šž œœ˜Kšœœ˜%K˜šœœœ˜K˜D—K˜K˜—šžœ"˜7Kšœœ˜0Kšœœœ˜šžœ˜šœœ˜#šœ ˜ Kšœ,˜,šœ ˜šœ˜Jšœ!˜#Jšœ˜J˜—Jšœœ˜&—Kšœ˜—Kšœœ!˜/—K˜—K˜0Kšœ˜K˜K˜—šžœ"˜7Jšœ œ˜Jšœ œ ˜šœœ˜šœ˜Kšœ˜Kšœ˜ ——K˜K˜—šžœœœ˜8Kš œœœœœ˜9J˜Jšœ˜Kšœ˜Kšœ˜ —K˜K˜—šž œ"˜-Jšœ œ˜K˜AK˜K˜—š žœœœœœ˜EKšœœœ4˜>Kšœœœ ˜Kšœ˜ K˜—K˜šž œ"˜,Kšœ œ˜Jšœœ˜Jšœ œ˜šœ˜&K˜K˜Kšœ˜—K˜K˜K˜—šžœ"˜)Jšœ œ˜J˜&Kšœœ˜šœ2œ˜AKšœœ˜Kšœ!˜(—K˜‹K˜K˜—šž œœ˜0K˜—K˜—K˜šžœœ˜3šœœœ˜K˜ Kšœœ˜šœ0œ˜?Kš œœœœœ ˜:Kšœ˜—˜/K˜@K˜—K˜(K˜(K˜—K˜K˜—šžœœ%˜?šœœ˜#˜ K˜0Kšœœœ!˜:K˜—Kšœ˜—K˜K˜—šžœ˜$K™3šœ œ˜šœœ˜#Kšœ#œ ˜6Kšœ˜——K˜K˜—K™sK˜Kš œœœœœ˜"šžœ!˜/Kšœœœ˜K˜K˜—šž œœœœ ˜=KšœœZ˜`Kšœœœœ˜š ž œœœœœœ˜1Kšœœ ˜ K˜—Kšœœœ˜%K˜K˜—šžœ˜+K™2Kšœ œ ˜šœœœ˜Kšœœ˜%Kšœ4œœ ˜DK˜—K˜K˜—šžœ˜'Kšœ œ ˜šœœ˜šœœ˜#˜ šœœœ˜:K˜+Kšœ œ˜K˜K˜—K˜K˜—Kšœ˜——K˜K˜—šžœœ˜0K˜0Kšœœœ"˜;K˜K˜—šž œœ˜-K˜+K˜/Kš œœœœ%œ˜`K˜#Kšœœœ,˜DK˜—K˜šžœœ œ˜8Kšœ ˜K˜K˜—šžœœ˜1šœ œ˜K˜Kšœ œ˜Kšœ œ˜ šœœ˜ Kšœ˜Kšœ˜!—K˜K˜—K˜—K˜šžœ˜)šœ œ˜šœœ˜#š œ œœœœ˜6K˜5K˜—Kšœ˜——K˜K˜—šžœ˜#šœ œœ˜Kšœœœ ˜&Kšœ ˜"K˜—K˜K˜—Kšœ œœ*˜AK˜šžœ˜(˜'K˜K˜%K˜K˜K˜—K˜)K˜LKšœœ œ ˜6K˜K˜§Kšœ9œA˜}˜1K˜9K˜—˜/K˜LK˜—˜&K˜RK˜—˜3K˜MK˜—˜0K˜NK˜—˜.K˜ZK˜—˜,Kšœ;˜?K˜—˜-Kšœ˜K˜—Kšœ.œ˜L˜Kšœ+˜1K˜ K˜—K˜9K˜*˜IKšœ<˜>Kšœ˜ K˜—K˜-K˜Kšœ ˜K˜—K˜šœœ˜Kšœ˜Kšœ ˜ Kšœ˜Kšœ˜Kšœ ˜ K˜K˜Kšœ˜—Kšœ8œ˜=Kšœ?œ˜DKšœAœ˜FKšœ?œ˜DKšœCœ˜HK˜ K˜ K˜K˜?K˜CKšœ[˜[Kšœ˜K˜K˜—…—­òݺ