<* PRAGMA LL *> MODULE********************** Reading ***************************; IMPORT ASCII, KeyFilter, MTextUnit, PaintOp, Text, TextPort, TextPortClass, VBT, VTDef, VText; FROM TextPortClass IMPORT IRange; REVEAL T = TextPortClass.Model BRANDED OBJECT dragButton : VBT.Button; clipboard := ""; sourceInClipboard := TRUE; OVERRIDES controlChord := ControlChord; copy := Copy; highlight := Highlight; init := Init; misc := Misc; mouse := Mouse; optionChord := OptionChord; position := Position; read := Read; write := Write; END; CONST Primary = TextPort.SelectionType.Primary; Secondary = TextPort.SelectionType.Secondary; Source = TextPortClass.VType.Source; Target = TextPortClass.VType.Target; VAR SwapHighlights := VBT.GetMiscCodeType ("SwapHighlights"); IvyMove := VBT.GetMiscCodeType ("IvyMove"); PROCEDURE IvyModel Init (m: T; colorScheme: PaintOp.ColorScheme; keyfilter: KeyFilter.T): TextPortClass.Model = BEGIN TRY m.selection [Primary] := NEW ( TextPortClass.SelectionRecord, type := Primary, alias := VBT.Target, interval := VText.CreateInterval ( vtext := m.v.vtext, indexL := 0, indexR := 0, options := VText.MakeIntervalOptions ( style := VText.IntervalStyle.NoStyle, whiteBlack := colorScheme, whiteStroke := colorScheme, leading := colorScheme.bg))); m.selection [Secondary] := NEW (TextPortClass.SelectionRecord, type := Secondary, alias := VBT.Source, interval := VText.CreateInterval ( vtext := m.v.vtext, indexL := 0, indexR := 0, options := VText.MakeIntervalOptions ( style := VText.IntervalStyle.NoStyle, whiteBlack := colorScheme, whiteStroke := colorScheme, leading := colorScheme.bg))); m.keyfilter := NEW (TextPortClass.Composer, next := keyfilter) EXCEPT | VTDef.Error (ec) => m.v.vterror ("Model Init", ec) END; RETURN m END Init; PROCEDUREControlChord (m: T; ch: CHAR; READONLY cd: VBT.KeyRec) = VAR time := cd.time; v := m.v; PROCEDURE delete (READONLY e: TextPort.Extent) = BEGIN IF e # TextPort.NotFound THEN m.select (time, e.l, e.r) END END delete; PROCEDURE toWord (READONLY e: TextPort.Extent; forward := TRUE) = BEGIN IF e # TextPort.NotFound THEN m.select (time, e.l, e.r, Primary, caretEnd := VAL (ORD (forward), VText.WhichEnd)) END END toWord; BEGIN CASE ch OF | ' ' => (* Just normalize. *) | ',' => find (m, time, Secondary, TextPortClass.Loc.Next) | ';' => ToEndOfLine (m, time) | 'a' => delete (TextPortClass.DeletePrevChar (v)) | 'b' => delete (TextPortClass.DeleteCurrentLine (v)) | 'c' => delete (TextPortClass.DeleteToStartOfLine (v)) | 'd' => delete (TextPortClass.DeleteToStartOfWord (v)) | 'e' => Move (m, time, MoveAndClear) | 'f' => delete (TextPortClass.DeleteToEndOfWord (v)) | 'g' => delete (TextPortClass.DeleteCurrentWord (v)) | 'h' => ExchangeSelections (m, time); | 'i' => toWord (TextPortClass.FindNextWord (v)) | 'j' => TextPortClass.ToPrevChar (v); sci (m, time) | 'k' => TextPortClass.ToNextChar (v); sci (m, time) | 'l' => ToStartOfLine (m, time) | 'm' => find (m, time, Secondary, TextPortClass.Loc.Prev) | 'n' => find (m, time, Primary, TextPortClass.Loc.Next) | 'o' => TextPortClass.UpOneLine (v); sci (m, time) | 'p' => TextPortClass.DownOneLine (v); sci (m, time) | 'q' => m.clear () | 'r' => Swap (m, time) | 's' => delete (TextPortClass.DeleteNextChar (v)) | 'u' => toWord (TextPortClass.FindPrevWord (v), FALSE) | 'v' => delete (TextPortClass.DeleteToEndOfLine (v)) | 'w' => Move (m, time, JustMove) | 'y' => TextPortClass.ToOtherEnd (v); sci (m, time) | 'z' => TextPortClass.Undo (v) | 'Z' => TextPortClass.Redo (v) ELSE (* Don't normalize if unknown chord, including just ctrl itself. *) RETURN END; m.v.normalize (-1) END ControlChord; PROCEDUREOptionChord (m: T; ch: CHAR; READONLY cd: VBT.KeyRec) = VAR time := cd.time; v := m.v; BEGIN CASE ch OF | 'c' => m.copy (time) | 'm' => find (m, time, Primary, TextPortClass.Loc.Prev) | 'n' => find (m, time, Primary, TextPortClass.Loc.First) | 'v' => m.paste (time) | 'x' => m.cut (time) | ASCII.BS, ASCII.DEL => TextPortClass.SwapChars (v); WITH index = m.v.index () DO m.select (time, index - 2, index) END | ASCII.NL => TextPortClass.InsertNewline (v); sci (m, time) ELSE (* Don't normalize if unknown chord, including just option itself. *) RETURN END; m.v.normalize (-1) END OptionChord; PROCEDURECopy (m: T; time: VBT.TimeStamp) = CONST name = "Copy"; VAR t := m.getSelectedText (Primary); BEGIN IF NOT Text.Empty (t) AND m.takeSelection (VBT.Source, Secondary, time) THEN m.clipboard := t; m.sourceInClipboard := TRUE; TRY VText.SwitchInterval ( m.selection [Secondary].interval, VText.OnOffState.Off) EXCEPT | VTDef.Error (ec) => m.v.vterror (name, ec) END END END Copy; PROCEDUREsci (m: T; time: VBT.TimeStamp) = (* Select Current Index *) BEGIN WITH index = m.v.index () DO m.select (time, index, index) END END sci; PROCEDUREfind (m : T; time: VBT.TimeStamp; type: TextPort.SelectionType; loc : TextPortClass.Loc ) = BEGIN IF type = Primary THEN m.copy (time) END; m.v.findSource (time, loc) END find; PROCEDUREExchangeSelections (m: T; time: VBT.TimeStamp) = CONST name = "Exchange"; VAR primary := m.selection [Primary]; intvl_1 := primary.interval; secondary := m.selection [Secondary]; intvl_2 := secondary.interval; pLeft := intvl_1.left (); pRight := intvl_1.right (); sLeft := intvl_2.left (); sRight := intvl_2.right (); BEGIN TRY IF NOT m.v.owns [Target] THEN RETURN END; (* This VBT owns the primary. Does it own both? *) IF m.v.owns [Source] THEN IF m.v.index () = pLeft THEN m.highlight (primary, IRange {sLeft, sLeft, sRight}) ELSE m.highlight (primary, IRange {sLeft, sRight, sRight}) END ELSE (* This is more complex. Must tell the owner of the secondary that this is going to be a swap, and then grab the secondary. *) VBT.Put (m.v, VBT.Source, time, SwapHighlights); VText.SwitchInterval (intvl_1, VText.OnOffState.Off); EVAL m.takeSelection (VBT.Source, Secondary, time) END; (* Now, move the secondary selection to what used to be the primary interval. *) m.highlight (secondary, IRange {pLeft, pRight, pRight}) EXCEPT | VTDef.Error (ec) => m.v.vterror (name, ec) | VBT.Error (ec) => m.v.vbterror (name, ec) END END ExchangeSelections; PROCEDUREMouse (m: T; READONLY cd: VBT.MouseRec) = CONST name = "Mouse"; VAR type: TextPort.SelectionType; BEGIN IF (VBT.Modifier.Control IN cd.modifiers) OR (VBT.Modifier.Shift IN cd.modifiers) THEN type := Secondary ELSE type := Primary END; IF type = Primary AND NOT m.v.getKFocus(cd.time) THEN RETURN END; VAR sel := Map[type]; rec := m.selection[type]; interval := rec.interval; r : TextPortClass.IRange; BEGIN TRY CASE cd.clickType OF | VBT.ClickType.FirstDown => IF m.takeSelection(sel, type, cd.time) THEN IF type = Secondary THEN m.sourceInClipboard := FALSE END; CASE cd.whatChanged OF | VBT.Modifier.MouseL, VBT.Modifier.MouseM => rec.mode := selectionModes[ cd.whatChanged, MIN(cd.clickCount, 4)]; IF type = Primary THEN rec.replaceMode := FALSE END; TextPortClass.ChangeIntervalOptions(m.v, rec); r := TextPortClass.GetRange(m.v, cd.cp, rec.mode); (* Select only the point between characters. *) IF rec.mode = VText.SelectionMode.CharSelection THEN r.left := r.middle; r.right := r.middle END; rec.anchor.l := r.left; rec.anchor.r := r.right; m.highlight(rec, r) | VBT.Modifier.MouseR => IF cd.clickCount < 2 THEN (* single click *) IF interval.left() >= m.v.typeinStart THEN IF type = Primary THEN rec.replaceMode := NOT m.v.readOnly END; TextPortClass.ChangeIntervalOptions( m.v, rec) END ELSIF rec.mode > FIRST(VText.SelectionMode) THEN (* multi-clicking: make the selection-mode smaller. *) DEC(rec.mode) END; r := TextPortClass.GetRange(m.v, cd.cp, rec.mode); m.approachingFromLeft := r.left < (rec.anchor.l + rec.anchor.r) DIV 2; m.extend(rec, r.left, r.right) ELSE END; m.dragButton := cd.whatChanged; m.dragType := type; m.dragging := TRUE END | VBT.ClickType.LastUp => IF m.dragging THEN rec.anchor.l := rec.interval.left(); rec.anchor.r := rec.interval.right(); m.dragging := FALSE END ELSE m.dragging := FALSE END EXCEPT | VTDef.Error (ec) => m.v.vterror(name, ec) END END END Mouse; PROCEDUREPosition (m: T; READONLY cd: VBT.PositionRec) = VAR rec := m.selection [m.dragType]; r := TextPortClass.GetRange (m.v, cd.cp, rec.mode); BEGIN CASE m.dragButton OF | VBT.Modifier.MouseL, VBT.Modifier.MouseM => m.highlight (rec, r) | VBT.Modifier.MouseR => m.extend (rec, r.left, r.right) ELSE END END Position;
PROCEDURE********************** Writing ***************************Read (m: T; READONLY s: VBT.Selection; time: VBT.TimeStamp): TEXT RAISES {VBT.Error} = BEGIN IF s = VBT.Source AND m.v.owns [Source] THEN IF m.sourceInClipboard THEN RETURN m.clipboard ELSE RETURN m.getSelectedText (Secondary) END ELSIF s = VBT.Target AND m.v.owns [Target] THEN RETURN m.getSelectedText (Primary) ELSE RETURN TextPortClass.Model.read (m, s, time) END END Read;
PROCEDURE**************** Other things ************************Write (m: T; READONLY s: VBT.Selection; time: VBT.TimeStamp; t: TEXT) RAISES {VBT.Error} = PROCEDURE write (t: TEXT; type: TextPort.SelectionType) RAISES {VBT.Error} = BEGIN IF m.selection [type].interval.left () < m.v.typeinStart THEN RAISE VBT.Error (VBT.ErrorCode.Unwritable) ELSE m.putSelectedText (t, type) END END write; BEGIN IF s = VBT.Source AND m.v.owns [Source] THEN IF m.sourceInClipboard THEN m.clipboard := t ELSE write (t, Secondary) END ELSIF s = VBT.Target AND m.v.owns [Target] THEN write (t, Primary) ELSE TextPortClass.Model.write (m, s, time, t) END END Write;
PROCEDUREMisc (m: T; READONLY cd: VBT.MiscRec) = BEGIN IF cd.type = SwapHighlights THEN (* We owned Source, so grab Target and put Source's highlights on it. *) VAR i := m.selection [Secondary].interval; left := i.left (); right := i.right (); BEGIN IF m.takeSelection (VBT.Target, Primary, cd.time) THEN m.highlight (m.selection [Primary], IRange {left, right, right}) END END ELSIF cd.type = IvyMove THEN Move (m, cd.time, cd.detail) END; TextPortClass.Model.misc (m, cd) END Misc; PROCEDUREToStartOfLine (m: T; time: VBT.TimeStamp) = VAR index := m.v.index (); mtext := m.v.vtext.mtext; line := MTextUnit.LineInfo (mtext, index); x := m.selection [Primary].interval; BEGIN IF x.left () = line.left AND x.right () = line.right AND index = line.left AND line.left > 0 THEN line := MTextUnit.LineInfo (mtext, line.left - 1) END; m.select (time, line.left, line.right, caretEnd := VText.WhichEnd.Left) END ToStartOfLine; PROCEDUREToEndOfLine (m: T; time: VBT.TimeStamp) = VAR index := m.v.index (); mtext := m.v.vtext.mtext; line := MTextUnit.LineInfo (mtext, index); x := m.selection [Primary].interval; BEGIN IF x.left () = line.left AND x.right () = line.rightEnd AND index = line.rightEnd AND line.rightEnd < m.v.length () THEN line := MTextUnit.LineInfo (mtext, line.right) END; m.select (time, line.left, line.rightEnd) END ToEndOfLine; PROCEDURESwap (m: T; time: VBT.TimeStamp) = BEGIN TRY WITH primaryValue = m.read (VBT.Target, time), secondaryValue = m.read (VBT.Source, time) DO m.write (VBT.Target, time, secondaryValue); m.write (VBT.Source, time, primaryValue) END EXCEPT | VBT.Error (ec) => m.v.vbterror ("Swap", ec) END END Swap; CONST MoveAndClear = VBT.MiscCodeDetail {0, 1}; JustMove = VBT.NullDetail; PROCEDUREMove (m: T; time: VBT.TimeStamp; READONLY detail: VBT.MiscCodeDetail) = VAR v := m.v; BEGIN TRY IF v.owns [Target] THEN VAR rec := m.selection [Primary]; text := m.read (VBT.Source, time); p := rec.cursor; BEGIN IF v.isReplaceMode () THEN m.putSelectedText (text, Primary) ELSIF v.replace (p, p, text) # TextPort.NotFound THEN INC (p, Text.Length (text)); m.highlight (rec, IRange {p, p, p}) END; IF detail = MoveAndClear THEN m.write (VBT.Source, time, "") END END ELSE VBT.Put (v, VBT.Target, time, IvyMove, detail) END EXCEPT | VBT.Error (ec) => v.vbterror ("Move", ec) END END Move; PROCEDUREHighlight (m : T; rec: TextPortClass.SelectionRecord; READONLY r: IRange) = (* The only difference in this version is the test for rec.type. *) CONST name = "Highlight"; BEGIN TRY VText.MoveInterval (rec.interval, r.left, r.right); VText.SwitchInterval (rec.interval, VText.OnOffState.On); IF rec.type = Primary THEN rec.cursor := r.middle; m.seek (r.middle) END; VBT.Mark (m.v) EXCEPT | VTDef.Error (ec) => m.v.vterror (name, ec) END END Highlight; VAR Map := ARRAY TextPort.SelectionType OF VBT.Selection {VBT.Target, VBT.Source}; VAR selectionModes: ARRAY [VBT.Modifier.MouseL .. VBT.Modifier.MouseR], [0 .. 4] OF VText.SelectionMode; BEGIN selectionModes [VBT.Modifier.MouseL, 0] := VText.SelectionMode.CharSelection; selectionModes [VBT.Modifier.MouseL, 1] := VText.SelectionMode.CharSelection; selectionModes [VBT.Modifier.MouseL, 2] := VText.SelectionMode.LineSelection; selectionModes [VBT.Modifier.MouseL, 3] := VText.SelectionMode.LineSelection; selectionModes [VBT.Modifier.MouseL, 4] := VText.SelectionMode.AllSelection; selectionModes [VBT.Modifier.MouseM, 0] := VText.SelectionMode.WordSelection; selectionModes [VBT.Modifier.MouseM, 1] := VText.SelectionMode.WordSelection; selectionModes [VBT.Modifier.MouseM, 2] := VText.SelectionMode.ParagraphSelection; selectionModes [VBT.Modifier.MouseM, 3] := VText.SelectionMode.ParagraphSelection; selectionModes [VBT.Modifier.MouseM, 4] := VText.SelectionMode.AllSelection; END IvyModel.