TextBox.cs 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132
  1. using Avalonia.Input.Platform;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reactive.Linq;
  6. using Avalonia.Controls.Presenters;
  7. using Avalonia.Controls.Primitives;
  8. using Avalonia.Controls.Utils;
  9. using Avalonia.Input;
  10. using Avalonia.Interactivity;
  11. using Avalonia.Media;
  12. using Avalonia.Metadata;
  13. using Avalonia.Data;
  14. using Avalonia.Layout;
  15. using Avalonia.Utilities;
  16. namespace Avalonia.Controls
  17. {
  18. public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
  19. {
  20. public static readonly StyledProperty<bool> AcceptsReturnProperty =
  21. AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsReturn));
  22. public static readonly StyledProperty<bool> AcceptsTabProperty =
  23. AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsTab));
  24. public static readonly DirectProperty<TextBox, int> CaretIndexProperty =
  25. AvaloniaProperty.RegisterDirect<TextBox, int>(
  26. nameof(CaretIndex),
  27. o => o.CaretIndex,
  28. (o, v) => o.CaretIndex = v);
  29. public static readonly StyledProperty<bool> IsReadOnlyProperty =
  30. AvaloniaProperty.Register<TextBox, bool>(nameof(IsReadOnly));
  31. public static readonly StyledProperty<char> PasswordCharProperty =
  32. AvaloniaProperty.Register<TextBox, char>(nameof(PasswordChar));
  33. public static readonly StyledProperty<IBrush> SelectionBrushProperty =
  34. AvaloniaProperty.Register<TextBox, IBrush>(nameof(SelectionBrushProperty));
  35. public static readonly StyledProperty<IBrush> SelectionForegroundBrushProperty =
  36. AvaloniaProperty.Register<TextBox, IBrush>(nameof(SelectionForegroundBrushProperty));
  37. public static readonly StyledProperty<IBrush> CaretBrushProperty =
  38. AvaloniaProperty.Register<TextBox, IBrush>(nameof(CaretBrushProperty));
  39. public static readonly DirectProperty<TextBox, int> SelectionStartProperty =
  40. AvaloniaProperty.RegisterDirect<TextBox, int>(
  41. nameof(SelectionStart),
  42. o => o.SelectionStart,
  43. (o, v) => o.SelectionStart = v);
  44. public static readonly DirectProperty<TextBox, int> SelectionEndProperty =
  45. AvaloniaProperty.RegisterDirect<TextBox, int>(
  46. nameof(SelectionEnd),
  47. o => o.SelectionEnd,
  48. (o, v) => o.SelectionEnd = v);
  49. public static readonly StyledProperty<int> MaxLengthProperty =
  50. AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0);
  51. public static readonly DirectProperty<TextBox, string> TextProperty =
  52. TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>(
  53. o => o.Text,
  54. (o, v) => o.Text = v,
  55. defaultBindingMode: BindingMode.TwoWay,
  56. enableDataValidation: true);
  57. public static readonly StyledProperty<TextAlignment> TextAlignmentProperty =
  58. TextBlock.TextAlignmentProperty.AddOwner<TextBox>();
  59. /// <summary>
  60. /// Defines the <see cref="HorizontalAlignment"/> property.
  61. /// </summary>
  62. public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
  63. ContentControl.HorizontalContentAlignmentProperty.AddOwner<TextBox>();
  64. /// <summary>
  65. /// Defines the <see cref="VerticalAlignment"/> property.
  66. /// </summary>
  67. public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
  68. ContentControl.VerticalContentAlignmentProperty.AddOwner<TextBox>();
  69. public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
  70. TextBlock.TextWrappingProperty.AddOwner<TextBox>();
  71. public static readonly StyledProperty<string> WatermarkProperty =
  72. AvaloniaProperty.Register<TextBox, string>(nameof(Watermark));
  73. public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
  74. AvaloniaProperty.Register<TextBox, bool>(nameof(UseFloatingWatermark));
  75. public static readonly DirectProperty<TextBox, string> NewLineProperty =
  76. AvaloniaProperty.RegisterDirect<TextBox, string>(nameof(NewLine),
  77. textbox => textbox.NewLine, (textbox, newline) => textbox.NewLine = newline);
  78. public static readonly StyledProperty<object> InnerLeftContentProperty =
  79. AvaloniaProperty.Register<TextBox, object>(nameof(InnerLeftContent));
  80. public static readonly StyledProperty<object> InnerRightContentProperty =
  81. AvaloniaProperty.Register<TextBox, object>(nameof(InnerRightContent));
  82. public static readonly StyledProperty<bool> RevealPasswordProperty =
  83. AvaloniaProperty.Register<TextBox, bool>(nameof(RevealPassword));
  84. struct UndoRedoState : IEquatable<UndoRedoState>
  85. {
  86. public string Text { get; }
  87. public int CaretPosition { get; }
  88. public UndoRedoState(string text, int caretPosition)
  89. {
  90. Text = text;
  91. CaretPosition = caretPosition;
  92. }
  93. public bool Equals(UndoRedoState other) => ReferenceEquals(Text, other.Text) || Equals(Text, other.Text);
  94. }
  95. private string _text;
  96. private int _caretIndex;
  97. private int _selectionStart;
  98. private int _selectionEnd;
  99. private TextPresenter _presenter;
  100. private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
  101. private bool _isUndoingRedoing;
  102. private bool _ignoreTextChanges;
  103. private string _newLine = Environment.NewLine;
  104. private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
  105. static TextBox()
  106. {
  107. FocusableProperty.OverrideDefaultValue(typeof(TextBox), true);
  108. }
  109. public TextBox()
  110. {
  111. var horizontalScrollBarVisibility = Observable.CombineLatest(
  112. this.GetObservable(AcceptsReturnProperty),
  113. this.GetObservable(TextWrappingProperty),
  114. (acceptsReturn, wrapping) =>
  115. {
  116. if (acceptsReturn)
  117. {
  118. return wrapping != TextWrapping.Wrap ?
  119. ScrollBarVisibility.Auto :
  120. ScrollBarVisibility.Disabled;
  121. }
  122. else
  123. {
  124. return ScrollBarVisibility.Hidden;
  125. }
  126. });
  127. this.Bind(
  128. ScrollViewer.HorizontalScrollBarVisibilityProperty,
  129. horizontalScrollBarVisibility,
  130. BindingPriority.Style);
  131. _undoRedoHelper = new UndoRedoHelper<UndoRedoState>(this);
  132. UpdatePseudoclasses();
  133. }
  134. public bool AcceptsReturn
  135. {
  136. get { return GetValue(AcceptsReturnProperty); }
  137. set { SetValue(AcceptsReturnProperty, value); }
  138. }
  139. public bool AcceptsTab
  140. {
  141. get { return GetValue(AcceptsTabProperty); }
  142. set { SetValue(AcceptsTabProperty, value); }
  143. }
  144. public int CaretIndex
  145. {
  146. get
  147. {
  148. return _caretIndex;
  149. }
  150. set
  151. {
  152. value = CoerceCaretIndex(value);
  153. SetAndRaise(CaretIndexProperty, ref _caretIndex, value);
  154. UndoRedoState state;
  155. if (_undoRedoHelper.TryGetLastState(out state) && state.Text == Text)
  156. _undoRedoHelper.UpdateLastState();
  157. }
  158. }
  159. public bool IsReadOnly
  160. {
  161. get { return GetValue(IsReadOnlyProperty); }
  162. set { SetValue(IsReadOnlyProperty, value); }
  163. }
  164. public char PasswordChar
  165. {
  166. get => GetValue(PasswordCharProperty);
  167. set => SetValue(PasswordCharProperty, value);
  168. }
  169. public IBrush SelectionBrush
  170. {
  171. get => GetValue(SelectionBrushProperty);
  172. set => SetValue(SelectionBrushProperty, value);
  173. }
  174. public IBrush SelectionForegroundBrush
  175. {
  176. get => GetValue(SelectionForegroundBrushProperty);
  177. set => SetValue(SelectionForegroundBrushProperty, value);
  178. }
  179. public IBrush CaretBrush
  180. {
  181. get => GetValue(CaretBrushProperty);
  182. set => SetValue(CaretBrushProperty, value);
  183. }
  184. public int SelectionStart
  185. {
  186. get
  187. {
  188. return _selectionStart;
  189. }
  190. set
  191. {
  192. value = CoerceCaretIndex(value);
  193. SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
  194. if (SelectionStart == SelectionEnd)
  195. {
  196. CaretIndex = SelectionStart;
  197. }
  198. }
  199. }
  200. public int SelectionEnd
  201. {
  202. get
  203. {
  204. return _selectionEnd;
  205. }
  206. set
  207. {
  208. value = CoerceCaretIndex(value);
  209. SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
  210. if (SelectionStart == SelectionEnd)
  211. {
  212. CaretIndex = SelectionEnd;
  213. }
  214. }
  215. }
  216. public int MaxLength
  217. {
  218. get { return GetValue(MaxLengthProperty); }
  219. set { SetValue(MaxLengthProperty, value); }
  220. }
  221. [Content]
  222. public string Text
  223. {
  224. get { return _text; }
  225. set
  226. {
  227. if (!_ignoreTextChanges)
  228. {
  229. var caretIndex = CaretIndex;
  230. SelectionStart = CoerceCaretIndex(SelectionStart, value);
  231. SelectionEnd = CoerceCaretIndex(SelectionEnd, value);
  232. CaretIndex = CoerceCaretIndex(caretIndex, value);
  233. if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing)
  234. {
  235. _undoRedoHelper.Clear();
  236. }
  237. }
  238. }
  239. }
  240. public string SelectedText
  241. {
  242. get { return GetSelection(); }
  243. set
  244. {
  245. _undoRedoHelper.Snapshot();
  246. if (string.IsNullOrEmpty(value))
  247. {
  248. DeleteSelection();
  249. }
  250. else
  251. {
  252. HandleTextInput(value);
  253. }
  254. _undoRedoHelper.Snapshot();
  255. }
  256. }
  257. /// <summary>
  258. /// Gets or sets the horizontal alignment of the content within the control.
  259. /// </summary>
  260. public HorizontalAlignment HorizontalContentAlignment
  261. {
  262. get { return GetValue(HorizontalContentAlignmentProperty); }
  263. set { SetValue(HorizontalContentAlignmentProperty, value); }
  264. }
  265. /// <summary>
  266. /// Gets or sets the vertical alignment of the content within the control.
  267. /// </summary>
  268. public VerticalAlignment VerticalContentAlignment
  269. {
  270. get { return GetValue(VerticalContentAlignmentProperty); }
  271. set { SetValue(VerticalContentAlignmentProperty, value); }
  272. }
  273. public TextAlignment TextAlignment
  274. {
  275. get { return GetValue(TextAlignmentProperty); }
  276. set { SetValue(TextAlignmentProperty, value); }
  277. }
  278. public string Watermark
  279. {
  280. get { return GetValue(WatermarkProperty); }
  281. set { SetValue(WatermarkProperty, value); }
  282. }
  283. public bool UseFloatingWatermark
  284. {
  285. get { return GetValue(UseFloatingWatermarkProperty); }
  286. set { SetValue(UseFloatingWatermarkProperty, value); }
  287. }
  288. public object InnerLeftContent
  289. {
  290. get { return GetValue(InnerLeftContentProperty); }
  291. set { SetValue(InnerLeftContentProperty, value); }
  292. }
  293. public object InnerRightContent
  294. {
  295. get { return GetValue(InnerRightContentProperty); }
  296. set { SetValue(InnerRightContentProperty, value); }
  297. }
  298. public bool RevealPassword
  299. {
  300. get { return GetValue(RevealPasswordProperty); }
  301. set { SetValue(RevealPasswordProperty, value); }
  302. }
  303. public TextWrapping TextWrapping
  304. {
  305. get { return GetValue(TextWrappingProperty); }
  306. set { SetValue(TextWrappingProperty, value); }
  307. }
  308. /// <summary>
  309. /// Gets or sets which characters are inserted when Enter is pressed. Default: <see cref="Environment.NewLine"/>
  310. /// </summary>
  311. public string NewLine
  312. {
  313. get { return _newLine; }
  314. set { SetAndRaise(NewLineProperty, ref _newLine, value); }
  315. }
  316. protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
  317. {
  318. _presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
  319. if (IsFocused)
  320. {
  321. _presenter?.ShowCaret();
  322. }
  323. }
  324. protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
  325. {
  326. base.OnPropertyChanged(change);
  327. if (change.Property == TextProperty)
  328. {
  329. UpdatePseudoclasses();
  330. }
  331. }
  332. protected override void OnGotFocus(GotFocusEventArgs e)
  333. {
  334. base.OnGotFocus(e);
  335. // when navigating to a textbox via the tab key, select all text if
  336. // 1) this textbox is *not* a multiline textbox
  337. // 2) this textbox has any text to select
  338. if (e.NavigationMethod == NavigationMethod.Tab &&
  339. !AcceptsReturn &&
  340. Text?.Length > 0)
  341. {
  342. SelectAll();
  343. }
  344. _presenter?.ShowCaret();
  345. }
  346. protected override void OnLostFocus(RoutedEventArgs e)
  347. {
  348. base.OnLostFocus(e);
  349. SelectionStart = 0;
  350. SelectionEnd = 0;
  351. _presenter?.HideCaret();
  352. }
  353. protected override void OnTextInput(TextInputEventArgs e)
  354. {
  355. if (!e.Handled)
  356. {
  357. HandleTextInput(e.Text);
  358. e.Handled = true;
  359. }
  360. }
  361. private void HandleTextInput(string input)
  362. {
  363. if (!IsReadOnly)
  364. {
  365. input = RemoveInvalidCharacters(input);
  366. string text = Text ?? string.Empty;
  367. int caretIndex = CaretIndex;
  368. if (!string.IsNullOrEmpty(input) && (MaxLength == 0 || input.Length + text.Length - (Math.Abs(SelectionStart - SelectionEnd)) <= MaxLength))
  369. {
  370. DeleteSelection();
  371. caretIndex = CaretIndex;
  372. text = Text ?? string.Empty;
  373. SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
  374. CaretIndex += input.Length;
  375. SelectionStart = SelectionEnd = CaretIndex;
  376. _undoRedoHelper.DiscardRedo();
  377. }
  378. }
  379. }
  380. public string RemoveInvalidCharacters(string text)
  381. {
  382. for (var i = 0; i < invalidCharacters.Length; i++)
  383. {
  384. text = text.Replace(invalidCharacters[i], string.Empty);
  385. }
  386. return text;
  387. }
  388. private async void Copy()
  389. {
  390. await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard)))
  391. .SetTextAsync(GetSelection());
  392. }
  393. private async void Paste()
  394. {
  395. var text = await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))).GetTextAsync();
  396. if (text == null)
  397. {
  398. return;
  399. }
  400. _undoRedoHelper.Snapshot();
  401. HandleTextInput(text);
  402. _undoRedoHelper.Snapshot();
  403. }
  404. protected override void OnKeyDown(KeyEventArgs e)
  405. {
  406. string text = Text ?? string.Empty;
  407. int caretIndex = CaretIndex;
  408. bool movement = false;
  409. bool selection = false;
  410. bool handled = false;
  411. var modifiers = e.KeyModifiers;
  412. var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
  413. bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
  414. bool DetectSelection() => e.KeyModifiers.HasFlag(keymap.SelectionModifiers);
  415. if (Match(keymap.SelectAll))
  416. {
  417. SelectAll();
  418. handled = true;
  419. }
  420. else if (Match(keymap.Copy))
  421. {
  422. if (!IsPasswordBox)
  423. {
  424. Copy();
  425. }
  426. handled = true;
  427. }
  428. else if (Match(keymap.Cut))
  429. {
  430. if (!IsPasswordBox)
  431. {
  432. _undoRedoHelper.Snapshot();
  433. Copy();
  434. DeleteSelection();
  435. _undoRedoHelper.Snapshot();
  436. }
  437. handled = true;
  438. }
  439. else if (Match(keymap.Paste))
  440. {
  441. Paste();
  442. handled = true;
  443. }
  444. else if (Match(keymap.Undo))
  445. {
  446. try
  447. {
  448. _isUndoingRedoing = true;
  449. _undoRedoHelper.Undo();
  450. }
  451. finally
  452. {
  453. _isUndoingRedoing = false;
  454. }
  455. handled = true;
  456. }
  457. else if (Match(keymap.Redo))
  458. {
  459. try
  460. {
  461. _isUndoingRedoing = true;
  462. _undoRedoHelper.Redo();
  463. }
  464. finally
  465. {
  466. _isUndoingRedoing = false;
  467. }
  468. handled = true;
  469. }
  470. else if (Match(keymap.MoveCursorToTheStartOfDocument))
  471. {
  472. MoveHome(true);
  473. movement = true;
  474. selection = false;
  475. handled = true;
  476. }
  477. else if (Match(keymap.MoveCursorToTheEndOfDocument))
  478. {
  479. MoveEnd(true);
  480. movement = true;
  481. selection = false;
  482. handled = true;
  483. }
  484. else if (Match(keymap.MoveCursorToTheStartOfLine))
  485. {
  486. MoveHome(false);
  487. movement = true;
  488. selection = false;
  489. handled = true;
  490. }
  491. else if (Match(keymap.MoveCursorToTheEndOfLine))
  492. {
  493. MoveEnd(false);
  494. movement = true;
  495. selection = false;
  496. handled = true;
  497. }
  498. else if (Match(keymap.MoveCursorToTheStartOfDocumentWithSelection))
  499. {
  500. MoveHome(true);
  501. movement = true;
  502. selection = true;
  503. handled = true;
  504. }
  505. else if (Match(keymap.MoveCursorToTheEndOfDocumentWithSelection))
  506. {
  507. MoveEnd(true);
  508. movement = true;
  509. selection = true;
  510. handled = true;
  511. }
  512. else if (Match(keymap.MoveCursorToTheStartOfLineWithSelection))
  513. {
  514. MoveHome(false);
  515. movement = true;
  516. selection = true;
  517. handled = true;
  518. }
  519. else if (Match(keymap.MoveCursorToTheEndOfLineWithSelection))
  520. {
  521. MoveEnd(false);
  522. movement = true;
  523. selection = true;
  524. handled = true;
  525. }
  526. else
  527. {
  528. bool hasWholeWordModifiers = modifiers.HasFlag(keymap.WholeWordTextActionModifiers);
  529. switch (e.Key)
  530. {
  531. case Key.Left:
  532. selection = DetectSelection();
  533. MoveHorizontal(-1, hasWholeWordModifiers, selection);
  534. movement = true;
  535. break;
  536. case Key.Right:
  537. selection = DetectSelection();
  538. MoveHorizontal(1, hasWholeWordModifiers, selection);
  539. movement = true;
  540. break;
  541. case Key.Up:
  542. movement = MoveVertical(-1);
  543. selection = DetectSelection();
  544. break;
  545. case Key.Down:
  546. movement = MoveVertical(1);
  547. selection = DetectSelection();
  548. break;
  549. case Key.Back:
  550. _undoRedoHelper.Snapshot();
  551. if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
  552. {
  553. SetSelectionForControlBackspace();
  554. }
  555. if (!DeleteSelection() && CaretIndex > 0)
  556. {
  557. var removedCharacters = 1;
  558. // handle deleting /r/n
  559. // you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
  560. // a /r should also be deleted.
  561. if (CaretIndex > 1 &&
  562. text[CaretIndex - 1] == '\n' &&
  563. text[CaretIndex - 2] == '\r')
  564. {
  565. removedCharacters = 2;
  566. }
  567. SetTextInternal(text.Substring(0, caretIndex - removedCharacters) +
  568. text.Substring(caretIndex));
  569. CaretIndex -= removedCharacters;
  570. SelectionStart = SelectionEnd = CaretIndex;
  571. }
  572. _undoRedoHelper.Snapshot();
  573. handled = true;
  574. break;
  575. case Key.Delete:
  576. _undoRedoHelper.Snapshot();
  577. if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
  578. {
  579. SetSelectionForControlDelete();
  580. }
  581. if (!DeleteSelection() && caretIndex < text.Length)
  582. {
  583. var removedCharacters = 1;
  584. // handle deleting /r/n
  585. // you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
  586. // a /r should also be deleted.
  587. if (CaretIndex < text.Length - 1 &&
  588. text[caretIndex + 1] == '\n' &&
  589. text[caretIndex] == '\r')
  590. {
  591. removedCharacters = 2;
  592. }
  593. SetTextInternal(text.Substring(0, caretIndex) +
  594. text.Substring(caretIndex + removedCharacters));
  595. }
  596. _undoRedoHelper.Snapshot();
  597. handled = true;
  598. break;
  599. case Key.Enter:
  600. if (AcceptsReturn)
  601. {
  602. _undoRedoHelper.Snapshot();
  603. HandleTextInput(NewLine);
  604. _undoRedoHelper.Snapshot();
  605. handled = true;
  606. }
  607. break;
  608. case Key.Tab:
  609. if (AcceptsTab)
  610. {
  611. _undoRedoHelper.Snapshot();
  612. HandleTextInput("\t");
  613. _undoRedoHelper.Snapshot();
  614. handled = true;
  615. }
  616. else
  617. {
  618. base.OnKeyDown(e);
  619. }
  620. break;
  621. default:
  622. handled = false;
  623. break;
  624. }
  625. }
  626. if (movement && selection)
  627. {
  628. SelectionEnd = CaretIndex;
  629. }
  630. else if (movement)
  631. {
  632. SelectionStart = SelectionEnd = CaretIndex;
  633. }
  634. if (handled || movement)
  635. {
  636. e.Handled = true;
  637. }
  638. }
  639. protected override void OnPointerPressed(PointerPressedEventArgs e)
  640. {
  641. var text = Text;
  642. var clickInfo = e.GetCurrentPoint(this);
  643. if (text != null && clickInfo.Properties.IsLeftButtonPressed && !(clickInfo.Pointer?.Captured is Border))
  644. {
  645. var point = e.GetPosition(_presenter);
  646. var index = CaretIndex = _presenter.GetCaretIndex(point);
  647. switch (e.ClickCount)
  648. {
  649. case 1:
  650. SelectionStart = SelectionEnd = index;
  651. break;
  652. case 2:
  653. if (!StringUtils.IsStartOfWord(text, index))
  654. {
  655. SelectionStart = StringUtils.PreviousWord(text, index);
  656. }
  657. SelectionEnd = StringUtils.NextWord(text, index);
  658. break;
  659. case 3:
  660. SelectAll();
  661. break;
  662. }
  663. }
  664. e.Pointer.Capture(_presenter);
  665. e.Handled = true;
  666. }
  667. protected override void OnPointerMoved(PointerEventArgs e)
  668. {
  669. // selection should not change during pointer move if the user right clicks
  670. if (_presenter != null && e.Pointer.Captured == _presenter && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
  671. {
  672. var point = e.GetPosition(_presenter);
  673. point = new Point(MathUtilities.Clamp(point.X, 0, _presenter.Bounds.Width - 1), MathUtilities.Clamp(point.Y, 0, _presenter.Bounds.Height - 1));
  674. CaretIndex = SelectionEnd = _presenter.GetCaretIndex(point);
  675. }
  676. }
  677. protected override void OnPointerReleased(PointerReleasedEventArgs e)
  678. {
  679. if (_presenter != null && e.Pointer.Captured == _presenter)
  680. {
  681. if (e.InitialPressMouseButton == MouseButton.Right)
  682. {
  683. var point = e.GetPosition(_presenter);
  684. var caretIndex = _presenter.GetCaretIndex(point);
  685. // see if mouse clicked inside current selection
  686. // if it did not, we change the selection to where the user clicked
  687. var firstSelection = Math.Min(SelectionStart, SelectionEnd);
  688. var lastSelection = Math.Max(SelectionStart, SelectionEnd);
  689. var didClickInSelection = SelectionStart != SelectionEnd &&
  690. caretIndex >= firstSelection && caretIndex <= lastSelection;
  691. if (!didClickInSelection)
  692. {
  693. CaretIndex = SelectionEnd = SelectionStart = caretIndex;
  694. }
  695. }
  696. e.Pointer.Capture(null);
  697. }
  698. }
  699. protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
  700. {
  701. if (property == TextProperty)
  702. {
  703. DataValidationErrors.SetError(this, value.Error);
  704. }
  705. }
  706. private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text);
  707. private int CoerceCaretIndex(int value, string text)
  708. {
  709. if (text == null)
  710. {
  711. return 0;
  712. }
  713. var length = text.Length;
  714. if (value < 0)
  715. {
  716. return 0;
  717. }
  718. else if (value > length)
  719. {
  720. return length;
  721. }
  722. else if (value > 0 && text[value - 1] == '\r' && value < length && text[value] == '\n')
  723. {
  724. return value + 1;
  725. }
  726. else
  727. {
  728. return value;
  729. }
  730. }
  731. public void Clear()
  732. {
  733. Text = string.Empty;
  734. }
  735. private int DeleteCharacter(int index)
  736. {
  737. var start = index + 1;
  738. var text = Text;
  739. var c = text[index];
  740. var result = 1;
  741. if (c == '\n' && index > 0 && text[index - 1] == '\r')
  742. {
  743. --index;
  744. ++result;
  745. }
  746. else if (c == '\r' && index < text.Length - 1 && text[index + 1] == '\n')
  747. {
  748. ++start;
  749. ++result;
  750. }
  751. Text = text.Substring(0, index) + text.Substring(start);
  752. return result;
  753. }
  754. private void MoveHorizontal(int direction, bool wholeWord, bool isSelecting)
  755. {
  756. var text = Text ?? string.Empty;
  757. var caretIndex = CaretIndex;
  758. if (!wholeWord)
  759. {
  760. if (SelectionStart != SelectionEnd && !isSelecting)
  761. {
  762. var start = Math.Min(SelectionStart, SelectionEnd);
  763. var end = Math.Max(SelectionStart, SelectionEnd);
  764. CaretIndex = direction < 0 ? start : end;
  765. return;
  766. }
  767. var index = caretIndex + direction;
  768. if (index < 0 || index > text.Length)
  769. {
  770. return;
  771. }
  772. else if (index == text.Length)
  773. {
  774. CaretIndex = index;
  775. return;
  776. }
  777. var c = text[index];
  778. if (direction > 0)
  779. {
  780. CaretIndex += (c == '\r' && index < text.Length - 1 && text[index + 1] == '\n') ? 2 : 1;
  781. }
  782. else
  783. {
  784. CaretIndex -= (c == '\n' && index > 0 && text[index - 1] == '\r') ? 2 : 1;
  785. }
  786. }
  787. else
  788. {
  789. if (direction > 0)
  790. {
  791. CaretIndex += StringUtils.NextWord(text, caretIndex) - caretIndex;
  792. }
  793. else
  794. {
  795. CaretIndex += StringUtils.PreviousWord(text, caretIndex) - caretIndex;
  796. }
  797. }
  798. }
  799. private bool MoveVertical(int count)
  800. {
  801. var formattedText = _presenter.FormattedText;
  802. var lines = formattedText.GetLines().ToList();
  803. var caretIndex = CaretIndex;
  804. var lineIndex = GetLine(caretIndex, lines) + count;
  805. if (lineIndex >= 0 && lineIndex < lines.Count)
  806. {
  807. var line = lines[lineIndex];
  808. var rect = formattedText.HitTestTextPosition(caretIndex);
  809. var y = count < 0 ? rect.Y : rect.Bottom;
  810. var point = new Point(rect.X, y + (count * (line.Height / 2)));
  811. var hit = formattedText.HitTestPoint(point);
  812. CaretIndex = hit.TextPosition + (hit.IsTrailing ? 1 : 0);
  813. return true;
  814. }
  815. else
  816. {
  817. return false;
  818. }
  819. }
  820. private void MoveHome(bool document)
  821. {
  822. var text = Text ?? string.Empty;
  823. var caretIndex = CaretIndex;
  824. if (document)
  825. {
  826. caretIndex = 0;
  827. }
  828. else
  829. {
  830. var lines = _presenter.FormattedText.GetLines();
  831. var pos = 0;
  832. foreach (var line in lines)
  833. {
  834. if (pos + line.Length > caretIndex || pos + line.Length == text.Length)
  835. {
  836. break;
  837. }
  838. pos += line.Length;
  839. }
  840. caretIndex = pos;
  841. }
  842. CaretIndex = caretIndex;
  843. }
  844. private void MoveEnd(bool document)
  845. {
  846. var text = Text ?? string.Empty;
  847. var caretIndex = CaretIndex;
  848. if (document)
  849. {
  850. caretIndex = text.Length;
  851. }
  852. else
  853. {
  854. var lines = _presenter.FormattedText.GetLines();
  855. var pos = 0;
  856. foreach (var line in lines)
  857. {
  858. pos += line.Length;
  859. if (pos > caretIndex)
  860. {
  861. if (pos < text.Length)
  862. {
  863. --pos;
  864. if (pos > 0 && text[pos - 1] == '\r' && text[pos] == '\n')
  865. {
  866. --pos;
  867. }
  868. }
  869. break;
  870. }
  871. }
  872. caretIndex = pos;
  873. }
  874. CaretIndex = caretIndex;
  875. }
  876. /// <summary>
  877. /// Select all text in the TextBox
  878. /// </summary>
  879. public void SelectAll()
  880. {
  881. SelectionStart = 0;
  882. SelectionEnd = Text?.Length ?? 0;
  883. CaretIndex = SelectionEnd;
  884. }
  885. private bool DeleteSelection()
  886. {
  887. if (!IsReadOnly)
  888. {
  889. var selectionStart = SelectionStart;
  890. var selectionEnd = SelectionEnd;
  891. if (selectionStart != selectionEnd)
  892. {
  893. var start = Math.Min(selectionStart, selectionEnd);
  894. var end = Math.Max(selectionStart, selectionEnd);
  895. var text = Text;
  896. SetTextInternal(text.Substring(0, start) + text.Substring(end));
  897. SelectionStart = SelectionEnd = CaretIndex = start;
  898. return true;
  899. }
  900. else
  901. {
  902. return false;
  903. }
  904. }
  905. else
  906. {
  907. return true;
  908. }
  909. }
  910. private string GetSelection()
  911. {
  912. var text = Text;
  913. if (string.IsNullOrEmpty(text))
  914. return "";
  915. var selectionStart = SelectionStart;
  916. var selectionEnd = SelectionEnd;
  917. var start = Math.Min(selectionStart, selectionEnd);
  918. var end = Math.Max(selectionStart, selectionEnd);
  919. if (start == end || (Text?.Length ?? 0) < end)
  920. {
  921. return "";
  922. }
  923. return text.Substring(start, end - start);
  924. }
  925. private int GetLine(int caretIndex, IList<FormattedTextLine> lines)
  926. {
  927. int pos = 0;
  928. int i;
  929. for (i = 0; i < lines.Count - 1; ++i)
  930. {
  931. var line = lines[i];
  932. pos += line.Length;
  933. if (pos > caretIndex)
  934. {
  935. break;
  936. }
  937. }
  938. return i;
  939. }
  940. private void SetTextInternal(string value)
  941. {
  942. try
  943. {
  944. _ignoreTextChanges = true;
  945. SetAndRaise(TextProperty, ref _text, value);
  946. }
  947. finally
  948. {
  949. _ignoreTextChanges = false;
  950. }
  951. }
  952. private void SetSelectionForControlBackspace()
  953. {
  954. SelectionStart = CaretIndex;
  955. MoveHorizontal(-1, true, false);
  956. SelectionEnd = CaretIndex;
  957. }
  958. private void SetSelectionForControlDelete()
  959. {
  960. SelectionStart = CaretIndex;
  961. MoveHorizontal(1, true, false);
  962. SelectionEnd = CaretIndex;
  963. }
  964. private void UpdatePseudoclasses()
  965. {
  966. PseudoClasses.Set(":empty", string.IsNullOrWhiteSpace(Text));
  967. }
  968. private bool IsPasswordBox => PasswordChar != default(char);
  969. UndoRedoState UndoRedoHelper<UndoRedoState>.IUndoRedoHost.UndoRedoState
  970. {
  971. get { return new UndoRedoState(Text, CaretIndex); }
  972. set
  973. {
  974. Text = value.Text;
  975. SelectionStart = SelectionEnd = CaretIndex = value.CaretPosition;
  976. }
  977. }
  978. }
  979. }