TextBox.cs 36 KB

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