DIRECTORY Atom USING [DottedPairNode, GetPName, PropList], Basics, CardTab, EditNotify USING [Change, Notify], NodeAddrs USING [MapTextAddrs, MoveTextAddr, PinTextAddr, PutTextAddr, Replace, UnpinAll], NodeProps USING [GetProp, PutProp], Rope USING [Cat, Fetch, FromChar, FromRefText, InlineSize, MaxLen, Replace, ROPE, Size, Substr], RopeEdit USING [AlphaNumericChar, Concat, LowerCase, MaxLen, MaxNat, ReplaceByTEXT, UpperCase], RopeReader USING [FreeRopeReader, Get, GetRope, GetRopeReader, Ref, SetPosition], Rosary USING [CatSegments, Fetch, FromItem, FromProcProc, MapRuns, ROSARY, RosaryRep, RunActionType, Size], TextEdit, TextLooks, TextNode USING [NewTextNode, Node, Root], UndoEvent USING [Note, Event, SubEvent]; TextEditImpl: CEDAR MONITOR IMPORTS Atom, Basics, CardTab, EditNotify, NodeAddrs, NodeProps, Rope, RopeEdit, RopeReader, Rosary, TextLooks, TextNode, UndoEvent EXPORTS TextEdit = BEGIN OPEN TextEdit; Node: TYPE = TextNode.Node; ROPE: TYPE = Rope.ROPE; ROSARY: TYPE = Rosary.ROSARY; Looks: TYPE = TextLooks.Looks; noLooks: Looks = TextLooks.noLooks; allLooks: Looks = TextLooks.allLooks; MaxLen, MaxOffset: INT = LAST[INT]; Event: TYPE = UndoEvent.Event; Change: TYPE = EditNotify.Change; Runs: TYPE = TextLooks.Runs; CharSet: TYPE ~ [0..256); scratch1, scratch2, scratch3: REF Change.ChangingText; Alloc: ENTRY PROC RETURNS [scratch: REF Change.ChangingText] = INLINE --gfi saver-- { ENABLE UNWIND => NULL; SELECT TRUE FROM (scratch3 # NIL) => { scratch _ scratch3; scratch3 _ NIL }; (scratch2 # NIL) => { scratch _ scratch2; scratch2 _ NIL }; (scratch1 # NIL) => { scratch _ scratch1; scratch1 _ NIL }; ENDCASE => { scratch _ NEW[Change.ChangingText] }; }; Free: ENTRY PROC [scratch: REF Change.ChangingText] = INLINE --gfi saver-- { ENABLE UNWIND => NULL; IF scratch3 = scratch OR scratch2 = scratch OR scratch1 = scratch THEN ERROR; SELECT TRUE FROM (scratch3 = NIL) => scratch3 _ scratch; (scratch2 = NIL) => scratch2 _ scratch; (scratch1 = NIL) => scratch1 _ scratch; ENDCASE => NULL; }; QuitIfNotNIL: PROC [item: REF, repeat: INT] RETURNS [quit: BOOL] ~ { quit _ item#NIL }; CheckAllNIL: PROC [rosary: ROSARY] RETURNS [ROSARY] = INLINE --gfi saver-- { IF Rosary.MapRuns[[rosary], QuitIfNotNIL].quit THEN RETURN [rosary] ELSE RETURN [NIL]; }; maxSearchDepth: NAT _ 20; AlreadySaved: PROC [text: Node, event: Event] RETURNS [BOOL] = INLINE --gfi saver-- { k: NAT _ maxSearchDepth; IF event = NIL THEN RETURN [TRUE]; FOR l: UndoEvent.SubEvent _ event.subevents, l.next UNTIL l=NIL DO IF l.undoRef # NIL THEN WITH l.undoRef SELECT FROM x: REF Change.ChangingText => IF x.text = text THEN RETURN[TRUE]; ENDCASE; IF (k _ k-1) = 0 THEN EXIT; ENDLOOP; RETURN [FALSE]; }; DoWithNotify: PROC [action: PROC, event: Event, root: Node, text: Node, start: INT, newlen: INT, oldlen: INT] = INLINE --gfi saver-- { alreadysaved: BOOL ~ AlreadySaved[text, event]; notify: REF Change.ChangingText = IF alreadysaved THEN Alloc[] ELSE NEW[Change.ChangingText]; notify^ _ [ChangingText[ root: root, text: text, start: start, newlen: newlen, oldlen: oldlen, oldRope: text.rope, oldRuns: text.charLooks, oldCharSets: text.charSets, oldCharProps: text.charProps ]]; EditNotify.Notify[notify, before]; action[]; EditNotify.Notify[notify, after]; IF alreadysaved THEN Free[notify] ELSE UndoEvent.Note[event, UndoChangeText, notify]; }; nilRosary: ROSARY ~ Rosary.FromItem[NIL, INT.LAST]; -- "infinite" rosary of NIL ItemNotNil: Rosary.RunActionType ~ { RETURN[quit: item#NIL] }; RosaryReplace: PROC [dest: ROSARY, size: INT, d1, d2: INT, source: ROSARY, s1, s2: INT] RETURNS [ROSARY] ~ { d: ROSARY ~ IF dest=NIL THEN nilRosary ELSE dest; s: ROSARY ~ IF source=NIL THEN nilRosary ELSE source; r: ROSARY ~ Rosary.CatSegments[[d, 0, d1], [s, s1, s2-s1], [d, d2, size-d2]]; RETURN[IF Rosary.MapRuns[[r], ItemNotNil] THEN r ELSE NIL]; }; DoReplace: PROC [root: Node, dest: Node, destStart, destLen: INT, sourceRope: ROPE, sourceRuns: Runs, sourceCharProps: ROSARY, sourceCharSets: ROSARY, sourceStart, sourceLen: INT, event: Event] RETURNS [resultStart, resultLen: INT] = { Inner: PROC = { replaceRope, newRope: ROPE; replaceRuns, newRuns: Runs; special: BOOL; destRope: ROPE ~ dest.rope; destSize: INT ~ Size[dest]; size: INT ~ destSize-destLen+sourceLen; destRuns: Runs ~ dest.charLooks; IF sourceLen > 0 THEN { replaceRope _ Rope.Substr[sourceRope, sourceStart, sourceLen]; replaceRuns _ TextLooks.Substr[sourceRuns, sourceStart, sourceLen]; }; special _ FALSE; IF destLen=0 THEN { -- doing an insert IF destStart=0 THEN { -- insert at start newRope _ RopeEdit.Concat[replaceRope, destRope, sourceLen, destSize]; newRuns _ TextLooks.Concat[replaceRuns, destRuns, sourceLen, destSize]; special _ TRUE; } ELSE IF destStart=destSize THEN { -- insert at end newRope _ RopeEdit.Concat[destRope, replaceRope, destSize, sourceLen]; newRuns _ TextLooks.Concat[destRuns, replaceRuns, destSize, sourceLen]; special _ TRUE; }; } ELSE IF sourceLen=0 THEN { -- doing a delete IF destStart=0 THEN { -- delete from start newRope _ Rope.Substr[destRope, destLen, size]; newRuns _ TextLooks.Substr[destRuns, destLen, size]; special _ TRUE; } ELSE IF (destStart+destLen)=destSize THEN { -- delete from end newRope _ Rope.Substr[destRope, 0, size]; newRuns _ TextLooks.Substr[destRuns, 0, size]; special _ TRUE; }; }; IF NOT special THEN { newRope _ Rope.Replace[base: destRope, start: destStart, len: destLen, with: Rope.Substr[replaceRope, 0, sourceLen]]; newRuns _ TextLooks.Replace[base: destRuns, start: destStart, len: destLen, replace: replaceRuns, baseSize: destSize, repSize: sourceLen] }; dest.rope _ newRope; dest.charLooks _ newRuns; IF dest.charSets#NIL OR sourceCharSets#NIL THEN dest.charSets _ RosaryReplace[ dest.charSets, destSize, destStart, destStart+destLen, sourceCharSets, sourceStart, sourceStart+sourceLen]; IF dest.charProps#NIL OR sourceCharProps#NIL THEN dest.charProps _ RosaryReplace[ dest.charProps, destSize, destStart, destStart+destLen, sourceCharProps, sourceStart, sourceStart+sourceLen]; NodeAddrs.Replace[dest, destStart, destLen, sourceLen]; }; DoWithNotify[action: Inner, event: event, root: root, text: dest, start: destStart, newlen: sourceLen, oldlen: destLen]; RETURN [destStart, sourceLen]; }; MoveText: PUBLIC PROC [destRoot, sourceRoot: Node, dest: Node, destLoc: INT _ 0, source: Node, start: INT _ 0, len: INT _ MaxLen, event: Event _ NIL] RETURNS [resultStart, resultLen: INT] = { MoveAddrs: PROC [addr: REF, location: INT] RETURNS [quit: BOOL] = { IF location NOT IN [start..start+len) THEN RETURN [FALSE]; IF dest=source THEN { -- pin addr at new location new: INT _ destLoc+location-start; IF destLoc > start THEN new _ new-len; -- moving toward end of node NodeAddrs.PutTextAddr[dest, addr, new]; NodeAddrs.PinTextAddr[dest, addr] -- so it won't be changed by Replace -- } ELSE NodeAddrs.MoveTextAddr[ -- move the addr to the new node from: source, to: dest, addr: addr, location: destLoc+location-start]; RETURN [FALSE]; }; sourceSize: INT ~ Rope.Size[source.rope]; destSize: INT ~ IF dest=source THEN sourceSize ELSE Rope.Size[dest.rope]; start _ MIN[MAX[0, start], sourceSize]; len _ MIN[MAX[0, len], sourceSize-start]; destLoc _ MIN[MAX[0, destLoc], destSize]; IF source=dest AND destLoc IN [start..start+len] THEN RETURN [start, len]; IF len=0 THEN RETURN [destLoc, 0]; [] _ NodeAddrs.MapTextAddrs[source, MoveAddrs]; [resultStart, resultLen] _ CopyText[destRoot, sourceRoot, dest, destLoc, source, start, len, event]; IF dest=source THEN { -- need to adjust locations IF destLoc > start THEN resultStart _ destLoc-len -- moving toward end of node ELSE start _ start+len; -- moving toward start of node }; DeleteText[sourceRoot, source, start, len, event]; IF dest=source THEN NodeAddrs.UnpinAll[source]; }; TransposeText: PUBLIC PROC [alphaRoot, betaRoot: Node, alpha: Node, alphaStart: INT _ 0, alphaLen: INT _ MaxLen, beta: Node, betaStart: INT _ 0, betaLen: INT _ MaxLen, event: Event _ NIL] RETURNS [alphaResultStart, alphaResultLen, betaResultStart, betaResultLen: INT] = { SwitchResults: PROC = INLINE --gfi saver-- { start, len: INT; start _ betaResultStart; len _ betaResultLen; betaResultStart _ alphaResultStart; betaResultLen _ alphaResultLen; alphaResultStart _ start; alphaResultLen _ len }; alphaSize, betaSize: INT; switched: BOOL _ FALSE; alphaSize _ Rope.Size[alpha.rope]; alphaStart _ MIN[MAX[0, alphaStart], alphaSize]; alphaResultLen _ alphaLen _ MIN[MAX[0, alphaLen], alphaSize-alphaStart]; betaSize _ IF beta=alpha THEN alphaSize ELSE Rope.Size[beta.rope]; betaStart _ MIN[MAX[0, betaStart], betaSize]; betaResultLen _ betaLen _ MIN[MAX[0, betaLen], betaSize-betaStart]; IF alpha=beta THEN { alphaEnd: INT; IF alphaStart > betaStart THEN { -- switch them start: INT _ alphaStart; len: INT _ alphaLen; root: Node _ alphaRoot; alphaStart _ betaStart; betaStart _ start; alphaResultLen _ alphaLen _ betaLen; alphaRoot _ betaRoot; betaRoot _ root; betaResultLen _ betaLen _ len; switched _ TRUE; }; alphaEnd _ alphaStart+alphaLen; IF alphaEnd = betaStart THEN { -- turn into a move instead [] _ MoveText[alphaRoot, betaRoot, alpha, alphaStart, alpha, betaStart, betaLen, event]; betaResultStart _ alphaStart; alphaResultStart _ alphaStart+betaLen; IF switched THEN SwitchResults[]; RETURN; }; IF alphaEnd > betaStart THEN { -- overlapping overlap, alphaHeadLen, betaTailLen: INT; alphaHeadLen _ betaStart-alphaStart; betaTailLen _ betaStart+betaLen-alphaEnd; IF alphaHeadLen < 0 OR betaTailLen < 0 THEN RETURN [alphaStart, alphaLen, betaStart, betaLen]; overlap _ alphaEnd-betaStart; [alphaResultStart, alphaResultLen, betaResultStart, betaResultLen] _ TransposeText[alphaRoot, betaRoot, alpha, alphaStart, alphaHeadLen, alpha, alphaEnd, betaTailLen, event]; betaResultLen _ betaResultLen+overlap; alphaResultStart _ alphaResultStart-overlap; alphaResultLen _ alphaResultLen+overlap; IF switched THEN SwitchResults[]; RETURN; }; }; [alphaResultStart, alphaResultLen] _ MoveText[ -- move alpha after beta destRoot: betaRoot, sourceRoot: alphaRoot, dest: beta, destLoc: betaStart+betaLen, source: alpha, start: alphaStart, len: alphaLen, event: event]; IF alpha=beta THEN betaStart _ betaStart-alphaLen ELSE alphaResultStart _ alphaResultStart-betaLen; [betaResultStart, betaResultLen] _ MoveText[ -- move beta to old alpha location destRoot: alphaRoot, sourceRoot: betaRoot, dest: alpha, destLoc: alphaStart, source: beta, start: betaStart, len: betaLen, event: event]; IF switched THEN SwitchResults; }; ReplaceByChar: PUBLIC PROC [root: Node, dest: Node, char: CHAR, start: INT, len: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ ReplaceByRope[root: root, dest: dest, rope: Rope.FromChar[char], start: start, len: len, inherit: inherit, looks: looks, charSet: charSet, event: event]; }; ReplaceByString: PUBLIC PROC [root: Node, dest: Node, string: REF READONLY TEXT, stringStart: NAT, stringNum: NAT, start: INT, len: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { stringStart _ MIN[string.length, stringStart]; stringNum _ MIN[string.length-stringStart, stringNum]; [resultStart, resultLen] _ ReplaceByRope[root: root, dest: dest, rope: Rope.FromRefText[s: string, start: stringStart, len: stringNum], start: start, len: len, inherit: inherit, looks: looks, charSet: charSet, event: event]; }; UndoChangeText: PROC [undoRef: REF, currentEvent: Event] = { WITH undoRef SELECT FROM x: REF Change.ChangingText => { destSize: INT ~ Rope.Size[x.text.rope]; sourceSize: INT ~ Rope.Size[x.oldRope]; [] _ DoReplace[ root: x.root, dest: x.text, destStart: 0, destLen: destSize, sourceRope: x.oldRope, sourceRuns: x.oldRuns, sourceCharProps: NARROW[x.oldCharProps], sourceCharSets: NARROW[x.oldCharSets], sourceStart: 0, sourceLen: sourceSize, event: currentEvent ]; }; ENDCASE => ERROR; }; ReplaceText: PUBLIC PROC [destRoot: Node, sourceRoot: Node, dest: Node, destStart: INT, destLen: INT, source: Node, sourceStart: INT, sourceLen: INT, event: Event] RETURNS [resultStart, resultLen: INT] = { IF dest = NIL THEN {resultStart _ resultLen _ 0} ELSE IF source = NIL THEN {[resultStart, resultLen] _ ReplaceByRope[root: destRoot, dest: dest, rope: NIL, start: destStart, len: destLen, inherit: FALSE, looks: ALL[FALSE], charSet: 0, event: event]} ELSE { sourceRope: ROPE ~ source.rope; sourceSize: INT ~ Rope.Size[sourceRope]; destSize: INT ~ Rope.Size[dest.rope]; sourceRuns: Runs ~ source.charLooks; sourceCharProps: ROSARY ~ source.charProps; sourceCharSets: ROSARY ~ source.charSets; sourceStart _ MIN[MAX[0, sourceStart], sourceSize]; IF sourceLen # 0 THEN { IF sourceStart >= sourceSize THEN sourceStart _ sourceSize; sourceLen _ MIN[MAX[0, sourceLen], sourceSize-sourceStart]; }; destStart _ MIN[MAX[0, destStart], destSize]; destLen _ MIN[MAX[0, destLen], destSize-destStart]; IF destLen=0 AND sourceLen=0 THEN RETURN [destStart, 0]; [resultStart, resultLen] _ DoReplace[ root: destRoot, dest: dest, destStart: destStart, destLen: destLen, sourceRope: sourceRope, sourceRuns: sourceRuns, sourceCharProps: sourceCharProps, sourceCharSets: sourceCharSets, sourceStart: sourceStart, sourceLen: sourceLen, event: event ]; }; }; DeleteText: PUBLIC OneSpanProc = { [] _ ReplaceByRope[root: root, dest: node, rope: NIL, start: start, len: len, inherit: FALSE, looks: ALL[FALSE], charSet: 0, event: event]; }; CopyText: PUBLIC DestSpanProc = { -- copy the specified text [resultStart, resultLen] _ ReplaceText[destRoot: destRoot, sourceRoot: sourceRoot, dest: dest, destStart: destLoc, destLen: 0, source: source, sourceStart: start, sourceLen: len, event: event]; }; MoveTextOnto: PUBLIC PROC [destRoot, sourceRoot: Node, dest: Node, destStart: INT _ 0, destLen: INT _ MaxLen, source: Node, sourceStart: INT _ 0, sourceLen: INT _ MaxLen, event: Event _ NIL] RETURNS [resultStart, resultLen: INT] = { sourceSize, destSize, start, len, end, destEnd: INT _ 0; start _ sourceStart; len _ sourceLen; sourceSize _ Size[source]; start _ MIN[MAX[0, start], sourceSize]; len _ MIN[MAX[0, len], sourceSize-start]; end _ start+len; destSize _ IF dest=source THEN sourceSize ELSE Size[dest]; destStart _ MIN[MAX[0, destStart], destSize]; destLen _ MIN[MAX[0, destLen], destSize-destStart]; destEnd _ destStart+destLen; IF source=dest THEN { -- check for overlapping or adjacent IF start IN [destStart..destEnd) THEN { IF end < destEnd THEN [] _ DeleteText[sourceRoot, source, end, destEnd-end, event]; IF start > destStart THEN [] _ DeleteText[sourceRoot, source, destStart, start-destStart, event]; RETURN [destStart, len]; }; IF end IN (destStart..destEnd) THEN { [] _ DeleteText[sourceRoot, source, end, destEnd-end, event]; RETURN [start, len]; }; IF start <= destStart AND end >= destEnd THEN RETURN [start, len]; IF end=destStart THEN { [] _ DeleteText[sourceRoot, source, destStart, destLen, event]; RETURN [start, len]; }; IF start=destEnd THEN { [] _ DeleteText[sourceRoot, source, destStart, destLen, event]; RETURN [destStart, len]; }; }; [] _ DeleteText[destRoot, dest, destStart, destLen, event]; IF source=dest AND start > destStart THEN start _ start-destLen; [resultStart, resultLen] _ MoveText[destRoot, sourceRoot, dest, destStart, source, start, len, event]; }; refFromCharSetTab: CardTab.Ref ~ CardTab.Create[]; RefFromCharSet: PROC [charSet: CharSet] RETURNS [ref: REF CharSet _ NIL] ~ { refFromCharSetAction: CardTab.UpdateAction ~ { IF found THEN { ref _ NARROW[val]; RETURN[op: none] } ELSE { ref _ NEW[CharSet _ charSet]; RETURN[op: store, new: ref] }; }; IF charSet=0 THEN RETURN[NIL]; CardTab.Update[refFromCharSetTab, charSet, refFromCharSetAction]; IF ref^#charSet THEN ERROR; }; ReplaceByRope: PUBLIC PROC [root: Node, dest: Node, rope: ROPE, start: INT, len: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT _ 0] = { IF dest#NIL THEN { destRope: ROPE ~ dest.rope; destSize: INT ~ Rope.Size[destRope]; ropeSize: INT ~ Rope.Size[rope]; Inner: PROC = { dest.rope _ Rope.Replace[base: destRope, start: start, len: len, with: rope]; dest.charLooks _ TextLooks.ReplaceByRun[dest.charLooks, start, len, ropeSize, destSize, inherit, looks]; NodeAddrs.Replace[dest, start, len, ropeSize]; IF dest.charSets#NIL OR charSet#0 THEN { sourceCharSets: ROSARY ~ IF charSet=0 THEN NIL ELSE Rosary.FromItem[RefFromCharSet[charSet], ropeSize]; dest.charSets _ RosaryReplace[dest.charSets, destSize, start, start+len, sourceCharSets, 0, ropeSize]; }; IF dest.charProps#NIL THEN { dest.charProps _ RosaryReplace[dest.charProps, destSize, start, start+len, NIL, 0, ropeSize]; }; resultStart _ start; resultLen _ ropeSize; }; start _ MIN[MAX[0, start], destSize]; len _ MIN[MAX[0, len], destSize-start]; DoWithNotify[action: Inner, event: event, root: root, text: dest, start: start, newlen: ropeSize, oldlen: len]; }; }; InsertChar: PUBLIC PROC [root: Node, dest: Node, char: CHAR, destLoc: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ ReplaceByChar[root: root, dest: dest, char: char, start: destLoc, len: 0, inherit: inherit, looks: looks, charSet: charSet, event: event] }; AppendChar: PUBLIC PROC [root: Node, dest: Node, char: CHAR, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ InsertChar[root, dest, char, MaxLen, inherit, looks, charSet, event] }; InsertString: PUBLIC PROC [root: Node, dest: Node, string: REF READONLY TEXT, stringStart: NAT, stringNum: NAT, destLoc: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ ReplaceByString[root: root, dest: dest, string: string, stringStart: stringStart, stringNum: stringNum, start: destLoc, len: 0, inherit: inherit, looks: looks, charSet: charSet, event: event] }; AppendString: PUBLIC PROC [root: Node, dest: Node, string: REF READONLY TEXT, stringStart: NAT, stringNum: NAT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ InsertString[root: root, dest: dest, string: string, stringStart: stringStart, stringNum: stringNum, destLoc: MaxLen, inherit: inherit, looks: looks, charSet: charSet, event: event] }; InsertRope: PUBLIC PROC [root: Node, dest: Node, rope: ROPE, destLoc: INT, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ ReplaceByRope[root: root, dest: dest, rope: rope, start: destLoc, len: 0, inherit: inherit, looks: looks, charSet: charSet, event: event] }; AppendRope: PUBLIC PROC [root: Node, dest: Node, rope: ROPE, inherit: BOOL, looks: Looks, charSet: CharSet, event: Event] RETURNS [resultStart, resultLen: INT] = { [resultStart, resultLen] _ InsertRope[root: root, dest: dest, rope: rope, destLoc: MaxLen, inherit: inherit, looks: looks, charSet: charSet, event: event] }; ChangeLooks: PUBLIC PROC [root: Node, node: Node, remove, add: Looks, start: INT _ 0, len: INT _ MaxOffset, event: Event _ NIL] = { size: INT; newRuns: Runs; IF node=NIL THEN RETURN; size _ Rope.Size[node.rope]; start _ MIN[MAX[0, start], size]; len _ MIN[MAX[0, len], size-start]; IF len=0 THEN RETURN; newRuns _ TextLooks.ChangeLooks[node.charLooks, size, remove, add, start, len]; IF newRuns=node.charLooks THEN RETURN; -- no change DoChangeLooks[root, node, newRuns, start, len, event]; }; DoChangeLooks: PROC [root: Node, text: Node, newRuns: Runs, start, len: INT, event: Event] = { Inner: PROC = { text.charLooks _ newRuns }; DoWithNotify[action: Inner, event: event, root: root, text: text, start: start, newlen: len, oldlen: len]; }; AddLooks: PUBLIC PROC [root: Node, text: Node, add: Looks, start: INT, len: INT, event: Event] = { ChangeLooks[root, text, noLooks, add, start, len, event] }; RemoveLooks: PUBLIC PROC [root: Node, text: Node, remove: Looks, start: INT, len: INT, event: Event] = { ChangeLooks[root, text, remove, noLooks, start, len, event] }; SetLooks: PUBLIC PROC [root: Node, text: Node, new: Looks, start: INT, len: INT, event: Event] = { ChangeLooks[root, text, allLooks, new, start, len, event] }; ClearLooks: PUBLIC PROC [root: Node, text: Node, start: INT, len: INT, event: Event] = { ChangeLooks[root, text, allLooks, noLooks, start, len, event] }; PropList: TYPE ~ Atom.PropList; GetPropFromList: PUBLIC PROC [propList: PropList, key: ATOM] RETURNS [value: REF] ~ { FOR list: PropList _ propList, list.rest UNTIL list=NIL DO IF list.first.key=key THEN RETURN[list.first.val]; ENDLOOP; RETURN[NIL]; }; PutPropOnList: PUBLIC PROC [propList: PropList, key: ATOM, value: REF] RETURNS [new: PropList] ~ { RemoveKeyFrom: PROC [p: PropList] RETURNS [PropList] = { SELECT TRUE FROM (p = NIL) => RETURN [NIL]; (p.first.key = key) => RETURN [p.rest]; ENDCASE => { rest: PropList _ RemoveKeyFrom[p.rest]; RETURN [IF rest # p.rest THEN CONS[p.first, rest] ELSE p] }; }; new _ RemoveKeyFrom[propList]; IF value # NIL THEN new _ CONS[NEW[Atom.DottedPairNode _ [key, value]], new]; }; GetCharProp: PUBLIC PROC [node: Node, index: INT, name: ATOM] RETURNS [value: REF] ~ { RETURN [GetPropFromList[FetchCharPropList[node, index], name]]; }; MapCharProps: PUBLIC PROC [node: Node, index: INT, action: MapPropsAction] RETURNS [quit: BOOL] ~ { FOR list: PropList _ FetchCharPropList[node, index], list.rest UNTIL list=NIL DO IF action[NARROW[list.first.key], list.first.val].quit THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE]; }; ChangeCharProps: PROC [node: Node, size, start, len: INT, source: ROSARY, event: Event, root: Node] ~ { newCharProps: ROSARY ~ RosaryReplace[node.charProps, size, start, start+len, source, 0, len]; inner: PROC ~ { node.charProps _ newCharProps }; DoWithNotify[action: inner, event: event, root: root, text: node, start: start, newlen: len, oldlen: len]; }; PutCharProp: PUBLIC PROC [node: Node, index: INT, name: ATOM, value: REF, nChars: INT, event: Event, root: Node] ~ { size: INT ~ Rope.Size[node.rope]; start: INT ~ MAX[LONG[0], MIN[index, size]]; len: INT ~ start+MAX[LONG[0], MIN[nChars, size-start]]; r: ROSARY ~ IF node.charProps=NIL THEN nilRosary ELSE node.charProps; p: PROC[q: PROC[REF, INT]] ~ { action: Rosary.RunActionType ~ { oldList: PropList ~ NARROW[item]; q[PutPropOnList[oldList, name, value], repeat]; }; [] _ Rosary.MapRuns[[r, start, len], action]; }; s: ROSARY ~ Rosary.FromProcProc[p]; ChangeCharProps[node, size, start, len, s, event, root]; }; PutCharPropList: PUBLIC PROC [node: Node, index: INT, propList: PropList, nChars: INT, event: Event, root: Node] ~ { size: INT ~ Rope.Size[node.rope]; start: INT ~ MAX[LONG[0], MIN[index, size]]; len: INT ~ start+MAX[LONG[0], MIN[nChars, size-start]]; s: ROSARY ~ Rosary.FromItem[propList, len]; ChangeCharProps[node, size, start, len, s, event, root]; }; ModifyCharProps: PUBLIC PROC [node: Node, name: ATOM, index: INT, nChars: INT, action: ModifyPropsAction, event: Event, root: Node] RETURNS [quit: BOOL] ~ { size: INT ~ Rope.Size[node.rope]; start: INT ~ MAX[LONG[0], MIN[index, size]]; len: INT ~ start+MAX[LONG[0], MIN[nChars, size-start]]; r: ROSARY ~ IF node.charProps=NIL THEN nilRosary ELSE node.charProps; prefixSize: INT _ INT.LAST; i: INT _ start; p: PROC[q: PROC[REF, INT]] ~ { runAction: Rosary.RunActionType ~ { oldList: PropList ~ NARROW[item]; oldValue: REF ~ GetPropFromList[oldList, name]; stop: BOOL _ FALSE; newValue: REF _ NIL; [stop, newValue] _ action[oldValue, i, repeat]; IF newValue # oldValue AND prefixSize = INT.LAST THEN prefixSize _ i; IF prefixSize # INT.LAST THEN { newList: PropList ~ IF newValue = oldValue THEN oldList ELSE PutPropOnList[oldList, name, newValue]; q[newList, repeat]; }; i _ i + repeat; IF stop THEN RETURN [TRUE]; }; [] _ Rosary.MapRuns[[r, start, len], runAction]; }; s: ROSARY ~ Rosary.FromProcProc[p]; -- sets prefixSize and i as side effect IF prefixSize # INT.LAST THEN { sSize: INT ~ Rosary.Size[s]; IF (prefixSize+sSize)#i THEN ERROR; ChangeCharProps[node, size, prefixSize, sSize, s, event, root]; }; }; ChangeFormat: PUBLIC PROC [node: Node, formatName: ATOM, event: Event _ NIL, root: Node _ NIL] = { notify: REF Change.ChangingFormat; oldFormatName: ATOM _ node.formatName; IF node=NIL THEN RETURN; IF root=NIL THEN root _ TextNode.Root[node]; IF oldFormatName = formatName THEN RETURN; -- no change notify _ NEW[Change.ChangingFormat _ [ChangingFormat[root, node, formatName, oldFormatName]]]; EditNotify.Notify[notify, before]; node.formatName _ formatName; EditNotify.Notify[notify, after]; IF event#NIL THEN UndoEvent.Note[event, UndoChangeFormat, notify]; }; UndoChangeFormat: PROC [undoRef: REF, currentEvent: Event] = { WITH undoRef SELECT FROM x: REF Change.ChangingFormat => { ChangeFormat[x.node, x.oldFormatName, currentEvent, x.root]; }; ENDCASE => ERROR; }; quote: ROPE ~ "\""; ChangeStyle: PUBLIC PROC [node: Node, name: ROPE, event: Event _ NIL, root: Node _ NIL] = { newprefix: ROPE _ NIL; IF node=NIL THEN RETURN; IF Rope.Size[name] > 0 THEN newprefix _ Rope.Cat[quote, name, quote, " style"]; PutProp[node, $Prefix, newprefix, event, root]; }; PutProp: PUBLIC PROC [node: Node, name: ATOM, value: REF, event: Event _ NIL, root: Node _ NIL] = { notify: REF Change.ChangingProp; oldval: REF; IF node=NIL THEN RETURN; IF root=NIL THEN root _ TextNode.Root[node]; IF (oldval _ NodeProps.GetProp[node, name]) = value THEN RETURN; notify _ NEW[Change.ChangingProp_[ChangingProp[root, node, Atom.GetPName[name], name, value, oldval]]]; EditNotify.Notify[notify, before]; NodeProps.PutProp[node, name, value]; EditNotify.Notify[notify, after]; IF event#NIL THEN UndoEvent.Note[event, UndoPutProp, notify]; }; UndoPutProp: PROC [undoRef: REF, currentEvent: Event] = { WITH undoRef SELECT FROM x: REF Change.ChangingProp => { PutProp[x.node, x.propAtom, x.oldval, currentEvent, x.root]; }; ENDCASE => ERROR; }; GetProp: PUBLIC PROC [node: Node, name: ATOM] RETURNS [value: REF] = { IF node=NIL THEN RETURN[NIL]; RETURN [NodeProps.GetProp[node, name]]; }; Size: PUBLIC PROC [node: Node] RETURNS [INT] ~ { RETURN [IF node=NIL THEN 0 ELSE Rope.InlineSize[node.rope]]; }; FetchChar: PUBLIC PROC [node: Node, index: INT] RETURNS [char: XChar _ [0, 0]] ~ { char.code _ ORD[Rope.Fetch[node.rope, index]]; IF node.charSets#NIL THEN { refSet: REF CharSet ~ NARROW[Rosary.Fetch[node.charSets, index]]; IF refSet#NIL THEN char.set _ refSet^; }; }; FetchLooks: PUBLIC PROC [node: Node, index: INT] RETURNS [Looks] ~ { charLooks: ROSARY ~ IF node=NIL THEN NIL ELSE node.charLooks; IF charLooks=NIL THEN { [] _ Basics.NonNegative[Size[node]-Basics.NonNegative[index]-1]; RETURN[noLooks]; } ELSE { refLooks: REF Looks ~ NARROW[Rosary.Fetch[charLooks, index]]; RETURN[IF refLooks=NIL THEN noLooks ELSE refLooks^]; }; }; FetchCharPropList: PUBLIC PROC [node: Node, index: INT] RETURNS [PropList] ~ { charProps: ROSARY ~ IF node=NIL THEN NIL ELSE node.charProps; IF charProps=NIL THEN { [] _ Basics.NonNegative[Size[node]-Basics.NonNegative[index]-1]; RETURN[NIL]; } ELSE { propList: PropList ~ NARROW[Rosary.Fetch[charProps, index]]; RETURN[propList]; }; }; ItemRun: TYPE ~ RECORD [item: REF, start, end: INT]; FetchItemRun: PROC [base: ROSARY, index: INT] RETURNS [ItemRun] ~ { origin: INT ~ Basics.NonNegative[index]; [] _ Basics.NonNegative[base.size-index-1]; -- bounds check DO WITH base SELECT FROM f: REF Rosary.RosaryRep.leaf => { start: INT ~ origin-index; RETURN [[f.item, start, start+f.size]]; }; c: REF Rosary.RosaryRep.concat => { pos: INT ~ c.base.size; IF index < pos THEN base _ c.base ELSE {base _ c.rest; index _ index-pos}; }; o: REF Rosary.RosaryRep.object => ERROR; -- not implemented ENDCASE => ERROR; -- malformed rosary ENDLOOP; }; FetchCharSetRun: PUBLIC PROC [node: Node, index: INT] RETURNS [CharSetRun] ~ { IF node=NIL OR node.charSets=NIL THEN RETURN[[charSet: 0, start: 0, end: Size[node]]] ELSE { run: ItemRun ~ FetchItemRun[node.charSets, index]; ref: REF CharSet ~ NARROW[run.item]; RETURN[[charSet: (IF ref=NIL THEN 0 ELSE ref^), start: run.start, end: run.end]] }; }; FetchLooksRun: PUBLIC PROC [node: Node, index: INT] RETURNS [LooksRun] ~ { IF node=NIL OR node.charLooks=NIL THEN RETURN[[looks: noLooks, start: 0, end: Size[node]]] ELSE { run: ItemRun ~ FetchItemRun[node.charLooks, index]; ref: REF Looks ~ NARROW[run.item]; RETURN[[looks: (IF ref=NIL THEN noLooks ELSE ref^), start: run.start, end: run.end]] }; }; FetchCharPropListRun: PUBLIC PROC [node: Node, index: INT] RETURNS [PropListRun] ~ { IF node=NIL OR node.charProps=NIL THEN RETURN[[propList: NIL, start: 0, end: Size[node]]] ELSE { run: ItemRun ~ FetchItemRun[node.charProps, index]; RETURN[[propList: NARROW[run.item], start: run.start, end: run.end]] }; }; FromRope: PUBLIC PROC [rope: ROPE] RETURNS [new: Node] = { new _ TextNode.NewTextNode[]; new.rope _ rope; new.last _ TRUE; new.charLooks _ TextLooks.CreateRun[Rope.Size[rope]]; }; FromString: PUBLIC PROC [string: REF READONLY TEXT] RETURNS [new: Node] = { new _ TextNode.NewTextNode[]; new.last _ TRUE; new.rope _ Rope.FromRefText[string]; new.charLooks _ TextLooks.CreateRun[Rope.Size[new.rope]]; }; DocFromNode: PUBLIC PROC [child: Node] RETURNS [new: Node] = { new _ TextNode.NewTextNode[]; new.child _ child; new.last _ TRUE; child.next _ new; child.last _ TRUE; }; ChangeCaps: PUBLIC PROC [root: Node, dest: Node, start: INT, len: INT, how: CapChange, event: Event] = { destSize: INT = (IF dest = NIL THEN 0 ELSE Rope.Size[dest.rope]); Inner: PROC = { destRope: ROPE _ dest.rope; rdr: RopeReader.Ref = RopeReader.GetRopeReader[]; UNTIL len=0 DO maxText: NAT = 1000; num: NAT _ MIN[len, maxText]; new: REF TEXT _ NEW[TEXT[num]]; RopeReader.SetPosition[rdr, destRope, start]; SELECT how FROM firstCap => { IF num > 0 THEN { new[0] _ RopeEdit.UpperCase[RopeReader.Get[rdr]]; FOR i: NAT IN [1..num) DO new[i] _ RopeEdit.LowerCase[RopeReader.Get[rdr]]; ENDLOOP }; }; allCaps => { FOR i: NAT IN [0..num) DO new[i] _ RopeEdit.UpperCase[RopeReader.Get[rdr]]; ENDLOOP; }; allLower => { FOR i: NAT IN [0..num) DO new[i] _ RopeEdit.LowerCase[RopeReader.Get[rdr]]; ENDLOOP; }; initCaps => { init: BOOL _ TRUE; c: CHAR; FOR i: NAT IN [0..num) DO SELECT c _ RopeReader.Get[rdr] FROM IN ['a..'z] => { new[i] _ IF init THEN c-40B ELSE c; -- force first upper init _ FALSE }; IN ['A..'Z] => { new[i] _ IF init THEN c ELSE c+40B; -- force others lower init _ FALSE }; ENDCASE => { new[i] _ c; init _ c # '\' AND ~RopeEdit.AlphaNumericChar[c] }; ENDLOOP }; ENDCASE => ERROR; new.length _ num; destRope _ RopeEdit.ReplaceByTEXT[destRope, new, 0, num, start, num, destSize]; IF Rope.Size[destRope] # destSize THEN ERROR; len _ len-num; start _ start+num; ENDLOOP; dest.rope _ destRope; RopeReader.FreeRopeReader[rdr]; }; start _ MIN[MAX[0, start], destSize]; len _ MIN[len, destSize-start]; IF len <= 0 THEN RETURN; DoWithNotify[action: Inner, event: event, root: root, text: dest, start: start, newlen: len, oldlen: len]; }; END. @TextEditImpl.mesa Copyright Ó 1985, 1986, 1987, 1988 by Xerox Corporation. All rights reserved. written by Bill Paxton, March 1981 last edit by Bill Paxton, December 28, 1982 11:43 am Last Edited by: Maxwell, January 5, 1983 3:31 pm Michael Plass, October 19, 1987 9:46:08 am PDT Doug Wyatt, February 19, 1988 11:53:29 am PST Cache of notify records Text Editing Support -- returns TRUE if there is already a ChangingText record for given node in the event Don't keep searching too long; give up and make a new event instead. - mfp Text Editing Operations must replace all of text since only save first change per event -- replace the dest text by a copy of the source text -- names that are in the replaced text move to destStart -- names that are after the replaced text are adjusted PROC [found: BOOL, val: Val] RETURNS [op: UpdateOperation _ none, new: Val _ NIL] Operation to add or delete looks -- first remove then add in the given range Character Properties Note: Character properties are implemented using a ROSARY (an immutable sequence of REFs; see Rosary.mesa), appearing in the CharProps property of the node. The values in this rosary are of type Atom.PropList, but don't use Atom.PutPropOnList, since we regard these lists as immutable. The values on the property lists are externalized and internalized using the procedures registered with NodeProps, but no warrantee is expressed or implied for props that are not immutable. This property list mechanism regards a PropList as an immutable value. We don't use the procedures from Atom since 1) they might smash an existing PropList, and 2) they hold a global monitor in AtomImpl. Changing Style / Format of node Fetch info operations Operations to create a text node -- create a text node with looks from a normal rope -- copies the contents of the string Caps and Lowercase Ê& ˜codešœ™KšœN™NKšœ"™"Kšœ4™4K™0K™.K™-K™—šÏk ˜ Kšœœ&˜0Kšœ˜Kšœ˜Kšœ œ˜"Kšœ œK˜ZKšœ œ˜#KšœœBœ˜`Kšœ œQ˜_Kšœ œA˜QKšœœ7œ"˜kKšœ ˜ Kšœ ˜ Kšœ œ˜)Kšœ œ˜(—K˜KšÐbl œœ˜Kšœ|˜ƒKšœ ˜Kšœœœ ˜˜Kšœœ˜Kšœœœ˜Kšœœ œ˜Kšœœ˜K˜#K˜%Kšœœœœ˜#Kšœœ˜K˜Kšœœ˜!Kšœœ˜Kšœ œ ˜K˜—headšœ™K˜Kšœœ˜6K˜šÏnœœœœ œœÏc œ˜UKšœœœ˜šœœ˜Kšœ œ&œ˜;Kšœ œ&œ˜;Kšœ œ&œ˜;Kšœœ˜2—Kšœ˜—K˜š Ÿœœœ œœ  œ˜LKšœœœ˜Kš œœœœœ˜Mšœœ˜Kšœ œ˜'Kšœ œ˜'Kšœ œ˜'Kšœœ˜—Kšœ˜—K™—šœ™K˜šŸ œœœ œœœœ˜WK˜—šŸ œœ œœœœ  œ˜LKš œ-œœ œœœ˜VKšœ˜K˜—Kšœœ˜š Ÿ œœœœœ  œ˜UKšœV™VKšœœ˜Kš œ œœœœ˜"šœ1œœ˜Bšœ œ˜šœ œ˜Kš œœœœœœ˜AKšœ˜——šœœœ˜K™J—Kšœ˜—Kšœœ˜Kšœ˜K˜—šŸ œœ œ/œ œ œœ  œ˜†Kšœœ˜/Kš œœœœœœ˜]šœ˜KšœJ˜JKšœ-˜-Kšœ˜Kšœ˜Kšœ˜—K˜"Kšœ ˜ K˜!Kšœœ œ/˜UKšœ˜K˜——šœ™Iunitš œ œœœœ ˜OMšŸ œœ œ˜>šŸ œœœœ œ œ œœœ˜mKš œœœœœ œ˜1Kš œœœœœ œ˜5KšœœD˜MKš œœ!œœœ˜;K˜—šŸ œœ.œœ%œœœœœ˜ëšŸœœ˜Kšœœ˜K˜Kšœ œ˜Kšœ œ ˜Kšœ œ˜Kšœœ˜'K˜ šœœ˜K˜>K˜CK˜—Kšœ œ˜šœ ˜ šœ ˜šœ ˜šœ ˜K˜FK˜GKšœ œ˜Kšœ˜—šœœœ ˜2K˜FK˜GKšœ œ˜Kšœ˜——Kšœ˜—šœœ œ ˜,šœ ˜šœ ˜K˜/K˜4Kšœ œ˜Kšœ˜—šœœœ ˜>K˜)K˜.Kšœ œ˜Kšœ˜——Kšœ˜——šœœ œ˜K•StartOfExpansionM[base: ROPE, start: INT _ 0, len: INT _ 2147483647, with: ROPE _ NIL]˜uK–†[base: TextLooks.Runs, start: INT, len: INT, replace: TextLooks.Runs, baseSize: INT, repSize: INT, tryFlat: BOOL _ TRUE]˜‰K˜—K˜K˜Kš œœœœœŒ˜»Kš œœœœœ˜ÀK˜7Kšœ˜—Kšœx˜xKšœ˜Kšœ˜K˜—šŸœœœ3œœ œœœœ˜¿š Ÿ œœœ œœœ˜CKš œ œœœœœ˜:šœ ˜šœ ˜"Kšœœ˜"Kšœœ ˜CKšœ'˜'Kšœ" '˜IKšœ˜—šœ  ˜=KšœF˜F——Kšœœ˜Kšœ˜—Kšœ œ˜)Kš œ œœ œ œ˜IKšœœœ˜'Kšœœœ˜)Kšœ œœ˜)Kš œ œ œœœ˜JKšœœœ˜"Kšœ/˜/Kšœd˜dšœ œ ˜1šœ˜Kšœ ˜;Kšœ ˜6—Kšœ˜—Kšœ2˜2Kšœ œ˜/Kšœ˜K˜—šŸ œœœ6œœ"œœœœDœ˜šŸ œœœ  œ˜,Kšœ œ˜K˜-K˜CK˜.K˜—Kšœœ˜Kšœ œœ˜K˜"Kšœ œœ˜0Kšœœœ%˜HKšœ œ œ œ˜BKšœ œœ˜-Kšœœœ"˜Cšœ œ˜Kšœ œ˜šœœ ˜/Kšœœ˜Kšœœ ˜Kšœ˜K˜*K˜$K˜&K˜Kšœ œ˜Kšœ˜—K˜šœœ ˜:K˜XK˜K˜&Kšœ œ˜!Kšœ˜Kšœ˜—šœœ ˜-Kšœ$œ˜(K˜$K˜)šœœ˜+Kšœ,˜2—K˜˜D˜DK˜%——K˜&K˜,K˜(Kšœ œ˜!Kšœ˜Kšœ˜—Kšœ˜—šœ/ ˜GK˜+K˜(K˜@—Kšœ œœ-˜cšœ- "˜OK˜+K˜"K˜<—Kšœ œ˜Kšœ˜K˜—šŸ œœœ œ œœ œ0œœ˜¼Kšœ´˜´Kšœ˜K˜—šŸœœœ"œœœœ œ œœ œ0œœ˜ïKšœœ˜.Kšœ œ'˜6K–>[s: REF READONLY TEXT, start: NAT _ 0, len: NAT _ 32767]šœà˜àKšœ˜K˜—šŸœœ œ˜<šœ œ˜šœœ˜Kšœ?™?Kšœ œ˜'Kšœ œ˜'šœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ˜Kšœ˜Kšœ˜Kšœœ˜(Kšœœ˜&Kšœ˜Kšœ˜Kšœ˜Kšœ˜—Kšœ˜—Kšœœ˜—Kšœ˜K˜—šŸ œœœ;œ œœ œœœ˜ÍKšœ5™5Kšœ8™8Kšœ6™6Kšœœœ˜0K–ç[root: TextEdit.RefTextNode, dest: TextEdit.RefTextNode, rope: ROPE, start: INT _ 0, len: INT _ 2147483647, inherit: BOOL _ TRUE, looks: TextLooks.Looks, charSet: TextEdit.CharSet _ 0, event: TextEdit.Event _ NIL]šœœ œœMœ+œ œœ˜Èšœ˜Kšœ œ˜Kšœ œ˜(Kšœ œ˜%Kšœ$˜$Kšœœ˜+Kšœœ˜)Kšœœœ˜3šœœ˜Kšœœ˜;Kšœ œœ(˜;Kšœ˜—Kšœ œœ˜-Kšœ œœ"˜3Kšœ œ œœ˜8šœ%˜%Kšœ˜Kšœ ˜ Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ!˜!Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ˜—Kšœ˜—˜K˜——šŸ œœ˜"Kš œ1œ#œ œœ˜‹Kšœ˜K˜—šŸœœ ˜˜SKšœœH˜aKšœ˜Kšœ˜—šœœœ˜%K˜=Kšœ˜Kšœ˜—Kšœœœœ˜Bšœœ˜K˜?Kšœ˜Kšœ˜—šœœ˜K˜?Kšœ˜Kšœ˜—Kšœ˜—K˜;Kšœ œœ˜@K˜fK˜—M˜2š Ÿœœœœ œ˜Lšœ.˜.Kšœ œ œ)œ™QKšœœ œœ ˜5Kšœ œœ˜CK˜—Kšœ œœœ˜KšœA˜AKšœœœ˜Kšœ˜—K˜šŸ œœœ œ œœ œ0œœ ˜Àšœœœ˜Kšœ œ ˜Kšœ œ˜$Kšœ œ˜ šŸœœ˜K˜MK˜hK˜.šœœœ œ˜(šœœœ œ˜.Kšœ4˜8—K˜gKšœ˜—šœœœ˜KšœLœ˜^Jšœ˜—Kšœ˜Kšœ˜Kšœ˜—Kšœœœ˜%Kšœœœ˜'Kšœo˜oJšœ˜—Kšœ˜K˜—šŸ œœœ œ œ œ0œœ˜®Kšœ«˜«K˜—šŸ œœœ œ œ0œœ˜ Kšœf˜fK˜—šŸ œœœ"œœœœ œ œ œ0œœ˜áKšœá˜áK˜—šŸ œœœ"œœœœ œ œ0œœ˜ÒKšœ×˜×K˜—šŸ œœœ œ œ œ0œœ˜®Kšœ«˜«K˜—šŸ œœœ œ œ0œœ˜ Kšœ¡˜¡—˜K˜——šœ ™ K˜š Ÿ œœœ5œ œœ˜ƒKšœ+™+Kšœœ˜ K˜Kšœœœœ˜K˜Kšœœœ˜!Kšœœœ˜#Kšœœœ˜K˜PKšœœœ  ˜3K˜6K˜K˜—šŸ œœ5œ˜^KšŸœœ ˜+Kšœj˜jKšœ˜K˜—š Ÿœœœ.œœ˜_Kšœ?˜?K˜—š Ÿ œœœ1œœ˜eKšœB˜BK˜—š Ÿœœœ.œœ˜_Kšœ@˜@K˜—š Ÿ œœœ"œœ˜UKšœD˜DK˜—˜K˜——šœ™K˜šœ3œ¤™ÝK˜—KšœÌ™ÌK™Kšœ œ˜K™š Ÿœœœœœ œ˜Ušœ&œœ˜:Kšœœœ˜2Kšœ˜—Kšœœ˜ K˜K˜—š Ÿ œœœœ œœ˜bšŸ œœœ˜8šœœ˜Kšœœœœ˜Kšœœ ˜'šœ˜ Kšœ'˜'Kš œœœœœ˜9Kšœ˜——Kšœ˜—Kšœ˜Kš œ œœœœ+˜MK˜K˜—šŸ œœœœœœ œ˜VKšœ9˜?Kšœ˜K˜—š Ÿ œœœœœœ˜cšœ<œœ˜PKš œœ'œœœ˜IKšœ˜—Kšœœ˜Kšœ˜K˜—šŸœœ œ œ˜gKšœœI˜]Kšœœ%˜0Kšœj˜jKšœ˜K˜—šŸ œœœœœ œ œ˜tKšœœ˜!Kš œœœœœ˜,Kš œœ œœœ˜7Kš œœœœœ œ˜Eš œœœœœ˜šœ ˜ Kšœœ˜!Kšœ/˜/Kšœ˜—Kšœ-˜-Kšœ˜—Kšœœ˜#K˜8Kšœ˜K˜—š Ÿœœœœœ˜tKšœœ˜!Kš œœœœœ˜,Kš œœ œœœ˜7Kšœœ"˜+K˜8Kšœ˜K˜—šŸœœœœ œ œ7œœ˜œKšœœ˜!Kš œœœœœ˜,Kš œœ œœœ˜7Kš œœœœœ œ˜EKšœ œœœ˜Kšœœ ˜š œœœœœ˜šœ#˜#Kšœœ˜!Kšœ œ"˜/Kš œœœ œœ˜(Kšœ/˜/Kš œœœœœ˜Ešœœœœ˜Kšœœœ œ(˜dKšœ˜Kšœ˜—Kšœ˜Kšœœœœ˜Kšœ˜—Kšœ0˜0Kšœ˜—Kšœœ '˜Kšœœœœ˜Kšœœ˜Kšœœœ˜#Kšœ?˜?Kšœ˜—Kšœ˜K˜——šœ™K˜š Ÿ œœœœœœ˜bKšœœ˜"Kšœœ˜&Kšœœœœ˜Kšœœœ˜,Kšœœœ  ˜7šœ œ˜$K˜9—K˜"K˜K˜!Kšœœœ1˜BKšœ˜K˜—šŸœœ œ˜>šœ œ˜šœœ˜!Kšœ<˜K˜Kšœœ˜#Kšœœ˜$Kšœ˜K˜——šœ™K˜š Ÿ œœœ!œœ#˜hKš œ œœœœœ˜AšŸœœ˜Kšœ œ ˜K˜1šœ˜Kšœ œ˜Kšœœœ˜Kš œœœœœ˜K˜-šœ˜šœ ˜ šœ œ˜K˜1šœœœ ˜K˜1Kš˜—Kšœ˜—Kšœ˜—šœ ˜ šœœœ ˜Kšœ1˜1Kšœ˜—Kšœ˜—šœ ˜ šœœœ ˜K˜1Kšœ˜—Kšœ˜—˜ Kšœœœ˜Kšœœ˜šœœœ ˜šœ˜#šœ˜Kšœ œœœ ˜8Kšœœ˜—šœ˜Kšœ œœœ ˜9Kšœœ˜—šœ˜ K˜ Kšœœ!˜3——Kš˜—Kšœ˜—Kšœœ˜—K˜KšœO˜OKšœ œœ˜-K˜!Kšœ˜—K˜K˜Kšœ˜—Kšœœœ˜%Kšœœ˜Kšœ œœ˜Kšœj˜jKšœ˜K˜——Kšœ˜—…—yp¦¼