DIRECTORY Carets USING [StartCaret, StopCaret], Convert USING [Error, IntFromRope], EditSpan USING [CompareNodeOrder], Imager USING [Color, Context, MaskRectangleI, SetColor], ImagerBackdoor USING [MakeStipple], InputFocus USING [GetInputFocus, SetInputFocus], MessageWindow USING [Append, Blink, Clear], NodeProps USING [GetProp], Process USING [Detach, GetCurrent], Rope USING [Concat, IsEmpty, ROPE, Size, Substr], TEditDocument USING [LineTable, maxClip, Selection, SelectionId, SelectionPoint, SelectionRec, TEditDocumentData], TEditInput USING [CloseEvent, interrupt], TEditLocks USING [LockDocAndTdd, LockRef, Unlock, UnlockDocAndTdd], TEditOps USING [GetSelContents, RememberCurrentPosition], TEditProfile USING [selectionCaret], TEditRefresh USING [ScrollToEndOfSel], TEditSelection USING [FindWhere, ForceDown, IsDown, SetSelLooks], TEditSelectionOps USING [], TEditSelectionPrivate USING [CharPositionInCachedLine], TEditTouchup USING [LockAfterRefresh, UnlockAfterRefresh], TextEdit USING [FetchChar, Size], TextFind USING [MalformedPattern], TextNode USING [EndPos, FirstChild, ForwardClipped, LastWithin, Level, Location, LocWithin, Parent, Ref, Root, Span, StepBackward, StepForward], TreeFind USING [CreateFromRope, Finder, Try, TryBackwards], ViewerClasses USING [ModifyProc, Viewer], ViewerLocks USING [CallUnderWriteLock], ViewerOps USING [AddProp, BlinkDisplay, FetchProp, PaintViewer]; TEditSelectionImpl: CEDAR MONITOR IMPORTS Carets, Convert, EditSpan, Imager, ImagerBackdoor, InputFocus, MessageWindow, NodeProps, Process, Rope, TEditInput, TEditLocks, TEditOps, TEditProfile, TEditRefresh, TEditSelection, TEditSelectionPrivate, TEditTouchup, TextEdit, TextFind, TextNode, TreeFind, ViewerLocks, ViewerOps EXPORTS TEditSelection, TEditSelectionOps = BEGIN Node: TYPE = TextNode.Ref; ROPE: TYPE = Rope.ROPE; Selection: TYPE = TEditDocument.Selection; SelectionId: TYPE ~ TEditDocument.SelectionId; FindWhere: TYPE ~ TEditSelection.FindWhere; Viewer: TYPE = ViewerClasses.Viewer; LockRec: TYPE = RECORD [ process: PROCESS _ NIL, -- the process holding the lock whoLast, whoFirst: ROPE, -- the last lockers count, maxCount: [0..255] _ 0 -- number of times inside the lock ]; LockRef: TYPE = REF LockRec; selLock: ARRAY SelectionId OF LockRef _ [NEW[LockRec], NEW[LockRec], NEW[LockRec]]; unlocked: CONDITION; UnlockDocAndPSel: PUBLIC PROC [root: Node] = { TEditLocks.Unlock[root]; UnlockSel[primary] }; LockBothSelections: PUBLIC PROC [who: ROPE] = { LockSel[primary, who]; LockSel[secondary, who] }; UnlockBothSelections: PUBLIC PROC = { UnlockSel[primary]; UnlockSel[secondary] }; LockSel: PUBLIC ENTRY PROC [selection: SelectionId, who: ROPE] = { ENABLE UNWIND => NULL; myProcess: PROCESS; lock: LockRef = selLock[selection]; TRUSTED {myProcess _ LOOPHOLE[Process.GetCurrent[]]}; IF myProcess#lock.process THEN { WHILE lock.count>0 DO WAIT unlocked; ENDLOOP; lock.process _ myProcess; }; IF lock.count=0 THEN lock.whoFirst _ who ELSE lock.whoLast _ who; lock.count _ lock.count+1; lock.maxCount _ MAX[lock.maxCount, lock.count]; }; UnlockSel: PUBLIC ENTRY PROC [selection: SelectionId _ primary] = { ENABLE UNWIND => NULL; lock: LockRef = selLock[selection]; TRUSTED { IF lock.process # Process.GetCurrent[] THEN ERROR }; IF (lock.count _ lock.count-1) > 0 THEN RETURN; lock.whoLast _ lock.whoFirst _ NIL; BROADCAST unlocked }; pSel: PUBLIC Selection _ Create[]; sSel: PUBLIC Selection _ Create[]; fSel: PUBLIC Selection _ Create[]; oldSel: PUBLIC Selection _ Create[]; nilSel: PUBLIC Selection _ Create[]; tSel1, tSel2, tSel3: Selection _ NIL; -- cache of temporary selection records Create: PUBLIC PROC RETURNS [Selection] = { RETURN [NEW[TEditDocument.SelectionRec]] }; Alloc: PUBLIC ENTRY PROC RETURNS [tSel: Selection] = { ENABLE UNWIND => NULL; IF tSel3 # NIL THEN { tSel _ tSel3; tSel3 _ NIL } ELSE IF tSel2 # NIL THEN { tSel _ tSel2; tSel2 _ NIL } ELSE IF tSel1 # NIL THEN { tSel _ tSel1; tSel1 _ NIL } ELSE tSel _ Create[]; }; Free: PUBLIC ENTRY PROC [tSel: Selection] = { ENABLE UNWIND => NULL; IF tSel3 = tSel OR tSel2 = tSel OR tSel1 = tSel THEN ERROR; IF tSel3 = NIL THEN tSel3 _ tSel ELSE IF tSel2 = NIL THEN tSel2 _ tSel ELSE IF tSel1 = NIL THEN tSel1 _ tSel; }; Copy: PUBLIC ENTRY PROC [source, dest: Selection] = { ENABLE UNWIND => NULL; dest^ _ source^; }; MakePointSelection: PUBLIC PROC [selection: Selection, pos: TextNode.Location] = { tSel: Selection _ Alloc[]; Copy[source: selection, dest: tSel]; tSel.start.pos _ tSel.end.pos _ pos; tSel.granularity _ point; tSel.pendingDelete _ FALSE; tSel.insertion _ before; MakeSelection[selection: primary, new: tSel]; Free[tSel]; }; ChangeSelections: PROC [proc: PROC [tSel: Selection], sel: Selection] = { tSel: Selection _ Alloc[]; LockBothSelections["ChangeBothSelections"]; { ENABLE UNWIND => UnlockBothSelections[]; Copy[source: sel, dest: tSel]; proc[tSel] }; UnlockBothSelections[]; Free[tSel] }; PushOrExchangeSelections: PUBLIC PROC = { DoPush: PROC [tSel: Selection] = { MakeSelection[IF sSel.viewer=NIL THEN NIL ELSE sSel, primary]; MakeSelection[IF tSel.viewer=NIL THEN NIL ELSE tSel, secondary] }; ChangeSelections[DoPush, pSel] }; MakePrimary: PUBLIC PROC = { DoMakePrimary: PROC [tSel: Selection] = { Deselect[secondary]; MakeSelection[tSel, primary] }; ChangeSelections[DoMakePrimary, sSel] }; MakeSecondary: PUBLIC PROC = { DoMakeSecondary: PROC [tSel: Selection] = { Deselect[primary]; MakeSelection[tSel, secondary] ; }; ChangeSelections[DoMakeSecondary, pSel] }; CancelPrimary: PUBLIC PROC = { MakeSelection[NIL, primary]; }; CancelSecondary: PUBLIC PROC = { MakeSelection[NIL, secondary]; }; CancelFeedback: PUBLIC PROC = { MakeSelection[NIL, feedback]; }; FakeSecondary: PUBLIC PROC [sel: Selection] = { inner: PROC = { IF sSel.viewer # NIL THEN Deselect[secondary]; Copy[source: sel, dest: sSel]; }; WithLockedSelection[secondary, inner, "FakeSecondary"]; }; Deselect: PUBLIC PROC [selection: SelectionId _ primary] = { inner: PROC = { sel: Selection = SELECT selection FROM primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR; viewer: Viewer = sel.viewer; op: ATOM = SELECT selection FROM primary => $TakeDownPSel, secondary => $TakeDownSSel, feedback => $TakeDownFSel, ENDCASE => ERROR; down: BOOL = TEditSelection.IsDown[selection]; Copy[source: nilSel, dest: sel]; IF viewer#NIL AND NOT down THEN { ViewerOps.PaintViewer[viewer, client, FALSE, op]; IF NOT TEditSelection.IsDown[selection] THEN TEditSelection.ForceDown[selection]; } }; WithLockedSelection[selection, inner, "Deselect"]; }; MakeSelection: PUBLIC PROC [new: Selection _ NIL, selection: SelectionId _ primary, startValid, endValid: BOOL _ FALSE, forkPaint: BOOL _ TRUE] = { inner: PROC = { sel: Selection; op: ATOM; SELECT selection FROM primary => { if: Viewer = InputFocus.GetInputFocus[].owner; sel _ pSel; op _ $ShowPSel; IF new=NIL THEN {IF if#NIL THEN InputFocus.SetInputFocus[NIL]} ELSE {IF if#new.viewer THEN InputFocus.SetInputFocus[new.viewer]}; }; feedback => { sel _ fSel; op _ $ShowFSel; }; secondary => { sel _ sSel; op _ $ShowSSel; }; ENDCASE => ERROR; IF new=NIL OR new.viewer # sel.viewer THEN Deselect[selection]; IF new#NIL THEN { IF selection#feedback AND fSel.viewer=new.viewer THEN Deselect[feedback]; Copy[source: new, dest: sel]; sel.start.metricsValid _ startValid; sel.end.metricsValid _ endValid; IF sel.granularity=point THEN sel.pendingDelete _ FALSE; IF forkPaint THEN { showSel: REF ShowSelRec = SELECT selection FROM primary => showPSel, secondary => showSSel, ENDCASE => showFSel; IF showSel.process = NIL THEN TRUSTED { Process.Detach[showSel.process _ FORK ShowSel[showSel, op ! ABORTED => CONTINUE]] } } ELSE ViewerOps.PaintViewer[sel.viewer, client, FALSE, op]; }; }; WithLockedSelection[selection, inner, "MakeSelection"]; }; ShowSelRec: TYPE = RECORD [ process: PROCESS, selection: SelectionId ]; showPSel: REF ShowSelRec _ NEW[ShowSelRec _ [NIL, primary]]; showSSel: REF ShowSelRec _ NEW[ShowSelRec _ [NIL, secondary]]; showFSel: REF ShowSelRec _ NEW[ShowSelRec _ [NIL, feedback]]; ShowSel: PROC [my: REF ShowSelRec, op: ATOM] = { selection: TEditDocument.SelectionId _ my.selection; sel: Selection _ SELECT selection FROM primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR; inner: PROC = { viewer: Viewer _ sel.viewer; inner: PROC = { WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { IF NOT TEditTouchup.LockAfterRefresh[tdd, "ShowSel"] THEN RETURN; ViewerOps.PaintViewer[viewer, client, FALSE, op ! UNWIND => TEditTouchup.UnlockAfterRefresh[tdd]]; TEditTouchup.UnlockAfterRefresh[tdd]; }; ENDCASE; }; IF viewer#NIL THEN ViewerLocks.CallUnderWriteLock[inner, viewer]; my.process _ NIL; }; WithLockedSelection[selection, inner, "ShowSel"]; }; lightGrey: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0208H, xor: TRUE]; darkGrey: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0A5A5H, xor: TRUE]; veryDarkGrey: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0EBEBH, xor: TRUE]; black: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0FFFFH, xor: TRUE]; SelColor: TYPE = {black, lightGrey, darkGrey, veryDarkGrey} _ black; SelBound: TYPE = {solid, line} _ solid; selectionLineWidth: NAT _ 2; feedbackLineWidth: NAT _ 4; feedbackLineRaise: INTEGER _ 1; selectionDescentLimit: NAT _ 7; MarkSelection: PUBLIC PROC [dc: Imager.Context, viewer: Viewer, selection: Selection, id: SelectionId] = { WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { lines: TEditDocument.LineTable = tdd.lineTable; vHeight: INTEGER = selection.viewer.ch; selBound: SelBound = IF selection.pendingDelete AND id#feedback THEN solid ELSE line; selColor: SelColor = IF id=primary THEN black ELSE IF id=feedback THEN veryDarkGrey ELSE IF selection.pendingDelete THEN lightGrey ELSE darkGrey; EffectSelect: PROC [x, y, w, h: INTEGER] = { lineHeight: NAT _ selectionLineWidth; bottom: INTEGER _ vHeight-y-h; IF id=feedback THEN { lineHeight _ feedbackLineWidth; bottom _ bottom - feedbackLineRaise }; SELECT selBound FROM line => Imager.MaskRectangleI[dc, x, bottom, w, lineHeight]; solid => Imager.MaskRectangleI[dc, x, bottom, w, vHeight-y-bottom]; ENDCASE => ERROR; }; IF selection.end.line<0 OR selection.start.line=LAST[INTEGER] OR (selection.start.line=selection.end.line AND selection.end.clipped) THEN RETURN; -- not visible SELECT selColor FROM black => Imager.SetColor[dc, black]; lightGrey => Imager.SetColor[dc, lightGrey]; darkGrey => Imager.SetColor[dc, darkGrey]; veryDarkGrey=> Imager.SetColor[dc, veryDarkGrey]; ENDCASE => ERROR; SELECT TRUE FROM selection.start.line = selection.end.line OR (selection.start.line < 0 AND selection.end.line = 0) => { x, y: INTEGER; IF selection.start.line < 0 OR selection.start.clipped THEN { line: INTEGER _ MAX[0, selection.start.line]; x _ lines[line].xOffset; y _ lines[line].yOffset-lines[line].ascent; } ELSE { x _ selection.start.x; y _ selection.start.y }; IF selection.end.clipped THEN ERROR; -- previous tests imply NOT end.clipped EffectSelect[x, y, selection.end.x-x+selection.end.w, selection.end.h] }; selection.start.line=lines.lastLine AND selection.end.line>lines.lastLine => { EffectSelect[selection.start.x, selection.start.y, lines[selection.start.line].width + lines[selection.start.line].xOffset - selection.start.x, selection.start.h] }; ENDCASE => { sl: INTEGER = MAX[0, MIN[lines.lastLine, selection.start.line]]; el: INTEGER = MAX[0, MIN[lines.lastLine, selection.end.line]]; EffectSelAll: PROC [n: INTEGER] = { EffectSelect[lines[n].xOffset, lines[n].yOffset-lines[n].ascent, lines[n].width, lines[n].ascent+lines[n].descent] }; IF sl=selection.start.line AND NOT selection.start.clipped THEN { EffectSelect[selection.start.x, selection.start.y, (lines[sl].width + lines[sl].xOffset - selection.start.x), selection.start.h] } ELSE { EffectSelAll[sl]; }; FOR n: INTEGER IN (sl..el) DO EffectSelAll[n]; ENDLOOP; SELECT TRUE FROM selection.end.clipped => {}; -- end.line is not actually part of the selection el=selection.end.line => { EffectSelect[lines[el].xOffset, selection.end.y, selection.end.x - lines[el].xOffset + selection.end.w, selection.end.h]; }; ENDCASE => { EffectSelAll[el];-- end.line is off screen, so select all of el }; }; }; ENDCASE; }; ExtendSelection: PUBLIC PROC [dc: Imager.Context, viewer: Viewer, old, new: Selection, id: SelectionId, updateEnd: BOOL] = { TooHard: PROC = { MarkSelection[dc, viewer, old, id]; -- take it down FixupSelection[new, viewer]; -- get position info about the new selection MarkSelection[dc, viewer, new, id]; -- put it up Copy[source: new, dest: old]; }; IF updateEnd THEN { BumpLoc: PROC [loc: TextNode.Location] RETURNS [TextNode.Location] = { n: Node = loc.node; where: INT _ loc.where+1; IF where >= TextEdit.Size[n] THEN RETURN [[TextNode.StepForward[n],0]]; RETURN [[n,where]]; }; tooHard: BOOL = old.end.line NOT IN [0..LAST[INTEGER]); sameNode: BOOL = (new.end.pos.node = old.end.pos.node); IF tooHard THEN {TooHard; RETURN}; SELECT TRUE FROM new.end.pos=old.end.pos => {}; (sameNode AND new.end.pos.where>old.end.pos.where) OR (NOT sameNode AND EditSpan.CompareNodeOrder[new.end.pos.node, old.end.pos.node]=after) => { new.start _ old.end; new.start.pos _ BumpLoc[new.start.pos]; IF sameNode AND new.start.pos.node # new.end.pos.node THEN new.start.pos _ new.end.pos; FixupSelection[new, viewer]; MarkSelection[dc, viewer, new, id]; old.end _ new.end; } ENDCASE => { save: TextNode.Location = new.end.pos; new.start _ new.end; new.start.pos _ BumpLoc[save]; IF sameNode AND new.start.pos.node # new.end.pos.node THEN new.start.pos _ old.end.pos; new.end _ old.end; FixupSelection[new, viewer, TRUE, FALSE]; -- fix start MarkSelection[dc, viewer, new, id]; old.end _ new.start; old.end.pos _ save; FixupSelection[old, viewer, FALSE, TRUE]; }; } ELSE { DecLoc: PROC [loc: TextNode.Location] RETURNS [TextNode.Location] = { n: Node; IF loc.where > 0 THEN RETURN [[loc.node, loc.where-1]]; n _ TextNode.StepBackward[loc.node]; RETURN [[n,MAX[TextEdit.Size[n],1]-1]] }; tooHard: BOOL = old.start.line NOT IN [0..LAST[INTEGER]); sameNode: BOOL = (new.start.pos.node = old.start.pos.node); IF tooHard THEN {TooHard; RETURN}; SELECT TRUE FROM new.start.pos=old.start.pos => {}; (sameNode AND new.start.pos.where { new.end.pos _ DecLoc[old.start.pos]; FixupSelection[new, viewer]; MarkSelection[dc, viewer, new, id]; old.start _ new.start; } ENDCASE => { save: TextNode.Location = new.start.pos; new.end.pos _ DecLoc[save]; new.start _ old.start; FixupSelection[new, viewer, FALSE, TRUE]; MarkSelection[dc, viewer, new, id]; old.start.pos _ save; FixupSelection[old, viewer, TRUE, FALSE]; }; }; Copy[source: old, dest: new]; }; CannotFindIt: PUBLIC ERROR = CODE; ComputeSpanLines: PUBLIC PROC [viewer: Viewer, span: TextNode.Span] RETURNS [start, end: INTEGER, startClipped, endClipped: BOOL] = { [start, startClipped] _ ComputePosLine[viewer, span.start]; SELECT TRUE FROM span.start=span.end => { end _ start; endClipped _ startClipped }; start=LAST[INTEGER] => end _ LAST[INTEGER]; -- scrolled off bottom ENDCASE => { firstLine: INTEGER = IF start<=0 THEN 0 ELSE IF startClipped THEN start-1 ELSE start; [end, endClipped] _ ComputePosLine[viewer, span.end, firstLine]; }; }; ComputePosLine: PUBLIC PROC [viewer: Viewer, pos: TextNode.Location, firstLine: INTEGER _ 0] RETURNS [line: INTEGER, clipped: BOOL] = { sp: TEditDocument.SelectionPoint _ ComputePosPoint[viewer, pos, firstLine, TRUE]; RETURN [sp.line, sp.clipped]; }; ComputePosPoint: PUBLIC PROC [viewer: Viewer, pos: TextNode.Location, firstLine: INTEGER _ 0, lineOnly: BOOL _ FALSE] RETURNS [sp: TEditDocument.SelectionPoint] = { WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { foundPos: BOOL _ FALSE; IsPosPastLine: PROC [pos: TextNode.Location, line: INTEGER] RETURNS [BOOL] = { next: INT; next _ lines[line].pos.where+lines[line].nChars; IF pos.where >= next AND lines[line].end # eon THEN RETURN [TRUE]; -- it is past this line RETURN [FALSE] }; FindPos: PROC [pos: TextNode.Location] RETURNS [found: BOOL, line: INTEGER] = { found _ FALSE; FOR n: INTEGER IN [firstLine..lines.lastLine] DO SELECT TRUE FROM lines[n].pos.node=pos.node => { IF pos.where < lines[n].pos.where THEN EXIT; -- have gone past it found _ TRUE; line _ n }; found => EXIT; ENDCASE; ENDLOOP }; lines: TEditDocument.LineTable _ tdd.lineTable; sp.pos _ pos; IF lines[0].pos.node = pos.node THEN { IF lines[0].pos.where > pos.where THEN { sp.y _ sp.line _ -LAST[INTEGER]; RETURN; }; }; IF lines[lines.lastLine].pos.node=pos.node AND IsPosPastLine[pos,lines.lastLine] THEN { sp.y _ sp.line _ LAST[INTEGER]; -- after end of text RETURN; }; firstLine _ MIN[lines.lastLine,MAX[firstLine,0]]; [foundPos, sp.line] _ FindPos[pos]; sp.clipped _ FALSE; IF NOT foundPos THEN SELECT EditSpan.CompareNodeOrder[pos.node, lines[0].pos.node] FROM same, before => { sp.line _ sp.y _ -LAST[INTEGER]; RETURN }; -- before beginning disjoint => ERROR CannotFindIt; ENDCASE => { -- pos.node comes after first line node IF tdd.clipLevel = TEditDocument.maxClip AND tdd.commentFilter=includeComments THEN { sp.line _ sp.y _ LAST[INTEGER]; RETURN } -- after end ELSE { SELECT EditSpan.CompareNodeOrder[pos.node, lines[lines.lastLine].pos.node] FROM same, after => { sp.line _ sp.y _ LAST[INTEGER]; RETURN }; disjoint => ERROR CannotFindIt; -- may have been deleted or moved ENDCASE => { -- it got clipped n: Node _ pos.node; delta: INTEGER _ TextNode.Level[n]-tdd.clipLevel; firstLine _ 0; -- need to let FindPos look at all the previous lines FOR i:INTEGER IN [0..delta) DO n _ TextNode.Parent[n]; ENDLOOP; n _ TextNode.ForwardClipped[n,tdd.clipLevel].nx; [foundPos, sp.line] _ FindPos[[n,0]]; IF NOT foundPos THEN ERROR CannotFindIt; sp.clipped _ TRUE; }; }; }; IF lineOnly OR sp.clipped THEN RETURN; -- otherwise, compute metrics sp.y _ lines[sp.line].yOffset-lines[sp.line].ascent; sp.h _ lines[sp.line].ascent+MIN[lines[sp.line].descent, selectionDescentLimit]; [sp.x, sp.w] _ TEditSelectionPrivate.CharPositionInCachedLine[viewer, sp.line, pos]; IF sp.x = LAST[INTEGER] THEN { IF sp.line=lines.lastLine THEN {sp.line _ sp.y _ LAST[INTEGER]} ELSE {sp.x _ lines[sp.line].width; sp.w _ 0}; }; sp.metricsValid _ TRUE; }; ENDCASE; }; FixupSelection: PUBLIC PROC [selection: Selection, viewer: Viewer, start, end: BOOL _ TRUE] = { IF start THEN selection.start _ ComputePosPoint[viewer, selection.start.pos]; IF end THEN SELECT TRUE FROM selection.start.pos=selection.end.pos => { selection.end _ selection.start; }; selection.start.line=LAST[INTEGER] => { selection.end.line _ LAST[INTEGER]; selection.end.metricsValid _ TRUE; }; ENDCASE => { firstLine: INTEGER = ( IF selection.start.line<=0 THEN 0 ELSE IF selection.start.clipped THEN selection.start.line-1 ELSE selection.start.line ); selection.end _ ComputePosPoint[viewer, selection.end.pos, firstLine]; }; }; FixupCaret: PUBLIC PROC [selection: Selection] = { lines: TEditDocument.LineTable = selection.data.lineTable; node: Node _ selection.end.pos.node; size: INT _ TextEdit.Size[node]; PutCaretOnNextLine: PROC = { SELECT selection.end.line FROM IN [0..lines.lastLine) => { nextLine: INTEGER _ selection.end.line+1; selection.caretX _ lines[nextLine].xOffset; selection.caretY _ selection.viewer.ch - lines[nextLine].yOffset - lines[nextLine].descent; }; = lines.lastLine => { selection.caretX _ lines[selection.end.line].xOffset; selection.caretY _ selection.viewer.ch - selection.end.y - selection.end.h - selection.end.h; }; ENDCASE => ERROR; }; IF selection.insertion=before THEN { IF selection.start.clipped THEN selection.caretY _ LAST[INTEGER] -- caret not visible ELSE { selection.caretX _ selection.start.x; selection.caretY _ IF selection.start.line NOT IN [0..lines.lastLine] THEN LAST[INTEGER] ELSE selection.viewer.ch - selection.start.y - selection.start.h; }; } ELSE { SELECT TRUE FROM selection.end.clipped => { selection.caretY _ LAST[INTEGER]; }; selection.end.pos.where IN [0..size) AND selection.end.line IN [0..lines.lastLine] AND TextEdit.FetchChar[node,selection.end.pos.where]=[0,15B] => { PutCaretOnNextLine[]; }; ENDCASE => { selection.caretX _ selection.end.x+selection.end.w; selection.caretY _ ( IF selection.end.line NOT IN [0..lines.lastLine] THEN LAST[INTEGER] ELSE selection.viewer.ch - selection.end.y - selection.end.h ); }; }; }; KillSelection: PUBLIC PROC = { IF InputFocus.GetInputFocus[]#NIL THEN InputFocus.SetInputFocus[]; }; ModifyPSel: PROC [proc: PROC [root: Node, tSel: Selection]] = { inner: PROC = { root: Node _ SelectionRoot[pSel]; IF root#NIL THEN { tSel: Selection _ Alloc[]; Copy[source: pSel, dest: tSel]; proc[root, tSel ! UNWIND => Free[tSel]]; MakeSelection[new: tSel]; Free[tSel]; }; }; WithLockedSelection[primary, inner, "CallWithSelLock"]; }; SelectEverything: PUBLIC PROC = { DoSelEverything: PROC [root: Node, tSel: Selection] = { root _ TextNode.Root[tSel.start.pos.node]; tSel.start.pos _ [TextNode.FirstChild[root], 0]; tSel.end.pos.node _ TextNode.LastWithin[root]; tSel.end.pos.where _ TextEdit.Size[tSel.end.pos.node]-1; tSel.granularity _ branch; }; ModifyPSel[DoSelEverything] }; PendingDeleteSelection: PUBLIC PROC = { DoPDel: PROC [root: Node, tSel: Selection] = { tSel.pendingDelete _ TRUE }; ModifyPSel[DoPDel] }; NotPendingDeleteSelection: PUBLIC PROC = { DoNotPDel: PROC [root: Node, tSel: Selection] = { tSel.pendingDelete _ FALSE }; ModifyPSel[DoNotPDel] }; CaretBeforeSelection: PUBLIC PROC = { DoCaretBeforeSelection: PROC [root: Node, tSel: Selection] = {tSel.insertion _ before }; ModifyPSel[DoCaretBeforeSelection] }; CaretAfterSelection: PUBLIC PROC = { DoCaretAfterSelection: PROC [root: Node, tSel: Selection] = { IF tSel.granularity # point THEN tSel.insertion _ after }; ModifyPSel[DoCaretAfterSelection] }; WithLockedSelection: PROC [which: SelectionId, inner: PROC, why: ROPE] = { LockSel[which, why]; inner[ ! UNWIND => UnlockSel[which]]; UnlockSel[which]; }; SelectionRoot: PUBLIC PROC [s: Selection _ pSel] RETURNS [root: Node] = { IF s.viewer=NIL THEN RETURN [TextNode.Root[s.start.pos.node]]; WITH s.viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => RETURN [tdd.text]; ENDCASE => RETURN [NIL]; }; InputModify: PUBLIC ViewerClasses.ModifyProc = { WITH self.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { SELECT change FROM set, pop => Carets.StartCaret[self, pSel.caretX, pSel.caretY, primary]; kill => { inner: PROC = {Copy[source: pSel, dest: prop]; Deselect[primary]}; prop: Selection _ NARROW[ViewerOps.FetchProp[self, $SelectionHistory]]; Carets.StopCaret[primary]; IF prop=NIL THEN ViewerOps.AddProp[self, $SelectionHistory, prop _ Create[]]; WithLockedSelection[primary, inner, "InputModify"]; }; push => Carets.StopCaret[primary]; ENDCASE => ERROR; }; ENDCASE; }; CallWithSelAndDocAndTddLocks: PUBLIC PROC [viewer: Viewer, id: SelectionId _ primary, proc: PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection]] = { IF viewer # NIL THEN WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { tSel: Selection _ NIL; lockRef: TEditLocks.LockRef _ NIL; Cleanup: PROC = { UnlockSel[id]; IF lockRef # NIL THEN TEditLocks.UnlockDocAndTdd[tdd]; IF tSel # NIL THEN Free[tSel] }; IF tdd # NIL THEN { ENABLE UNWIND => Cleanup[]; LockSel[id, "CallWithSelAndDocAndTddLocks"]; lockRef _ TEditLocks.LockDocAndTdd[tdd, "CallWithSelAndDocAndTddLocks", read]; IF lockRef#NIL THEN { sel: Selection = SELECT id FROM primary => pSel, secondary => sSel, feedback => fSel, ENDCASE => ERROR; tSel _ Alloc[]; Copy[source: sel, dest: tSel]; proc[tdd, tSel] }; Cleanup[]; }; }; ENDCASE; }; ShowPosition: PUBLIC PROC[viewer: Viewer, skipCommentNodes: BOOL _ TRUE] = { count: INT _ -1; sel: ROPE _ TEditOps.GetSelContents[]; count _ Convert.IntFromRope[sel ! Convert.Error => CONTINUE]; IF count>=0 THEN ShowGivenPosition[viewer, count, skipCommentNodes] ELSE { MessageWindow.Append["Select character count for position.", TRUE]; MessageWindow.Blink[] }; }; Position: PUBLIC PROC[viewer: Viewer] ~ { ShowPosition[viewer: viewer, skipCommentNodes: TRUE]; }; ShowGivenPosition: PUBLIC PROC [viewer: Viewer, pos: INT, skipCommentNodes: BOOL _ TRUE] = { DoPosition: PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection] = { tiogaFile: BOOL _ (NodeProps.GetProp[tdd.text, $FromTiogaFile] = $Yes); IF NOT tiogaFile THEN pos _ pos+1; -- hack to compensate for leading CR tSel.start.pos _ tSel.end.pos _ TextNode.LocWithin[tdd.text, pos, 1, skipCommentNodes]; tSel.end.pos.where _ MIN[tSel.end.pos.where+3, TextNode.EndPos[tSel.end.pos.node]]; tSel.start.pos.where _ MIN[tSel.end.pos.where, tSel.start.pos.where]; IF tSel.start.pos.where=tSel.end.pos.where AND tSel.start.pos.where > 0 THEN tSel.start.pos.where _ tSel.start.pos.where-1; tSel.granularity _ char; tSel.viewer _ viewer; tSel.data _ tdd; tSel.pendingDelete _ FALSE; tSel.insertion _ IF TEditProfile.selectionCaret=before THEN before ELSE after; TEditOps.RememberCurrentPosition[viewer]; TEditSelection.SetSelLooks[tSel]; MakeSelection[new: tSel, selection: feedback]; TEditRefresh.ScrollToEndOfSel[viewer, FALSE, feedback]; }; CallWithSelAndDocAndTddLocks[viewer, feedback, DoPosition]; }; ShowGivenPositionRange: PUBLIC PROC [viewer: Viewer, selectionId: SelectionId, posI, posF: INT, skipCommentNodes, pendingDelete: BOOL] = { DoPosition: PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection] = { tiogaFile: BOOL = (NodeProps.GetProp[tdd.text, $FromTiogaFile] = $Yes); IF posF < posI THEN {p: INT _ posI; posI _ posF; posF _ p}; IF NOT tiogaFile THEN {posI _ posI + 1; posF _ posF+1}; -- hack to compensate for leading CR tSel.start.pos _ TextNode.LocWithin[tdd.text, posI, 1, skipCommentNodes]; tSel.end.pos _ TextNode.LocWithin[tdd.text, posF, 1, skipCommentNodes]; tSel.granularity _ char; tSel.viewer _ viewer; tSel.data _ tdd; tSel.pendingDelete _ pendingDelete; tSel.insertion _ IF TEditProfile.selectionCaret=before THEN before ELSE after; TEditOps.RememberCurrentPosition[viewer]; TEditSelection.SetSelLooks[tSel]; MakeSelection[new: tSel, selection: selectionId]; TEditRefresh.ScrollToEndOfSel[viewer, FALSE, selectionId]; }; CallWithSelAndDocAndTddLocks[viewer, selectionId, DoPosition]; }; targetOfLastSearch: ROPE _ NIL; Find: PUBLIC PROC [viewer: Viewer, findWhere: FindWhere _ anywhere, def, word: BOOL _ FALSE, id: SelectionId _ primary, case: BOOL _ TRUE -- case => case of characters is significant -- ] ~ { rope: ROPE _ TEditOps.GetSelContents[]; IF Rope.IsEmpty[rope] THEN rope _ targetOfLastSearch ELSE targetOfLastSearch _ rope; IF Rope.IsEmpty[rope] THEN { MessageWindow.Append["Select target for search.", TRUE]; MessageWindow.Blink[]; } ELSE FindRope[viewer, rope, findWhere, def, word, id, case]; }; FindRope: PUBLIC PROC [viewer: Viewer, rope: ROPE, findWhere: FindWhere _ anywhere, def, word: BOOL _ FALSE, id: SelectionId _ primary, case: BOOL _ TRUE -- case => case of characters is significant -- ] = { IF NOT DoFind[viewer, rope, findWhere, def, word, id, case] THEN { target: ROPE _ rope; IF Rope.Size[target]>50 THEN target _ Rope.Concat[Rope.Substr[target, 0, 47], "..."]; MessageWindow.Append[Rope.Concat[target, " not found."], TRUE]; ViewerOps.BlinkDisplay[]; } ELSE MessageWindow.Clear[]; }; DoFind: PUBLIC PROC [viewer: Viewer, rope: ROPE, findWhere: FindWhere _ anywhere, def, word: BOOL _ FALSE, id: SelectionId _ primary, case: BOOL _ TRUE -- case => case of characters is significant -- ] RETURNS [found: BOOL] = { finder: TreeFind.Finder _ NIL; where: Node; first: Node; start, at, atEnd: INT; DoFindIt: PROC [tdd: TEditDocument.TEditDocumentData, tSel: Selection] = { SelectionVisibleInViewer: PROC RETURNS [BOOL] = { RETURN [tSel.viewer=viewer AND tSel.end.line IN [0..tdd.lineTable.lastLine]] }; visible: BOOL ~ SelectionVisibleInViewer[]; interrupt: REF BOOL ~ TEditInput.interrupt; Forward: PROC = { IF visible THEN { first _ tSel.end.pos.node; start _ tSel.end.pos.where+1 } ELSE { first _ tdd.lineTable.lines[0].pos.node; start _ tdd.lineTable.lines[0].pos.where }; [found,where,at,atEnd,,] _ TreeFind.Try[finder: finder, first: first, start: start, interrupt: interrupt]; }; Backward: PROC = { IF visible THEN { first _ tSel.start.pos.node; start _ tSel.start.pos.where } ELSE { first _ tdd.lineTable.lines[0].pos.node; start _ tdd.lineTable.lines[0].pos.where }; [found,where,at,atEnd,,] _ TreeFind.TryBackwards[finder: finder, first: first, len: start, interrupt: interrupt] }; FromStart: PROC = { first _ TextNode.FirstChild[tdd.text]; start _ 0; [found,where,at,atEnd,,] _ TreeFind.Try[finder: finder, first: first, start: start, interrupt: interrupt] }; interrupt^ _ FALSE; SELECT findWhere FROM forwards => Forward[]; backwards => Backward[]; anywhere => { Forward[]; IF NOT found THEN FromStart[] }; ENDCASE => ERROR; IF NOT found THEN RETURN; IF def THEN atEnd _ atEnd-1; -- skip the trailing : tSel.start.pos _ [where,at]; tSel.end.pos _ [where,MAX[0,atEnd-1]]; tSel.granularity _ IF word THEN word ELSE char; tSel.viewer _ viewer; tSel.data _ tdd; tSel.insertion _ IF TEditProfile.selectionCaret=before THEN before ELSE after; tSel.insertion _ after; tSel.pendingDelete _ FALSE; TEditOps.RememberCurrentPosition[viewer]; TEditSelection.SetSelLooks[tSel]; MakeSelection[new: tSel, selection: id]; TEditInput.CloseEvent[]; TEditRefresh.ScrollToEndOfSel[viewer, FALSE, id]; }; found _ FALSE; IF Rope.Size[rope] = 0 THEN RETURN; IF def THEN rope _ Rope.Concat[rope,":"]; finder _ TreeFind.CreateFromRope[pattern: rope, literal: TRUE, ignoreCase: NOT case, word: word ! TextFind.MalformedPattern => IF ec=toobig THEN GOTO TooBig ]; IF finder#NIL THEN CallWithSelAndDocAndTddLocks[viewer, id, DoFindIt]; EXITS TooBig => { MessageWindow.Append["Pattern too long", TRUE]; MessageWindow.Blink[]; }; }; END. †TEditSelectionImpl.mesa Copyright Σ 1985, 1987, 1988 by Xerox Corporation. All rights reserved. Russ Atkinson (RRA) June 10, 1985 11:52:15 pm PDT Michael Plass, October 20, 1987 5:06:49 pm PDT Doug Wyatt, February 17, 1988 6:12:42 pm PST Selection Locks lock the selection so that no other process can change it give up lock on the selection Selection Allocation Selection Display and Control make a point selection at pos out the the current primary selection make secondary selection be the primary make secondary selection be the primary Take down the selection without changing the input focus. take the selection down from the screen If the selection was not in a visible viewer, PaintViewer didn't call our paint proc, so the state didn't change. Force change directly. Changes input focus as well as the selection. When called to make a non-feedback selection is the viewer containing the feedback selection, automatically cancel the feedback selection. N.B. Also in TEditSelection one liner select from beginning of line select end portion of sl select all of sl select all of the intermediate lines when done, old is valid (it's = pSel, e.g.) and new^ = old^ update right end new end after old end This weird case can happen if old ended with final CR of node and new is in the null line displayed after the CR. new end before old end old ended in null line after final CR, new ends with the final CR update start new start comes before old start new start comes after old start this must work correctly even if the node was filtered out for some reason if pos was filtered, return line where it would have been and set clipped=TRUE before beginning of text nothing special going on, so would have found it if not after end must think about this harder it really is after the end may have been restored by Undo past this line recompute the xywh fields in the selection fixes selection screen info assuming start.pos and end.pos are correct no need to re-compute scrolled off bottom recompute the xy fields in the caret caret not visible selection actually get taken down in our InputModify Proc expand to include everything Misc functions SelectionOps Exported to TEditSelectionOps (new for 6.0) sets bit to cause scroll after refresh Should join or replace ShowGivenPosition, which is currently in TEditSelectionOps sets bit to cause scroll after refresh since literal=TRUE, no other PatternErrorCode should occur Κ!˜codešœ™KšœH™HKšœ1™1Kšœ.™.Kšœ,™,K™—šΟk ˜ Kšœœ˜%Kšœœ˜#Kšœ œ˜"Kšœœ,˜8Kšœœ˜#Kšœ œ ˜0Kšœœ˜+Kšœ œ ˜Kšœœ˜#Kšœœœ˜1Kšœœ_˜rKšœ œ˜)Kšœ œ3˜CKšœ œ+˜9Kšœ œ˜$Kšœ œ˜&Kšœœ-˜AKšœœ˜Kšœœ˜7Kšœ œ(˜:Kšœ œ˜!Kšœ œ˜"Kšœ œ‚˜Kšœ œ-˜;Kšœœ˜)Kšœ œ˜'Kšœ œ1˜@—K˜KšΠlnœœ˜!Kšœš˜‘Kšœ"˜)šœ˜K˜Kšœœ˜Kšœœœ˜Kšœ œ˜*Kšœ œ˜.Kšœ œ˜+Kšœœ˜$—headšœ™šœ œœ˜šœ œœΟc˜7KšœœŸ˜,KšœŸ"˜@K˜——šœ œœ ˜K˜—Kš œ œ œ œ œ œ ˜Sšœ  œ˜K˜—šΟnœœœ˜.K˜,K˜—K˜š œœœœ˜/K˜/K˜—K˜Kš œœœ0˜QK˜š  œœœœœ˜BKšœ9™9Kšœœœ˜Kšœ œ˜Kšœ#˜#Kšœœ˜5šœœ˜ Kšœœœ œ˜-K˜K˜—Kšœœœ˜AK˜Kšœœ˜/K˜K˜—š  œœœœ'˜CKšœ™Kšœœœ˜Kšœ#˜#Kšœœ%œœ˜>Kšœ!œœ˜/Kšœœ˜#Kš œ ˜K˜——šœ™Kšœœ˜"Kšœœ˜"Kšœœ˜"Kšœœ˜$Kšœœ˜$Kšœ!œŸ'˜Mš œœœœ˜+Kšœœ˜)K˜K˜—š  œœœœœ˜6Kšœœœ˜Kšœ œœœ˜1Kš œœ œœœ˜6Kš œœ œœœ˜6Kšœ˜K˜K˜—š œœœœ˜-Kšœœœ˜Kš œœœœœ˜;šœ œœ ˜ Kšœœ œœ ˜%Kšœœ œœ˜&—K˜K˜—š œœœœ˜5Kšœœœ˜K˜K˜——šœ™š œœœ3˜RKšœC™CK˜K˜$K˜$K˜Kšœœ˜K˜K˜-K˜ K˜—K˜š œœœ'˜IK˜K˜+šœœœ˜+K˜*K˜—K˜#K˜—K˜š œœœ˜)š œœ˜"Kš œœ œœœœ˜>Kš œœ œœœœ˜@K˜—K˜K˜—K˜š  œœœ˜Kšœ'™'š  œœ˜)K˜2K˜—K˜&K˜—K˜š  œœœ˜Kšœ'™'š œœ˜+K˜K˜ K˜—K˜(K˜—K˜š  œœœ˜Kšœœ ˜K˜—K˜š œœœ˜ Kšœœ ˜K˜—K˜š œœœ˜Kšœœ ˜K˜—K˜š  œœœ˜/šœœ˜Kšœœœ˜.K˜K˜—K˜7K˜—K˜š œœœ'˜Kšœ œœœ ˜=š œœœœ˜0K˜4šœœ ˜&Kšœ6œœ˜G—šœœ˜K˜šœœ˜šœ œ˜˜)Kšœœ/œœ˜Ašœ&œ˜/Kšœœ*˜2—K˜%K˜—Kšœ˜—K˜—Kšœœœ/˜AKšœ œ˜K˜—K˜1K˜K˜—K˜KšœJœ˜PKšœJœ˜PKšœNœ˜TKšœGœ˜MKšœ œ6˜DKšœ œ˜'Kšœœ˜Kšœœ˜Kšœœ˜K˜Kšœœ˜KšΟbœ™K˜š  œœœP˜jšœ œ˜˜)K˜/Kšœ œ˜'Kš œœœ œœ˜UKšœœ œœœ œœœœ œ ˜’š  œœœ˜,Kšœ œ˜%Kšœœ˜Kšœ œI˜\šœ ˜K˜š  œœœ˜#˜PK˜"—K˜—šœœœ˜:šœ˜Kšœ™K˜€K˜—šœ˜Kšœ™K˜K˜——šœœœ ˜Kšœ$™$K˜Kšœ˜—šœœ˜KšœŸ1˜N˜K˜yK˜—šœ˜ KšœŸ.˜?K˜——K˜——K˜—Kšœ˜—K˜—K˜š œœœWœ˜|Kšœ;™;š œœ˜Kšœ$Ÿ˜3KšœŸ,˜IKšœ$Ÿ ˜0K˜K˜—šœ ˜ šœ˜Kšœ™š œœœ˜FK˜Kšœœ˜Kšœœœ˜GKšœ ˜K˜—Kš œ œœœœœ˜7Kšœ œ)˜7Kšœ œ œ˜"šœœ˜K˜š œ œ&œœ œJ˜‘Kšœ™K˜K˜'Kšœ œ'˜:K˜Kšœq™qK˜K˜#K˜K˜—šœ˜ Kšœ™K˜&K˜K˜Kšœ œ'˜:K˜KšœA™AK˜KšœœœŸ ˜6K˜#K˜K˜Kšœœœ˜)K˜——K˜—šœ˜Kšœ ™ š œœœ˜EK˜Kšœœœ˜7K˜$Kšœœ˜'K˜—Kš œ œœœœœ˜9Kšœ œ-˜;Kšœ œ œ˜"šœœ˜K˜"š œ œ*œœ œO˜šKšœ ™ K˜$K˜K˜#K˜K˜—šœ˜ Kšœ™K˜(K˜K˜Kšœœœ˜)K˜#K˜Kšœœœ˜)K˜——K˜——K˜K˜—K˜Kš  œœœœ˜"š  œœœ'œœœ˜…K˜;šœœ˜K˜BKš œœœ œœŸ˜Bšœ˜ Kšœ œœ œœœœ œ˜UK˜@K˜——K˜—K˜š œœœ5œœœ œ˜‡KšœKœ˜QKšœ˜K˜—K˜š œœœ5œœœœ'˜€KšœJ™JKšœN™Nšœ œ˜˜)Kšœ œœ˜š   œœ œœœ˜NKšœœ˜ K˜0Kš œœœœœŸ˜ZKšœœ˜K˜—š  œœœ œœ˜OKšœœ˜šœœœ˜0šœœ˜˜Kšœ œœŸ˜AKšœœ˜ ˜ K˜——Kšœ œ˜Kšœ˜—Kš˜—K˜—K˜/K˜ šœœ˜&šœ œ˜(Kšœ™Kšœœœ˜ Kšœ˜K˜—K˜—šœ)œ#œ˜WKšœœœŸ˜4Kšœ˜K˜—Kšœ œœ˜1Kšœ1œ˜7Kšœœ ˜šœ8˜BKšœ$œœœŸ˜PKšœ œ˜šœ˜ KšŸ'˜'šœ'œ"˜Nšœ˜KšœA™AKšœœœ˜Kšœ˜KšœŸ ˜—šœ˜Kšœ™šœE˜O˜Kšœ™Kšœœœ˜Kšœ˜K˜—Kšœ œŸ!˜AšœŸ˜K˜Kšœœ$˜2KšœŸ5˜DKš œœœ œœ˜?K˜0K˜%Kšœœ œœ˜(Kšœ™Kšœ œ˜K˜——K˜—Kšœ˜———Kš œ œ œœŸ˜DK˜4Kšœœ0˜PK˜Tšœœœœ˜Kšœ™šœ˜Kšœœœ˜%Kšœ)˜-—K˜—Kšœœ˜K˜—Kšœ˜—K˜—K˜š  œœœ4œœ˜_Kšœ*™*KšœF™FKšœœ@˜MKšœ˜ šœœ˜šœ*˜*Kšœ™K˜ Kšœ˜—šœœœ˜'Kšœ™Kšœœœ˜#Kšœœ˜"K˜—šœ˜ šœ œ˜Kš œœœœœœ˜wK˜—K˜FK˜——K˜—K˜š  œœœ˜2Kšœ$™$K˜:K˜$Kšœœ˜ š œœ˜šœ˜šœ˜Kšœ œ˜)K˜+K˜[K˜—˜K˜5K˜]K˜—Kšœœ˜—K˜—šœ˜šœ˜Kšœ˜šœœœŸ˜:šœ˜K˜%—Kšœœœœœœœœ=˜šK˜—K˜—šœ˜šœœ˜šœ˜Kšœ™Kšœœœ˜!Kšœ˜—š œœ œœœ>˜”Kšœ˜Kšœ˜—šœ˜ K˜3˜šœœœ˜0Kšœœœ˜Kšœ8˜<—K˜—K˜——K˜——K˜—K˜š  œœœ˜Kšœ9™9Kšœœœ˜BK˜—K˜š  œœœ#˜?šœœ˜K˜!šœœœ˜K˜K˜Kšœœ˜(K˜K˜ K˜—K˜—K˜7K˜—K˜š œœœ˜!Kšœ™š œœ"˜7K˜*K˜0K˜.K˜8K˜K˜—K˜K˜—K˜š œœœ˜'Kš œœ8œ˜KK˜K˜—K˜š œœœ˜*Kš  œœ8œ˜PK˜K˜—K˜š œœœ˜%Kš œœ<˜XK˜#K˜—K˜š œœœ˜$Kš œœ#œœ˜xK˜"K˜—K˜—šœ™K˜š œœœœ˜JK˜Kšœ œ˜%K˜K˜—K˜š  œœœœ˜IKšœ œœœ#˜>šœœ˜Kšœ(œ ˜:Kšœœœ˜—K˜—K˜š  œœ˜0šœ œ˜˜)šœ˜K˜G˜ Kšœœ7˜BKšœœ/˜GK˜Kšœœœ=˜MK˜3K˜—K˜"Kšœœ˜—K˜—Kšœ˜—K˜—K˜—šœ ™ š œœœ4œ=˜žš œ œœœ œ˜1šœ)˜)Kšœœ˜Kšœœ˜"š œœ˜K˜Kšœ œœ!˜6Kšœœœ ˜K˜—šœœœ˜Kšœœ˜K˜,K˜Nšœ œœ˜šœœ˜Kšœ6œœ˜G—K˜K˜K˜K˜—K˜ K˜—K˜—Kšœ˜—K˜—K˜š   œœœ#œœ˜LKšœœ˜Kšœœ˜&Kšœ3œ˜=šœ ˜ Kšœ3˜7šœ˜Kšœ=œ˜CK˜K˜——K˜—K˜š œœœ˜)Kšœ/œ˜5K˜K˜—š  œœœœœœ˜\Kšœ+™+š  œœ<˜LKšœ œ8˜GKšœœ œŸ$˜GKšœW˜WKšœœ;˜SKšœœ+˜EKšœ)œ˜LK˜.K˜K˜K˜Kšœœ˜Kšœœ$œœ˜NK˜)Kšœ!˜!K˜.Kšœ&œ ˜7Kšœ&™&K˜—K˜;K˜K˜—š  œœœ8œ#œ˜ŠKšœQ™Qš  œœ<˜LKšœ œ8˜GKšœ œœ ˜;Kšœœ œ#Ÿ$˜\KšœI˜IKšœG˜GK˜K˜K˜Kšœ#˜#Kšœœ$œœ˜NK˜)Kšœ!˜!Kšœ1˜1Kšœ&œ˜:Kšœ&™&K˜—Kšœ>˜>K˜K˜—Kšœœœ˜K˜š œœœ@œœ%œœŸ/œ˜ΓKšœœ˜'Kšœœœ˜Tšœœ˜Kšœ2œ˜8K˜K˜—Kšœ8˜˜MKšœW˜[Kšœq˜qK˜—š  œœ˜K˜1Kšœj˜jK˜—Kšœ œ˜šœ ˜K˜K˜Kšœœœœ˜9Kšœœ˜—Kšœœœœ˜KšœœŸ˜3K˜Kšœœ ˜&Kšœœœœ˜/K˜K˜Kšœœ$œœ˜NK˜Kšœœ˜K˜)Kšœ!˜!K˜(K˜Kšœ&œ˜1K˜—Kšœœ˜Kšœœœ˜#Kšœœ˜)šœ:œœ˜bšœœ œœ˜:Kšœœ(™:—Kšœ˜—Kšœœœ4˜Fš˜šœ ˜ Kšœ)œ˜/K˜K˜——K˜—K˜—Kšœ˜—…—tŸZ