[WinForms] Display caret while click on ComboBox (#18523)
[mono-project.git] / mcs / class / System.Windows.Forms / System.Windows.Forms / TextBoxBase.cs
bloba2cc8560ffdc6b5eee0509e4d8be439bf879e9ef
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:c
8 //
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)
22 // Authors:
23 // Peter Bartok pbartok@novell.com
27 // NOT COMPLETE
28 #undef Debug
29 #undef DebugClick
31 using System.ComponentModel;
32 using System.ComponentModel.Design;
33 using System.Drawing;
34 using System.Drawing.Text;
35 using System.Text;
36 using System.Runtime.InteropServices;
37 using System.Collections;
39 namespace System.Windows.Forms
41 [ComVisible (true)]
42 [DefaultBindingProperty ("Text")]
43 [ClassInterface (ClassInterfaceType.AutoDispatch)]
44 [DefaultEvent("TextChanged")]
45 [Designer("System.Windows.Forms.Design.TextBoxBaseDesigner, " + Consts.AssemblySystem_Design)]
46 public abstract class TextBoxBase : Control
48 #region Local Variables
49 internal HorizontalAlignment alignment;
50 internal bool accepts_tab;
51 internal bool accepts_return;
52 internal bool auto_size;
53 internal bool backcolor_set;
54 internal CharacterCasing character_casing;
55 internal bool hide_selection;
56 int max_length;
57 internal bool modified;
58 internal char password_char;
59 internal bool read_only;
60 internal bool word_wrap;
61 internal Document document;
62 internal LineTag caret_tag; // tag our cursor is in
63 internal int caret_pos; // position on the line our cursor is in (can be 0 = beginning of line)
64 internal ImplicitHScrollBar hscroll;
65 internal ImplicitVScrollBar vscroll;
66 internal RichTextBoxScrollBars scrollbars;
67 internal Timer scroll_timer;
68 internal bool richtext;
69 internal bool show_selection; // set to true to always show selection, even if no focus is set
70 internal ArrayList list_links; // currently showing links
71 private LinkRectangle current_link; // currently hovering link
72 private bool enable_links; // whether links are enabled
74 internal bool has_been_focused;
76 private bool delayed_font_or_color_change;
77 private int requested_height;
79 internal int selection_length = -1; // set to the user-specified selection length, or -1 if none
80 internal bool show_caret_w_selection; // TextBox shows the caret when the selection is visible
81 internal int canvas_width;
82 internal int canvas_height;
83 static internal int track_width = 2; //
84 static internal int track_border = 5; //
85 internal DateTime click_last;
86 internal int click_point_x;
87 internal int click_point_y;
88 internal CaretSelection click_mode;
89 internal BorderStyle actual_border_style = BorderStyle.Fixed3D;
90 internal bool shortcuts_enabled = true;
91 #if Debug
92 internal static bool draw_lines = false;
93 #endif
95 #endregion // Local Variables
97 #region Internal Constructor
98 // Constructor will go when complete, only for testing - pdb
99 internal TextBoxBase ()
101 alignment = HorizontalAlignment.Left;
102 accepts_return = false;
103 accepts_tab = false;
104 auto_size = true;
105 InternalBorderStyle = BorderStyle.Fixed3D;
106 character_casing = CharacterCasing.Normal;
107 hide_selection = true;
108 max_length = short.MaxValue;
109 password_char = '\0';
110 read_only = false;
111 word_wrap = true;
112 richtext = false;
113 show_selection = false;
114 enable_links = false;
115 list_links = new ArrayList ();
116 current_link = null;
117 show_caret_w_selection = (this is TextBox);
118 document = new Document(this);
119 document.WidthChanged += new EventHandler(document_WidthChanged);
120 document.HeightChanged += new EventHandler(document_HeightChanged);
121 //document.CaretMoved += new EventHandler(CaretMoved);
122 document.Wrap = false;
123 click_last = DateTime.Now;
124 click_mode = CaretSelection.Position;
126 MouseDown += new MouseEventHandler(TextBoxBase_MouseDown);
127 MouseUp += new MouseEventHandler(TextBoxBase_MouseUp);
128 MouseMove += new MouseEventHandler(TextBoxBase_MouseMove);
129 SizeChanged += new EventHandler(TextBoxBase_SizeChanged);
130 FontChanged += new EventHandler(TextBoxBase_FontOrColorChanged);
131 ForeColorChanged += new EventHandler(TextBoxBase_FontOrColorChanged);
132 MouseWheel += new MouseEventHandler(TextBoxBase_MouseWheel);
133 RightToLeftChanged += new EventHandler (TextBoxBase_RightToLeftChanged);
135 scrollbars = RichTextBoxScrollBars.None;
137 hscroll = new ImplicitHScrollBar();
138 hscroll.ValueChanged += new EventHandler(hscroll_ValueChanged);
139 hscroll.SetStyle (ControlStyles.Selectable, false);
140 hscroll.Enabled = false;
141 hscroll.Visible = false;
142 hscroll.Maximum = Int32.MaxValue;
144 vscroll = new ImplicitVScrollBar();
145 vscroll.ValueChanged += new EventHandler(vscroll_ValueChanged);
146 vscroll.SetStyle (ControlStyles.Selectable, false);
147 vscroll.Enabled = false;
148 vscroll.Visible = false;
149 vscroll.Maximum = Int32.MaxValue;
151 SuspendLayout ();
152 this.Controls.AddImplicit (hscroll);
153 this.Controls.AddImplicit (vscroll);
154 ResumeLayout ();
156 SetStyle (ControlStyles.UserPaint | ControlStyles.StandardClick, false);
157 SetStyle (ControlStyles.UseTextForAccessibility, false);
158 SetStyle (ControlStyles.FixedHeight, true);
160 //base.SetAutoSizeMode (AutoSizeMode.GrowAndShrink);
162 canvas_width = ClientSize.Width;
163 canvas_height = ClientSize.Height;
164 document.ViewPortWidth = canvas_width;
165 document.ViewPortHeight = canvas_height;
167 Cursor = Cursors.IBeam;
169 requested_height = Height;
171 can_cache_preferred_size = true;
173 #endregion // Internal Constructor
175 #region Private and Internal Methods
176 internal string CaseAdjust (string s)
178 if (character_casing == CharacterCasing.Normal)
179 return s;
180 if (character_casing == CharacterCasing.Lower)
181 return s.ToLower();
182 return s.ToUpper();
185 internal override Size GetPreferredSizeCore (Size proposedSize)
187 Size bordersAndPadding = SizeFromClientSize(Size.Empty) + Padding.Size;
188 if (BorderStyle != BorderStyle.None)
189 bordersAndPadding += new Size(0, 7);
190 proposedSize -= bordersAndPadding;
192 TextFormatFlags format = TextFormatFlags.NoPrefix;
193 if (!Multiline)
194 format |= TextFormatFlags.SingleLine;
195 else if (WordWrap)
196 format |= TextFormatFlags.WordBreak;
198 Size textSize = TextRenderer.MeasureText(this.Text, this.Font, proposedSize, format);
199 textSize.Height = Math.Max(textSize.Height, FontHeight);
200 return textSize + bordersAndPadding;
203 internal override void HandleClick (int clicks, MouseEventArgs me)
205 // MS seems to fire the click event in spite of the styles they set
206 bool click_set = GetStyle (ControlStyles.StandardClick);
207 bool doubleclick_set = GetStyle (ControlStyles.StandardDoubleClick);
209 // so explicitly set them to true first
210 SetStyle (ControlStyles.StandardClick | ControlStyles.StandardDoubleClick, true);
212 base.HandleClick (clicks, me);
214 // then revert to our previous state
215 if (!click_set)
216 SetStyle (ControlStyles.StandardClick, false);
217 if (!doubleclick_set)
218 SetStyle (ControlStyles.StandardDoubleClick, false);
221 internal override void PaintControlBackground (PaintEventArgs pevent)
223 if (!ThemeEngine.Current.TextBoxBaseShouldPaintBackground (this))
224 return;
225 base.PaintControlBackground (pevent);
227 #endregion // Private and Internal Methods
229 #region Public Instance Properties
230 [DefaultValue(false)]
231 [MWFCategory("Behavior")]
232 public bool AcceptsTab {
233 get {
234 return accepts_tab;
237 set {
238 if (value != accepts_tab) {
239 accepts_tab = value;
240 OnAcceptsTabChanged(EventArgs.Empty);
245 [Browsable (false)]
246 [EditorBrowsable (EditorBrowsableState.Never)]
247 [DefaultValue(true)]
248 [Localizable(true)]
249 [RefreshProperties(RefreshProperties.Repaint)]
250 [MWFCategory("Behavior")]
251 public override bool AutoSize {
252 get {
253 return auto_size;
256 set {
257 if (value != auto_size) {
258 auto_size = value;
259 if (auto_size) {
260 if (PreferredHeight != Height) {
261 Height = PreferredHeight;
268 [DispId(-501)]
269 public override System.Drawing.Color BackColor {
270 get {
271 return base.BackColor;
273 set {
274 backcolor_set = true;
275 base.BackColor = ChangeBackColor (value);
279 [Browsable(false)]
280 [EditorBrowsable(EditorBrowsableState.Never)]
281 public override System.Drawing.Image BackgroundImage {
282 get {
283 return base.BackgroundImage;
285 set {
286 base.BackgroundImage = value;
290 [DefaultValue(BorderStyle.Fixed3D)]
291 [DispId(-504)]
292 [MWFCategory("Appearance")]
293 public BorderStyle BorderStyle {
294 get { return actual_border_style; }
295 set {
296 if (value == actual_border_style)
297 return;
299 if (actual_border_style != BorderStyle.Fixed3D || value != BorderStyle.Fixed3D)
300 Invalidate ();
302 actual_border_style = value;
303 document.UpdateMargins ();
305 if (value != BorderStyle.Fixed3D)
306 value = BorderStyle.None;
308 InternalBorderStyle = value;
309 OnBorderStyleChanged(EventArgs.Empty);
313 [Browsable(false)]
314 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
315 public bool CanUndo {
316 get {
317 return document.undo.CanUndo;
321 [DispId(-513)]
322 public override System.Drawing.Color ForeColor {
323 get {
324 return base.ForeColor;
326 set {
327 base.ForeColor = value;
331 [DefaultValue(true)]
332 [MWFCategory("Behavior")]
333 public bool HideSelection {
334 get {
335 return hide_selection;
338 set {
339 if (value != hide_selection) {
340 hide_selection = value;
341 OnHideSelectionChanged(EventArgs.Empty);
343 document.selection_visible = !hide_selection;
344 document.InvalidateSelectionArea();
348 [MergableProperty (false)]
349 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
350 [Editor("System.Windows.Forms.Design.StringArrayEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))]
351 [Localizable(true)]
352 [MWFCategory("Appearance")]
353 public string[] Lines {
354 get {
355 int count;
356 ArrayList lines;
358 count = document.Lines;
360 // Handle empty document
361 if ((count == 1) && (document.GetLine (1).text.Length == 0)) {
362 return new string [0];
365 lines = new ArrayList ();
367 int i = 1;
368 while (i <= count) {
369 Line line;
370 StringBuilder lt = new StringBuilder ();
372 do {
373 line = document.GetLine (i++);
374 lt.Append (line.TextWithoutEnding ());
375 } while (line.ending == LineEnding.Wrap && i <= count);
377 lines.Add (lt.ToString ());
380 return (string []) lines.ToArray (typeof (string));
383 set {
384 StringBuilder sb = new StringBuilder ();
386 for (int i = 0; i < value.Length; i++) {
387 // Don't add the last line if it is just an empty line feed
388 // the line feed is reflected in the previous line's ending
389 if (i == value.Length - 1 && value[i].Length == 0)
390 break;
392 sb.Append (value[i] + Environment.NewLine);
395 int newline_length = Environment.NewLine.Length;
397 // We want to remove the final new line character
398 if (sb.Length >= newline_length)
399 sb.Remove (sb.Length - newline_length, newline_length);
401 Text = sb.ToString ();
405 [DefaultValue(32767)]
406 [Localizable(true)]
407 [MWFCategory("Behavior")]
408 public virtual int MaxLength {
409 get {
410 if (max_length == (int.MaxValue - 1)) { // We don't distinguish between single and multi-line limits
411 return 0;
413 return max_length;
416 set {
417 if (value != max_length) {
418 if (value == 0)
419 value = int.MaxValue - 1;
421 max_length = value;
426 [Browsable(false)]
427 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
428 public bool Modified {
429 get {
430 return modified;
433 set {
434 if (value != modified) {
435 modified = value;
436 OnModifiedChanged(EventArgs.Empty);
441 [DefaultValue(false)]
442 [Localizable(true)]
443 [RefreshProperties(RefreshProperties.All)]
444 [MWFCategory("Behavior")]
445 public virtual bool Multiline {
446 get {
447 return document.multiline;
450 set {
451 if (value != document.multiline) {
452 document.multiline = value;
454 SetStyle (ControlStyles.FixedHeight, !value);
455 FixupHeight ();
457 if (Parent != null)
458 Parent.PerformLayout ();
460 OnMultilineChanged(EventArgs.Empty);
463 if (document.multiline) {
464 document.Wrap = word_wrap;
465 document.PasswordChar = "";
467 } else {
468 document.Wrap = false;
469 if (this.password_char != '\0') {
470 if (this is TextBox)
471 document.PasswordChar = (this as TextBox).PasswordChar.ToString ();
472 } else {
473 document.PasswordChar = "";
477 CalculateDocument ();
481 [Browsable(false)]
482 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
483 [EditorBrowsable(EditorBrowsableState.Advanced)]
484 // This returns the preferred outer height, not the client height.
485 public int PreferredHeight {
486 get {
487 int height = FontHeight;
488 if (BorderStyle != BorderStyle.None) {
489 height += 7;
491 return height;
495 [RefreshProperties (RefreshProperties.Repaint)]
496 [DefaultValue(false)]
497 [MWFCategory("Behavior")]
498 public bool ReadOnly {
499 get {
500 return read_only;
503 set {
504 if (value != read_only) {
505 read_only = value;
506 if (!backcolor_set) {
507 if (read_only)
508 background_color = SystemColors.Control;
509 else
510 background_color = SystemColors.Window;
512 OnReadOnlyChanged(EventArgs.Empty);
513 Invalidate ();
518 [Browsable(false)]
519 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
520 public virtual string SelectedText {
521 get {
522 string retval = document.GetSelection ();
524 return retval;
527 set {
528 if (value == null)
529 value = String.Empty;
531 document.ReplaceSelection(CaseAdjust(value), false);
533 ScrollToCaret();
534 OnTextChanged(EventArgs.Empty);
538 [Browsable(false)]
539 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
540 public virtual int SelectionLength {
541 get {
542 int res = document.SelectionLength ();
544 return res;
547 set {
548 if (value < 0) {
549 string msg = String.Format ("'{0}' is not a valid value for 'SelectionLength'", value);
550 throw new ArgumentOutOfRangeException ("SelectionLength", msg);
553 document.InvalidateSelectionArea ();
554 if (value != 0) {
555 int start;
556 Line line;
557 LineTag tag;
558 int pos;
560 selection_length = value;
561 start = document.LineTagToCharIndex (document.selection_start.line, document.selection_start.pos);
562 document.CharIndexToLineTag (start + value, out line, out tag, out pos);
563 document.SetSelectionEnd (line, pos, true);
564 document.PositionCaret (line, pos);
565 } else {
566 selection_length = -1;
567 document.SetSelectionEnd (document.selection_start.line, document.selection_start.pos, true);
568 document.PositionCaret (document.selection_start.line, document.selection_start.pos);
573 [Browsable(false)]
574 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
575 public int SelectionStart {
576 get {
577 return document.LineTagToCharIndex(document.selection_start.line,
578 document.selection_start.pos);
581 set {
582 if (value < 0) {
583 string msg = String.Format ("'{0}' is not a valid value for 'SelectionStart'", value);
584 throw new ArgumentOutOfRangeException ("SelectionStart", msg);
587 // If SelectionStart has been used, we don't highlight on focus
588 has_been_focused = true;
590 document.InvalidateSelectionArea ();
591 document.SetSelectionStart (value, false);
592 if (selection_length > -1)
593 document.SetSelectionEnd (value + selection_length, true);
594 else
595 document.SetSelectionEnd (value, true);
596 document.PositionCaret (document.selection_start.line, document.selection_start.pos);
597 ScrollToCaret ();
601 [DefaultValue (true)]
602 public virtual bool ShortcutsEnabled {
603 get { return shortcuts_enabled; }
604 set { shortcuts_enabled = value; }
607 [Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design,
608 "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
609 [Localizable(true)]
610 public override string Text {
611 get {
612 if (document == null || document.Root == null || document.Root.text == null)
613 return string.Empty;
615 StringBuilder sb = new StringBuilder();
617 Line line = null;
618 for (int i = 1; i <= document.Lines; i++) {
619 line = document.GetLine (i);
620 sb.Append(line.text.ToString ());
623 return sb.ToString();
626 set {
627 // reset to force a select all next time the box gets focus
628 has_been_focused = false;
630 if (value == Text)
631 return;
633 document.Empty ();
634 if ((value != null) && (value != "")) {
635 document.Insert (document.GetLine (1), 0, false, value);
636 } else {
637 if (IsHandleCreated) {
638 document.SetSelectionToCaret (true);
639 CalculateDocument ();
643 document.PositionCaret (document.GetLine (1), 0);
644 document.SetSelectionToCaret (true);
646 ScrollToCaret ();
648 OnTextChanged(EventArgs.Empty);
652 [Browsable(false)]
653 public virtual int TextLength {
654 get {
655 if (document == null || document.Root == null || document.Root.text == null)
656 return 0;
657 return Text.Length;
661 [DefaultValue(true)]
662 [Localizable(true)]
663 [MWFCategory("Behavior")]
664 public bool WordWrap {
665 get {
666 return word_wrap;
669 set {
670 if (value != word_wrap) {
671 if (document.multiline) {
672 word_wrap = value;
673 document.Wrap = value;
675 CalculateDocument ();
680 [Browsable (false)]
681 [EditorBrowsable (EditorBrowsableState.Never)]
682 public override ImageLayout BackgroundImageLayout {
683 get { return base.BackgroundImageLayout; }
684 set { base.BackgroundImageLayout = value; }
687 [Browsable (false)]
688 [EditorBrowsable (EditorBrowsableState.Never)]
689 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
690 public new Padding Padding {
691 get { return base.Padding; }
692 set { base.Padding = value; }
695 protected override Cursor DefaultCursor {
696 get { return Cursors.IBeam; }
698 #endregion // Public Instance Properties
700 #region Protected Instance Properties
701 protected override bool CanEnableIme {
702 get {
703 if (ReadOnly || password_char != '\0')
704 return false;
706 return true;
710 protected override CreateParams CreateParams {
711 get {
712 return base.CreateParams;
716 protected override System.Drawing.Size DefaultSize {
717 get {
718 return new Size(100, PreferredHeight);
722 // Currently our double buffering breaks our scrolling, so don't let people enable this
723 [EditorBrowsable (EditorBrowsableState.Never)]
724 protected override bool DoubleBuffered {
725 get { return false; }
726 set { }
729 #endregion // Protected Instance Properties
731 #region Public Instance Methods
732 public void AppendText (string text)
734 // Save some cycles and only check the Text if we are one line
735 bool is_empty = document.Lines == 1 && Text == String.Empty;
737 // make sure the caret begins at the end
738 if (document.caret.line.line_no != document.Lines ||
739 (document.caret.pos) != document.caret.line.TextLengthWithoutEnding ()) {
740 document.MoveCaret (CaretDirection.CtrlEnd);
742 document.Insert (document.caret.line, document.caret.pos, false, text, document.CaretTag);
743 document.MoveCaret (CaretDirection.CtrlEnd);
744 document.SetSelectionToCaret (true);
746 if (!is_empty)
747 ScrollToCaret ();
750 // Avoid the initial focus selecting all when append text is used
752 has_been_focused = true;
754 Modified = false;
755 OnTextChanged(EventArgs.Empty);
758 public void Clear ()
760 Modified = false;
761 Text = string.Empty;
764 public void ClearUndo ()
766 document.undo.Clear();
769 public void Copy ()
771 DataObject o;
773 o = new DataObject(DataFormats.Text, SelectedText);
774 if (this is RichTextBox)
775 o.SetData(DataFormats.Rtf, ((RichTextBox)this).SelectedRtf);
776 Clipboard.SetDataObject(o);
779 public void Cut ()
781 DataObject o;
783 o = new DataObject(DataFormats.Text, SelectedText);
784 if (this is RichTextBox)
785 o.SetData(DataFormats.Rtf, ((RichTextBox)this).SelectedRtf);
786 Clipboard.SetDataObject (o);
788 document.undo.BeginUserAction (Locale.GetText ("Cut"));
789 document.ReplaceSelection (String.Empty, false);
790 document.undo.EndUserAction ();
792 Modified = true;
793 OnTextChanged (EventArgs.Empty);
796 public void Paste ()
798 Paste (Clipboard.GetDataObject(), null, false);
801 public void ScrollToCaret ()
803 if (IsHandleCreated)
804 CaretMoved (this, EventArgs.Empty);
807 public void Select(int start, int length)
809 SelectionStart = start;
810 SelectionLength = length;
813 public void SelectAll ()
815 Line last;
817 last = document.GetLine(document.Lines);
818 document.SetSelectionStart(document.GetLine(1), 0, false);
819 document.SetSelectionEnd(last, last.text.Length, true);
820 document.PositionCaret (document.selection_end.line, document.selection_end.pos);
821 selection_length = -1;
823 CaretMoved (this, null);
825 document.DisplayCaret ();
828 internal void SelectAllNoScroll ()
830 Line last;
832 last = document.GetLine(document.Lines);
833 document.SetSelectionStart(document.GetLine(1), 0, false);
834 document.SetSelectionEnd(last, last.text.Length, false);
835 document.PositionCaret (document.selection_end.line, document.selection_end.pos);
836 selection_length = -1;
838 document.DisplayCaret ();
841 public override string ToString ()
843 return String.Concat (base.ToString (), ", Text: ", Text);
846 [MonoInternalNote ("Deleting is classed as Typing, instead of its own Undo event")]
847 public void Undo ()
849 if (document.undo.Undo ()) {
850 Modified = true;
851 OnTextChanged (EventArgs.Empty);
855 public void DeselectAll ()
857 SelectionLength = 0;
860 public virtual char GetCharFromPosition (Point pt)
862 return GetCharFromPositionInternal (pt);
865 internal virtual char GetCharFromPositionInternal (Point p)
867 int index;
868 LineTag tag = document.FindCursor (p.X, p.Y, out index);
869 if (tag == null)
870 return (char) 0; // Shouldn't happen
872 if (index >= tag.Line.text.Length) {
874 if (tag.Line.ending == LineEnding.Wrap) {
875 // If we have wrapped text, we return the first char of the next line
876 Line line = document.GetLine (tag.Line.line_no + 1);
877 if (line != null)
878 return line.text [0];
882 if (tag.Line.line_no == document.Lines) {
883 // Last line returns the last char
884 return tag.Line.text [tag.Line.text.Length - 1];
887 // This really shouldn't happen
888 return (char) 0;
890 return tag.Line.text [index];
893 public virtual int GetCharIndexFromPosition (Point pt)
895 int line_index;
896 LineTag tag = document.FindCursor (pt.X, pt.Y, out line_index);
897 if (tag == null)
898 return 0;
900 if (line_index >= tag.Line.text.Length) {
902 if (tag.Line.ending == LineEnding.Wrap) {
903 // If we have wrapped text, we return the first char of the next line
904 Line line = document.GetLine (tag.Line.line_no + 1);
905 if (line != null)
906 return document.LineTagToCharIndex (line, 0);
909 if (tag.Line.line_no == document.Lines) {
910 // Last line returns the last char
911 return document.LineTagToCharIndex (tag.Line, tag.Line.text.Length - 1);
914 return 0;
917 return document.LineTagToCharIndex (tag.Line, line_index);
920 public virtual Point GetPositionFromCharIndex (int index)
922 int pos;
923 Line line;
924 LineTag tag;
926 document.CharIndexToLineTag (index, out line, out tag, out pos);
928 return new Point ((int) (line.widths [pos] +
929 line.X + document.viewport_x),
930 line.Y + document.viewport_y + tag.Shift);
933 public int GetFirstCharIndexFromLine (int lineNumber)
935 Line line = document.GetLine (lineNumber + 1);
936 if (line == null)
937 return -1;
939 return document.LineTagToCharIndex (line, 0);
942 public int GetFirstCharIndexOfCurrentLine ()
944 return document.LineTagToCharIndex (document.caret.line, 0);
946 #endregion // Public Instance Methods
948 #region Protected Instance Methods
949 protected override void CreateHandle ()
951 CalculateDocument ();
952 base.CreateHandle ();
953 document.AlignCaret();
954 ScrollToCaret();
957 internal virtual void HandleLinkClicked (LinkRectangle link_clicked)
961 protected override bool IsInputKey (Keys keyData)
963 if ((keyData & Keys.Alt) != 0)
964 return base.IsInputKey(keyData);
966 switch (keyData & Keys.KeyCode) {
967 case Keys.Enter: {
968 return (accepts_return && document.multiline);
971 case Keys.Tab: {
972 if (accepts_tab && document.multiline)
973 if ((keyData & Keys.Control) == 0)
974 return true;
975 return false;
978 case Keys.Left:
979 case Keys.Right:
980 case Keys.Up:
981 case Keys.Down:
982 case Keys.PageUp:
983 case Keys.PageDown:
984 case Keys.Home:
985 case Keys.End:
986 case Keys.Back: {
987 return true;
990 return false;
993 protected virtual void OnAcceptsTabChanged(EventArgs e)
995 EventHandler eh = (EventHandler)(Events [AcceptsTabChangedEvent]);
996 if (eh != null)
997 eh (this, e);
1000 protected virtual void OnBorderStyleChanged (EventArgs e)
1002 EventHandler eh = (EventHandler)(Events [BorderStyleChangedEvent]);
1003 if (eh != null)
1004 eh (this, e);
1007 protected override void OnFontChanged (EventArgs e)
1009 base.OnFontChanged (e);
1010 FixupHeight ();
1013 protected override void OnHandleCreated (EventArgs e)
1015 base.OnHandleCreated (e);
1016 if (delayed_font_or_color_change) {
1017 TextBoxBase_FontOrColorChanged (this, e);
1019 FixupHeight ();
1022 protected override void OnHandleDestroyed (EventArgs e)
1024 base.OnHandleDestroyed (e);
1027 protected virtual void OnHideSelectionChanged (EventArgs e)
1029 EventHandler eh = (EventHandler)(Events [HideSelectionChangedEvent]);
1030 if (eh != null)
1031 eh (this, e);
1034 protected virtual void OnModifiedChanged (EventArgs e)
1036 EventHandler eh = (EventHandler)(Events [ModifiedChangedEvent]);
1037 if (eh != null)
1038 eh (this, e);
1041 protected virtual void OnMultilineChanged (EventArgs e)
1043 EventHandler eh = (EventHandler)(Events [MultilineChangedEvent]);
1044 if (eh != null)
1045 eh (this, e);
1048 protected override void OnPaddingChanged (EventArgs e)
1050 base.OnPaddingChanged (e);
1053 protected virtual void OnReadOnlyChanged (EventArgs e)
1055 EventHandler eh = (EventHandler)(Events [ReadOnlyChangedEvent]);
1056 if (eh != null)
1057 eh (this, e);
1060 protected override bool ProcessCmdKey (ref Message msg, Keys keyData)
1062 return base.ProcessCmdKey (ref msg, keyData);
1064 protected override bool ProcessDialogKey (Keys keyData)
1066 // The user can use Ctrl-Tab or Ctrl-Shift-Tab to move control focus
1067 // instead of inserting a Tab. However, the focus-moving-tab-stuffs
1068 // doesn't work if Ctrl is pushed, so we remove it before sending it.
1069 if (accepts_tab && (keyData & (Keys.Control | Keys.Tab)) == (Keys.Control | Keys.Tab))
1070 keyData ^= Keys.Control;
1072 return base.ProcessDialogKey(keyData);
1075 private bool ProcessKey (Keys keyData)
1077 bool control;
1078 bool shift;
1080 control = (Control.ModifierKeys & Keys.Control) != 0;
1081 shift = (Control.ModifierKeys & Keys.Shift) != 0;
1083 if (shortcuts_enabled) {
1084 switch (keyData & Keys.KeyCode) {
1085 case Keys.X:
1086 if (control && read_only == false) {
1087 Cut();
1088 return true;
1090 return false;
1092 case Keys.C:
1093 if (control) {
1094 Copy();
1095 return true;
1097 return false;
1099 case Keys.V:
1100 if (control && read_only == false) {
1101 return Paste(Clipboard.GetDataObject(), null, true);
1103 return false;
1105 case Keys.Z:
1106 if (control && read_only == false) {
1107 Undo ();
1108 return true;
1110 return false;
1112 case Keys.A:
1113 if (control) {
1114 SelectAll();
1115 return true;
1117 return false;
1119 case Keys.Insert:
1121 if (read_only == false) {
1122 if (shift) {
1123 Paste (Clipboard.GetDataObject (), null, true);
1124 return true;
1127 if (control) {
1128 Copy ();
1129 return true;
1133 return false;
1135 case Keys.Delete:
1137 if (read_only)
1138 break;
1140 if (shift && read_only == false) {
1141 Cut ();
1142 return true;
1145 if (document.selection_visible) {
1146 document.ReplaceSelection("", false);
1147 } else {
1148 // DeleteChar only deletes on the line, doesn't do the combine
1149 if (document.CaretPosition >= document.CaretLine.TextLengthWithoutEnding ()) {
1150 if (document.CaretLine.LineNo < document.Lines) {
1151 Line line;
1153 line = document.GetLine(document.CaretLine.LineNo + 1);
1155 // this line needs to be invalidated before it is combined
1156 // because once it is combined, all it's coordinates will
1157 // have changed
1158 document.Invalidate (line, 0, line, line.text.Length);
1159 document.Combine(document.CaretLine, line);
1161 document.UpdateView(document.CaretLine,
1162 document.Lines, 0);
1165 } else {
1166 if (!control) {
1167 document.DeleteChar(document.CaretTag.Line, document.CaretPosition, true);
1168 } else {
1169 int end_pos;
1171 end_pos = document.CaretPosition;
1173 while ((end_pos < document.CaretLine.Text.Length) && !Document.IsWordSeparator(document.CaretLine.Text[end_pos])) {
1174 end_pos++;
1177 if (end_pos < document.CaretLine.Text.Length) {
1178 end_pos++;
1180 document.DeleteChars(document.CaretTag.Line, document.CaretPosition, end_pos - document.CaretPosition);
1185 document.AlignCaret();
1186 document.UpdateCaret();
1187 CaretMoved(this, null);
1189 Modified = true;
1190 OnTextChanged (EventArgs.Empty);
1192 return true;
1196 switch (keyData & Keys.KeyCode) {
1197 case Keys.Left: {
1198 if (control) {
1199 document.MoveCaret(CaretDirection.WordBack);
1200 } else {
1201 if (!document.selection_visible || shift) {
1202 document.MoveCaret(CaretDirection.CharBack);
1203 } else {
1204 document.MoveCaret(CaretDirection.SelectionStart);
1208 if (!shift) {
1209 document.SetSelectionToCaret(true);
1210 } else {
1211 document.SetSelectionToCaret(false);
1214 CaretMoved(this, null);
1215 return true;
1218 case Keys.Right: {
1219 if (control) {
1220 document.MoveCaret(CaretDirection.WordForward);
1221 } else {
1222 if (!document.selection_visible || shift) {
1223 document.MoveCaret(CaretDirection.CharForward);
1224 } else {
1225 document.MoveCaret(CaretDirection.SelectionEnd);
1228 if (!shift) {
1229 document.SetSelectionToCaret(true);
1230 } else {
1231 document.SetSelectionToCaret(false);
1234 CaretMoved(this, null);
1235 return true;
1238 case Keys.Up: {
1239 if (control) {
1240 if (document.CaretPosition == 0) {
1241 document.MoveCaret(CaretDirection.LineUp);
1242 } else {
1243 document.MoveCaret(CaretDirection.Home);
1245 } else {
1246 document.MoveCaret(CaretDirection.LineUp);
1249 if ((Control.ModifierKeys & Keys.Shift) == 0) {
1250 document.SetSelectionToCaret(true);
1251 } else {
1252 document.SetSelectionToCaret(false);
1255 CaretMoved(this, null);
1256 return true;
1259 case Keys.Down: {
1260 if (control) {
1261 if (document.CaretPosition == document.CaretLine.Text.Length) {
1262 document.MoveCaret(CaretDirection.LineDown);
1263 } else {
1264 document.MoveCaret(CaretDirection.End);
1266 } else {
1267 document.MoveCaret(CaretDirection.LineDown);
1270 if ((Control.ModifierKeys & Keys.Shift) == 0) {
1271 document.SetSelectionToCaret(true);
1272 } else {
1273 document.SetSelectionToCaret(false);
1276 CaretMoved(this, null);
1277 return true;
1280 case Keys.Home: {
1281 if ((Control.ModifierKeys & Keys.Control) != 0) {
1282 document.MoveCaret(CaretDirection.CtrlHome);
1283 } else {
1284 document.MoveCaret(CaretDirection.Home);
1287 if ((Control.ModifierKeys & Keys.Shift) == 0) {
1288 document.SetSelectionToCaret(true);
1289 } else {
1290 document.SetSelectionToCaret(false);
1293 CaretMoved(this, null);
1294 return true;
1297 case Keys.End: {
1298 if ((Control.ModifierKeys & Keys.Control) != 0) {
1299 document.MoveCaret(CaretDirection.CtrlEnd);
1300 } else {
1301 document.MoveCaret(CaretDirection.End);
1304 if ((Control.ModifierKeys & Keys.Shift) == 0) {
1305 document.SetSelectionToCaret(true);
1306 } else {
1307 document.SetSelectionToCaret(false);
1310 CaretMoved(this, null);
1311 return true;
1314 //case Keys.Enter: {
1315 // // ignoring accepts_return, fixes bug #76355
1316 // if (!read_only && document.multiline && (accepts_return || (FindForm() != null && FindForm().AcceptButton == null) || ((Control.ModifierKeys & Keys.Control) != 0))) {
1317 // Line line;
1319 // if (document.selection_visible) {
1320 // document.ReplaceSelection("\n", false);
1321 // }
1323 // line = document.CaretLine;
1325 // document.Split (document.CaretLine, document.CaretTag, document.CaretPosition);
1326 // line.ending = LineEnding.Rich;
1327 // document.InsertString (line, line.text.Length,
1328 // document.LineEndingToString (line.ending));
1329 // OnTextChanged(EventArgs.Empty);
1331 // document.UpdateView (line, document.Lines - line.line_no, 0);
1332 // CaretMoved(this, null);
1333 // return true;
1334 // }
1335 // break;
1338 case Keys.Tab: {
1339 if (!read_only && accepts_tab && document.multiline) {
1340 document.InsertCharAtCaret ('\t', true);
1342 CaretMoved(this, null);
1343 Modified = true;
1344 OnTextChanged (EventArgs.Empty);
1346 return true;
1348 break;
1351 case Keys.PageUp: {
1352 if ((Control.ModifierKeys & Keys.Control) != 0) {
1353 document.MoveCaret(CaretDirection.CtrlPgUp);
1354 } else {
1355 document.MoveCaret(CaretDirection.PgUp);
1357 document.DisplayCaret ();
1358 return true;
1361 case Keys.PageDown: {
1362 if ((Control.ModifierKeys & Keys.Control) != 0) {
1363 document.MoveCaret(CaretDirection.CtrlPgDn);
1364 } else {
1365 document.MoveCaret(CaretDirection.PgDn);
1367 document.DisplayCaret ();
1368 return true;
1372 return false;
1375 internal virtual void RaiseSelectionChanged ()
1377 // Do nothing, overridden in RTB
1380 private void HandleBackspace (bool control)
1382 bool fire_changed;
1384 fire_changed = false;
1386 // delete only deletes on the line, doesn't do the combine
1387 if (document.selection_visible) {
1388 document.undo.BeginUserAction (Locale.GetText ("Delete"));
1389 document.ReplaceSelection("", false);
1390 document.undo.EndUserAction ();
1391 fire_changed = true;
1392 document.SetSelectionToCaret (true);
1393 } else {
1394 document.SetSelectionToCaret (true);
1396 if (document.CaretPosition == 0) {
1397 if (document.CaretLine.LineNo > 1) {
1398 Line line;
1399 int new_caret_pos;
1401 line = document.GetLine(document.CaretLine.LineNo - 1);
1402 new_caret_pos = line.TextLengthWithoutEnding ();
1404 // Invalidate the old line position before we do the combine
1405 document.Invalidate (line, 0, line, line.text.Length);
1406 document.Combine(line, document.CaretLine);
1408 document.UpdateView(line, document.Lines - line.LineNo, 0);
1409 document.PositionCaret(line, new_caret_pos);
1410 document.SetSelectionToCaret (true);
1411 document.UpdateCaret();
1412 fire_changed = true;
1414 } else {
1415 if (!control || document.CaretPosition == 0) {
1417 // Move before we delete because the delete will change positions around
1418 // if we cross a wrap border
1419 LineTag tag = document.CaretTag;
1420 int pos = document.CaretPosition;
1421 document.MoveCaret (CaretDirection.CharBack);
1422 document.DeleteChar (tag.Line, pos, false);
1423 document.SetSelectionToCaret (true);
1424 } else {
1425 int start_pos;
1428 start_pos = document.CaretPosition - 1;
1429 while ((start_pos > 0) && !Document.IsWordSeparator(document.CaretLine.Text[start_pos - 1])) {
1430 start_pos--;
1433 document.undo.BeginUserAction (Locale.GetText ("Delete"));
1434 document.DeleteChars(document.CaretTag.Line, start_pos, document.CaretPosition - start_pos);
1435 document.undo.EndUserAction ();
1436 document.PositionCaret(document.CaretLine, start_pos);
1437 document.SetSelectionToCaret (true);
1439 document.UpdateCaret();
1440 fire_changed = true;
1444 CaretMoved (this, null);
1446 if (fire_changed) {
1447 Modified = true;
1448 OnTextChanged(EventArgs.Empty);
1452 private void HandleEnter ()
1454 // ignoring accepts_return, fixes bug #76355
1455 if (!read_only && document.multiline && (accepts_return || (FindForm() != null && FindForm().AcceptButton == null) || ((Control.ModifierKeys & Keys.Control) != 0))) {
1456 Line line;
1458 if (document.selection_visible)
1459 document.ReplaceSelection ("", false);
1461 line = document.CaretLine;
1463 document.Split (document.CaretLine, document.CaretTag, document.CaretPosition);
1464 line.ending = document.StringToLineEnding (Environment.NewLine);
1465 document.InsertString (line, line.text.Length, document.LineEndingToString (line.ending));
1467 document.UpdateView (line, document.Lines - line.line_no, 0);
1468 CaretMoved (this, null);
1469 Modified = true;
1470 OnTextChanged (EventArgs.Empty);
1474 protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified)
1476 // Make sure we don't get sized bigger than we want to be
1478 if ((specified & BoundsSpecified.Height) != 0) {
1479 requested_height = height;
1482 if (!richtext) {
1483 if (!document.multiline) {
1484 height = PreferredHeight;
1488 base.SetBoundsCore (x, y, width, height, specified);
1491 protected override void WndProc (ref Message m)
1493 switch ((Msg)m.Msg) {
1494 case Msg.WM_KEYDOWN: {
1495 if (ProcessKeyMessage(ref m) || ProcessKey((Keys)m.WParam.ToInt32() | XplatUI.State.ModifierKeys)) {
1496 m.Result = IntPtr.Zero;
1497 return;
1499 DefWndProc (ref m);
1500 return;
1503 case Msg.WM_CHAR: {
1504 int ch;
1506 if (ProcessKeyMessage(ref m)) {
1507 m.Result = IntPtr.Zero;
1508 return;
1511 if (read_only) {
1512 return;
1515 m.Result = IntPtr.Zero;
1517 ch = m.WParam.ToInt32();
1519 if (ch == 127) {
1520 HandleBackspace(true);
1521 } else if (ch >= 32) {
1522 if (document.selection_visible) {
1523 document.ReplaceSelection("", false);
1526 char c = (char)m.WParam;
1527 switch (character_casing) {
1528 case CharacterCasing.Upper:
1529 c = Char.ToUpper((char) m.WParam);
1530 break;
1531 case CharacterCasing.Lower:
1532 c = Char.ToLower((char) m.WParam);
1533 break;
1536 if (document.Length < max_length) {
1537 document.InsertCharAtCaret(c, true);
1538 OnTextUpdate ();
1539 CaretMoved (this, null);
1540 Modified = true;
1541 OnTextChanged(EventArgs.Empty);
1543 } else {
1544 XplatUI.AudibleAlert(AlertType.Default);
1546 return;
1547 } else if (ch == 8) {
1548 HandleBackspace(false);
1549 } else if (ch == 13)
1550 HandleEnter ();
1552 return;
1555 case Msg.WM_SETFOCUS:
1556 base.WndProc(ref m);
1557 document.CaretHasFocus ();
1558 break;
1560 case Msg.WM_KILLFOCUS:
1561 base.WndProc(ref m);
1562 document.CaretLostFocus ();
1563 break;
1565 case Msg.WM_NCPAINT:
1566 if (!ThemeEngine.Current.TextBoxBaseHandleWmNcPaint (this, ref m))
1567 base.WndProc(ref m);
1568 break;
1570 default:
1571 base.WndProc(ref m);
1572 return;
1576 #endregion // Protected Instance Methods
1578 #region Events
1579 static object AcceptsTabChangedEvent = new object ();
1580 static object AutoSizeChangedEvent = new object ();
1581 static object BorderStyleChangedEvent = new object ();
1582 static object HideSelectionChangedEvent = new object ();
1583 static object ModifiedChangedEvent = new object ();
1584 static object MultilineChangedEvent = new object ();
1585 static object ReadOnlyChangedEvent = new object ();
1586 static object HScrolledEvent = new object ();
1587 static object VScrolledEvent = new object ();
1589 public event EventHandler AcceptsTabChanged {
1590 add { Events.AddHandler (AcceptsTabChangedEvent, value); }
1591 remove { Events.RemoveHandler (AcceptsTabChangedEvent, value); }
1594 [Browsable (false)]
1595 [EditorBrowsable (EditorBrowsableState.Never)]
1596 public new event EventHandler AutoSizeChanged {
1597 add { Events.AddHandler (AutoSizeChangedEvent, value); }
1598 remove { Events.RemoveHandler (AutoSizeChangedEvent, value); }
1601 public event EventHandler BorderStyleChanged {
1602 add { Events.AddHandler (BorderStyleChangedEvent, value); }
1603 remove { Events.RemoveHandler (BorderStyleChangedEvent, value); }
1606 public event EventHandler HideSelectionChanged {
1607 add { Events.AddHandler (HideSelectionChangedEvent, value); }
1608 remove { Events.RemoveHandler (HideSelectionChangedEvent, value); }
1611 public event EventHandler ModifiedChanged {
1612 add { Events.AddHandler (ModifiedChangedEvent, value); }
1613 remove { Events.RemoveHandler (ModifiedChangedEvent, value); }
1616 public event EventHandler MultilineChanged {
1617 add { Events.AddHandler (MultilineChangedEvent, value); }
1618 remove { Events.RemoveHandler (MultilineChangedEvent, value); }
1621 public event EventHandler ReadOnlyChanged {
1622 add { Events.AddHandler (ReadOnlyChangedEvent, value); }
1623 remove { Events.RemoveHandler (ReadOnlyChangedEvent, value); }
1626 internal event EventHandler HScrolled {
1627 add { Events.AddHandler (HScrolledEvent, value); }
1628 remove { Events.RemoveHandler (HScrolledEvent, value); }
1631 internal event EventHandler VScrolled {
1632 add { Events.AddHandler (VScrolledEvent, value); }
1633 remove { Events.RemoveHandler (VScrolledEvent, value); }
1636 [Browsable(false)]
1637 [EditorBrowsable(EditorBrowsableState.Never)]
1638 public new event EventHandler BackgroundImageChanged {
1639 add { base.BackgroundImageChanged += value; }
1640 remove { base.BackgroundImageChanged -= value; }
1643 [Browsable (false)]
1644 [EditorBrowsable (EditorBrowsableState.Never)]
1645 public new event EventHandler BackgroundImageLayoutChanged {
1646 add { base.BackgroundImageLayoutChanged += value; }
1647 remove { base.BackgroundImageLayoutChanged -= value; }
1650 [Browsable (true)]
1651 [EditorBrowsable (EditorBrowsableState.Always)]
1652 public new event MouseEventHandler MouseClick {
1653 add { base.MouseClick += value; }
1654 remove { base.MouseClick -= value; }
1657 [Browsable (false)]
1658 [EditorBrowsable (EditorBrowsableState.Never)]
1659 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
1660 public new event EventHandler PaddingChanged {
1661 add { base.PaddingChanged += value; }
1662 remove { base.PaddingChanged -= value; }
1665 [Browsable (true)]
1666 [EditorBrowsable (EditorBrowsableState.Always)]
1667 public new event EventHandler Click {
1668 add { base.Click += value; }
1669 remove { base.Click -= value; }
1672 // XXX should this not manipulate base.Paint?
1673 #pragma warning disable 0067
1674 [MonoTODO]
1675 [Browsable(false)]
1676 [EditorBrowsable(EditorBrowsableState.Never)]
1677 public new event PaintEventHandler Paint;
1678 #pragma warning restore 0067
1680 #endregion // Events
1682 #region Private Methods
1683 internal Document Document {
1684 get {
1685 return document;
1688 set {
1689 document = value;
1693 internal bool EnableLinks {
1694 get { return enable_links; }
1695 set {
1696 enable_links = value;
1698 document.EnableLinks = value;
1702 internal override bool ScaleChildrenInternal {
1703 get { return false; }
1706 internal bool ShowSelection {
1707 get {
1708 if (show_selection || !hide_selection) {
1709 return true;
1712 return has_focus;
1715 set {
1716 if (show_selection == value)
1717 return;
1719 show_selection = value;
1720 // Currently InvalidateSelectionArea is commented out so do a full invalidate
1721 document.InvalidateSelectionArea();
1725 internal int TopMargin {
1726 get {
1727 return document == null ? 0 : document.top_margin;
1729 set {
1730 document.top_margin = value;
1734 #region UIA Framework Properties
1736 internal ScrollBar UIAHScrollBar {
1737 get { return hscroll; }
1740 internal ScrollBar UIAVScrollBar {
1741 get { return vscroll; }
1744 #endregion UIA Framework Properties
1746 internal override void OnPaintInternal (PaintEventArgs pevent)
1748 Draw (pevent.Graphics, pevent.ClipRectangle);
1751 // OnPaint does not get raised on MS (see bug #80639)
1753 pevent.Handled = true;
1756 internal void Draw (Graphics g, Rectangle clippingArea)
1758 ThemeEngine.Current.TextBoxBaseFillBackground (this, g, clippingArea);
1760 // Draw the viewable document
1761 document.Draw(g, clippingArea);
1764 private void FixupHeight ()
1766 if (!richtext) {
1767 int saved_requested_height = requested_height;
1768 if (!document.multiline) {
1769 if (PreferredHeight != Height) {
1770 SetBoundsCore (Left, Top, Width, PreferredHeight, BoundsSpecified.Height);
1772 } else {
1773 SetBoundsCore (Left, Top, Width, Math.Max(PreferredHeight, requested_height), BoundsSpecified.Height);
1775 requested_height = saved_requested_height;
1779 private bool IsDoubleClick (MouseEventArgs e)
1781 TimeSpan interval = DateTime.Now - click_last;
1782 if (interval.TotalMilliseconds > SystemInformation.DoubleClickTime)
1783 return false;
1784 Size dcs = SystemInformation.DoubleClickSize;
1785 if (e.X < click_point_x - dcs.Width / 2 || e.X > click_point_x + dcs.Width / 2)
1786 return false;
1787 if (e.Y < click_point_y - dcs.Height / 2 || e.Y > click_point_y + dcs.Height / 2)
1788 return false;
1789 return true;
1792 private void TextBoxBase_MouseDown (object sender, MouseEventArgs e)
1794 bool dbliclick = false;
1796 if (e.Button == MouseButtons.Left) {
1798 // Special case when shift key is pressed and
1799 // left mouse is clicked.. set selection from
1800 // current cursor to mouse
1801 if ((Control.ModifierKeys & Keys.Shift) > 0) {
1802 document.PositionCaret (e.X + document.ViewPortX, e.Y + document.ViewPortY);
1803 document.SetSelectionToCaret (false);
1804 document.DisplayCaret ();
1805 return;
1808 dbliclick = IsDoubleClick (e);
1810 if (current_link != null) {
1811 HandleLinkClicked (current_link);
1812 return;
1815 //ensure nothing is selected anymore BEFORE we
1816 //position the caret, so the caret is recreated
1817 //(caret is only visible when nothing is selected)
1818 if (document.selection_visible && dbliclick == false) {
1819 document.SetSelectionToCaret (true);
1820 click_mode = CaretSelection.Position;
1823 document.PositionCaret(e.X + document.ViewPortX, e.Y + document.ViewPortY);
1824 document.DisplayCaret ();
1826 if (dbliclick) {
1827 switch (click_mode) {
1828 case CaretSelection.Position:
1829 SelectWord ();
1830 click_mode = CaretSelection.Word;
1831 break;
1832 case CaretSelection.Word:
1834 if (this is TextBox) {
1835 document.SetSelectionToCaret (true);
1836 click_mode = CaretSelection.Position;
1837 break;
1840 document.ExpandSelection (CaretSelection.Line, false);
1841 click_mode = CaretSelection.Line;
1842 break;
1843 case CaretSelection.Line:
1845 // Gotta do this first because Exanding to a word
1846 // from a line doesn't really work
1847 document.SetSelectionToCaret (true);
1849 SelectWord ();
1850 click_mode = CaretSelection.Word;
1851 break;
1853 } else {
1854 document.SetSelectionToCaret (true);
1855 click_mode = CaretSelection.Position;
1858 click_point_x = e.X;
1859 click_point_y = e.Y;
1860 click_last = DateTime.Now;
1863 if ((e.Button == MouseButtons.Middle) && XplatUI.RunningOnUnix) {
1864 Document.Marker marker;
1866 marker.tag = document.FindCursor(e.X + document.ViewPortX, e.Y + document.ViewPortY, out marker.pos);
1867 marker.line = marker.tag.Line;
1868 marker.height = marker.tag.Height;
1870 document.SetSelection(marker.line, marker.pos, marker.line, marker.pos);
1871 Paste (Clipboard.GetDataObject (true), null, true);
1875 private void TextBoxBase_MouseUp (object sender, MouseEventArgs e)
1877 if (e.Button == MouseButtons.Left) {
1878 if (click_mode == CaretSelection.Position) {
1879 document.SetSelectionToCaret(false);
1880 document.DisplayCaret();
1882 // Only raise if there is text.
1883 if (Text.Length > 0)
1884 RaiseSelectionChanged ();
1887 if (scroll_timer != null) {
1888 scroll_timer.Enabled = false;
1890 return;
1894 private void SizeControls ()
1896 if (hscroll.Visible) {
1897 //vscroll.Maximum += hscroll.Height;
1898 canvas_height = ClientSize.Height - hscroll.Height;
1899 } else {
1900 canvas_height = ClientSize.Height;
1903 if (vscroll.Visible) {
1904 //hscroll.Maximum += vscroll.Width;
1905 canvas_width = ClientSize.Width - vscroll.Width;
1907 if (GetInheritedRtoL () == RightToLeft.Yes) {
1908 document.OffsetX = vscroll.Width;
1909 } else {
1910 document.OffsetX = 0;
1913 } else {
1914 canvas_width = ClientSize.Width;
1915 document.OffsetX = 0;
1918 document.ViewPortWidth = canvas_width;
1919 document.ViewPortHeight = canvas_height;
1922 private void PositionControls ()
1924 if (canvas_height < 1 || canvas_width < 1)
1925 return;
1927 int hmod = vscroll.Visible ? vscroll.Width : 0;
1928 int vmod = hscroll.Visible ? hscroll.Height : 0;
1930 if (GetInheritedRtoL () == RightToLeft.Yes) {
1931 hscroll.Bounds = new Rectangle (ClientRectangle.Left + hmod,
1932 Math.Max(0, ClientRectangle.Height - hscroll.Height),
1933 ClientSize.Width,
1934 hscroll.Height);
1936 vscroll.Bounds = new Rectangle (ClientRectangle.Left,
1937 ClientRectangle.Top,
1938 vscroll.Width,
1939 Math.Max(0, ClientSize.Height - (vmod)));
1940 } else {
1941 hscroll.Bounds = new Rectangle (ClientRectangle.Left,
1942 Math.Max(0, ClientRectangle.Height - hscroll.Height),
1943 Math.Max(0, ClientSize.Width - hmod),
1944 hscroll.Height);
1946 vscroll.Bounds = new Rectangle (
1947 Math.Max(0, ClientRectangle.Right - vscroll.Width),
1948 ClientRectangle.Top,
1949 vscroll.Width,
1950 Math.Max(0, ClientSize.Height - vmod));
1954 internal RightToLeft GetInheritedRtoL ()
1956 for (Control c = this; c != null; c = c.Parent)
1957 if (c.RightToLeft != RightToLeft.Inherit)
1958 return c.RightToLeft;
1959 return RightToLeft.No;
1962 private void TextBoxBase_SizeChanged (object sender, EventArgs e)
1964 CalculateDocument ();
1967 private void TextBoxBase_RightToLeftChanged (object o, EventArgs e)
1969 CalculateDocument ();
1972 private void TextBoxBase_MouseWheel (object sender, MouseEventArgs e)
1974 if (!vscroll.Enabled)
1975 return;
1977 if (e.Delta < 0)
1978 vscroll.Value = Math.Min (vscroll.Value + SystemInformation.MouseWheelScrollLines * 5,
1979 Math.Max (0, vscroll.Maximum - document.ViewPortHeight + 1));
1980 else
1981 vscroll.Value = Math.Max (0, vscroll.Value - SystemInformation.MouseWheelScrollLines * 5);
1984 internal virtual void SelectWord ()
1986 StringBuilder s = document.caret.line.text;
1987 int start = document.caret.pos;
1988 int end = document.caret.pos;
1990 if (s.Length < 1) {
1991 if (document.caret.line.line_no >= document.Lines)
1992 return;
1993 Line line = document.GetLine (document.caret.line.line_no + 1);
1994 document.PositionCaret (line, 0);
1995 return;
1998 if (start > 0) {
1999 start--;
2000 end--;
2003 // skip whitespace until we hit a word
2004 while (start > 0 && s [start] == ' ')
2005 start--;
2006 if (start > 0) {
2007 while (start > 0 && (s [start] != ' '))
2008 start--;
2009 if (s [start] == ' ')
2010 start++;
2013 if (s [end] == ' ') {
2014 while (end < s.Length && s [end] == ' ')
2015 end++;
2016 } else {
2017 while (end < s.Length && s [end] != ' ')
2018 end++;
2019 while (end < s.Length && s [end] == ' ')
2020 end++;
2023 document.SetSelection (document.caret.line, start, document.caret.line, end);
2024 document.PositionCaret (document.selection_end.line, document.selection_end.pos);
2025 document.DisplayCaret();
2028 internal void CalculateDocument()
2030 if (!IsHandleCreated)
2031 return;
2033 CalculateScrollBars ();
2034 using (var graphics = CreateGraphics())
2035 document.RecalculateDocument (graphics);
2037 if (document.caret.line != null && document.caret.line.Y < document.ViewPortHeight) {
2038 // The window has probably been resized, making the entire thing visible, so
2039 // we need to set the scroll position back to zero.
2040 vscroll.Value = 0;
2043 Invalidate();
2046 internal void CalculateScrollBars ()
2048 // FIXME - need separate calculations for center and right alignment
2049 SizeControls ();
2051 if (document.Width >= document.ViewPortWidth) {
2052 hscroll.SetValues (0, Math.Max (1, document.Width), -1,
2053 document.ViewPortWidth < 0 ? 0 : document.ViewPortWidth);
2054 if (document.multiline)
2055 hscroll.Enabled = true;
2056 } else {
2057 hscroll.Enabled = false;
2058 hscroll.Maximum = document.ViewPortWidth;
2061 if (document.Height >= document.ViewPortHeight) {
2062 vscroll.SetValues (0, Math.Max (1, document.Height), -1,
2063 document.ViewPortHeight < 0 ? 0 : document.ViewPortHeight);
2064 if (document.multiline)
2065 vscroll.Enabled = true;
2066 } else {
2067 vscroll.Enabled = false;
2068 vscroll.Maximum = document.ViewPortHeight;
2071 if (!WordWrap) {
2072 switch (scrollbars) {
2073 case RichTextBoxScrollBars.Both:
2074 case RichTextBoxScrollBars.Horizontal:
2075 if (richtext)
2076 hscroll.Visible = hscroll.Enabled;
2077 else
2078 hscroll.Visible = this.Multiline;
2079 break;
2080 case RichTextBoxScrollBars.ForcedBoth:
2081 case RichTextBoxScrollBars.ForcedHorizontal:
2082 hscroll.Visible = true;
2083 break;
2084 default:
2085 hscroll.Visible = false;
2086 break;
2088 } else {
2089 hscroll.Visible = false;
2092 switch (scrollbars) {
2093 case RichTextBoxScrollBars.Both:
2094 case RichTextBoxScrollBars.Vertical:
2095 if (richtext)
2096 vscroll.Visible = vscroll.Enabled;
2097 else
2098 vscroll.Visible = this.Multiline;
2099 break;
2100 case RichTextBoxScrollBars.ForcedBoth:
2101 case RichTextBoxScrollBars.ForcedVertical:
2102 vscroll.Visible = true;
2103 break;
2104 default:
2105 vscroll.Visible = false;
2106 break;
2109 PositionControls ();
2111 SizeControls (); //Update sizings now we've decided whats visible
2114 private void document_WidthChanged (object sender, EventArgs e)
2116 CalculateScrollBars();
2119 private void document_HeightChanged (object sender, EventArgs e)
2121 CalculateScrollBars();
2124 private void ScrollLinks (int xChange, int yChange)
2126 foreach (LinkRectangle link in list_links)
2127 link.Scroll (xChange, yChange);
2130 private void hscroll_ValueChanged (object sender, EventArgs e)
2132 int old_viewport_x;
2134 old_viewport_x = document.ViewPortX;
2135 document.ViewPortX = this.hscroll.Value;
2138 // Before scrolling we want to destroy the caret, then draw a new one after the scroll
2139 // the reason for this is that scrolling changes the coordinates of the caret, and we
2140 // will get tracers if we don't
2142 if (Focused)
2143 document.CaretLostFocus ();
2145 if (vscroll.Visible) {
2146 if (GetInheritedRtoL () == RightToLeft.Yes) {
2147 XplatUI.ScrollWindow (this.Handle, new Rectangle (vscroll.Width, 0, ClientSize.Width - vscroll.Width, ClientSize.Height), old_viewport_x - this.hscroll.Value, 0, false);
2148 } else {
2149 XplatUI.ScrollWindow (this.Handle, new Rectangle (0, 0, ClientSize.Width - vscroll.Width, ClientSize.Height), old_viewport_x - this.hscroll.Value, 0, false);
2151 } else {
2152 XplatUI.ScrollWindow(this.Handle, ClientRectangle, old_viewport_x - this.hscroll.Value, 0, false);
2155 ScrollLinks (old_viewport_x - this.hscroll.Value, 0);
2157 if (Focused)
2158 document.CaretHasFocus ();
2160 EventHandler eh = (EventHandler)(Events [HScrolledEvent]);
2161 if (eh != null)
2162 eh (this, EventArgs.Empty);
2165 private void vscroll_ValueChanged (object sender, EventArgs e)
2167 int old_viewport_y;
2169 old_viewport_y = document.ViewPortY;
2170 document.ViewPortY = this.vscroll.Value;
2173 // Before scrolling we want to destroy the caret, then draw a new one after the scroll
2174 // the reason for this is that scrolling changes the coordinates of the caret, and we
2175 // will get tracers if we don't
2177 if (Focused)
2178 document.CaretLostFocus ();
2180 if (hscroll.Visible) {
2181 XplatUI.ScrollWindow(this.Handle, new Rectangle(0, 0, ClientSize.Width, ClientSize.Height - hscroll.Height), 0, old_viewport_y - this.vscroll.Value, false);
2182 } else {
2183 XplatUI.ScrollWindow(this.Handle, ClientRectangle, 0, old_viewport_y - this.vscroll.Value, false);
2186 ScrollLinks (0, old_viewport_y - this.vscroll.Value);
2188 if (Focused)
2189 document.CaretHasFocus ();
2191 EventHandler eh = (EventHandler)(Events [VScrolledEvent]);
2192 if (eh != null)
2193 eh (this, EventArgs.Empty);
2196 private void TextBoxBase_MouseMove (object sender, MouseEventArgs e)
2198 // FIXME - handle auto-scrolling if mouse is to the right/left of the window
2199 if (e.Button == MouseButtons.Left && Capture) {
2200 if (!ClientRectangle.Contains (e.X, e.Y)) {
2201 if (scroll_timer == null) {
2202 scroll_timer = new Timer ();
2203 scroll_timer.Interval = 100;
2204 scroll_timer.Tick += new EventHandler (ScrollTimerTickHandler);
2207 if (!scroll_timer.Enabled) {
2208 scroll_timer.Start ();
2210 // Force the first tick
2211 ScrollTimerTickHandler (null, EventArgs.Empty);
2215 document.PositionCaret(e.X + document.ViewPortX, e.Y + document.ViewPortY);
2216 if (click_mode == CaretSelection.Position) {
2217 document.SetSelectionToCaret(false);
2218 document.DisplayCaret();
2222 //search through link boxes to see if the mouse is over one of them
2224 bool found_link = false;
2225 foreach (LinkRectangle link in list_links) {
2226 if (link.LinkAreaRectangle.Contains (e.X, e.Y)) {
2227 XplatUI.SetCursor (window.Handle, Cursors.Hand.handle);
2229 found_link = true;
2230 current_link = link;
2231 break;
2235 if (found_link == false) {
2236 XplatUI.SetCursor (window.Handle, DefaultCursor.handle);
2237 current_link = null;
2241 private void TextBoxBase_FontOrColorChanged (object sender, EventArgs e)
2243 Line line;
2245 if (!IsHandleCreated) {
2246 delayed_font_or_color_change = true;
2247 return;
2250 document.SuspendRecalc ();
2251 // Font changes apply to the whole document
2252 for (int i = 1; i <= document.Lines; i++) {
2253 line = document.GetLine(i);
2254 if (LineTag.FormatText(line, 1, line.text.Length, Font, ForeColor,
2255 Color.Empty, FormatSpecified.Font | FormatSpecified.Color)) {
2256 using (var graphics = CreateGraphics())
2257 document.RecalculateDocument (graphics, line.LineNo, line.LineNo, false);
2260 document.ResumeRecalc (false);
2262 // Make sure the caret height is matching the new font height
2263 document.AlignCaret();
2266 private void ScrollTimerTickHandler (object sender, EventArgs e)
2268 Point pt = Cursor.Position;
2270 pt = PointToClient (pt);
2272 if (pt.X < ClientRectangle.Left) {
2273 document.MoveCaret(CaretDirection.CharBackNoWrap);
2274 document.SetSelectionToCaret(false);
2276 CaretMoved(this, null);
2277 } else if (pt.X > ClientRectangle.Right) {
2278 document.MoveCaret(CaretDirection.CharForwardNoWrap);
2279 document.SetSelectionToCaret(false);
2281 CaretMoved(this, null);
2282 } else if (pt.Y > ClientRectangle.Bottom) {
2283 document.MoveCaret(CaretDirection.LineDown);
2284 document.SetSelectionToCaret(false);
2286 CaretMoved(this, null);
2287 } else if (pt.Y < ClientRectangle.Top) {
2288 document.MoveCaret(CaretDirection.LineUp);
2289 document.SetSelectionToCaret(false);
2291 CaretMoved(this, null);
2295 /// <summary>Ensure the caret is always visible</summary>
2296 internal void CaretMoved (object sender, EventArgs e)
2298 Point pos;
2299 int height;
2301 if (!IsHandleCreated || canvas_width < 1 || canvas_height < 1)
2302 return;
2304 document.MoveCaretToTextTag ();
2305 pos = document.Caret;
2307 //Console.WriteLine("Caret now at {0} (Thumb: {1}x{2}, Canvas: {3}x{4}, Document {5}x{6})", pos, hscroll.Value, vscroll.Value, canvas_width, canvas_height, document.Width, document.Height);
2310 // Horizontal scrolling:
2311 // If the caret moves to the left outside the visible area, we jump the document into view, not just one
2312 // character, but 1/3 of the width of the document
2313 // If the caret moves to the right outside the visible area, we scroll just enough to keep the caret visible
2315 // Handle horizontal scrolling
2316 if (document.CaretLine.alignment == HorizontalAlignment.Left) {
2317 // Check if we moved out of view to the left
2318 if (pos.X < (document.ViewPortX)) {
2319 do {
2320 if ((hscroll.Value - document.ViewPortWidth / 3) >= hscroll.Minimum) {
2321 hscroll.SafeValueSet (hscroll.Value - document.ViewPortWidth / 3);
2322 } else {
2323 hscroll.Value = hscroll.Minimum;
2325 } while (hscroll.Value > pos.X);
2328 // Check if we moved out of view to the right
2329 if ((pos.X >= (document.ViewPortWidth + document.ViewPortX)) && (hscroll.Value != hscroll.Maximum)) {
2330 if ((pos.X - document.ViewPortWidth + 1) <= hscroll.Maximum) {
2331 if (pos.X - document.ViewPortWidth >= 0) {
2332 hscroll.SafeValueSet (pos.X - document.ViewPortWidth + 1);
2333 } else {
2334 hscroll.Value = 0;
2336 } else {
2337 hscroll.Value = hscroll.Maximum;
2340 } else if (document.CaretLine.alignment == HorizontalAlignment.Right) {
2341 // hscroll.Value = pos.X;
2343 // if ((pos.X > (this.canvas_width + document.ViewPortX)) && (hscroll.Enabled && (hscroll.Value != hscroll.Maximum))) {
2344 // hscroll.Value = hscroll.Maximum;
2345 // }
2346 } else {
2347 // FIXME - implement center cursor alignment
2350 if (Text.Length > 0)
2351 RaiseSelectionChanged ();
2353 if (!document.multiline)
2354 return;
2356 // Handle vertical scrolling
2357 height = document.CaretLine.Height + 1;
2359 if (pos.Y < document.ViewPortY)
2360 vscroll.SafeValueSet (pos.Y);
2361 if ((pos.Y + height) > (document.ViewPortY + canvas_height))
2362 vscroll.Value = Math.Min (vscroll.Maximum, pos.Y - canvas_height + height);
2365 internal bool Paste (IDataObject clip, DataFormats.Format format, bool obey_length)
2367 string s;
2369 if (clip == null)
2370 return false;
2372 if (format == null) {
2373 if ((this is RichTextBox) && clip.GetDataPresent(DataFormats.Rtf)) {
2374 format = DataFormats.GetFormat(DataFormats.Rtf);
2375 } else if ((this is RichTextBox) && clip.GetDataPresent (DataFormats.Bitmap)) {
2376 format = DataFormats.GetFormat (DataFormats.Bitmap);
2377 } else if (clip.GetDataPresent(DataFormats.UnicodeText)) {
2378 format = DataFormats.GetFormat(DataFormats.UnicodeText);
2379 } else if (clip.GetDataPresent(DataFormats.Text)) {
2380 format = DataFormats.GetFormat(DataFormats.Text);
2381 } else {
2382 return false;
2384 } else {
2385 if ((format.Name == DataFormats.Rtf) && !(this is RichTextBox)) {
2386 return false;
2389 if (!clip.GetDataPresent(format.Name)) {
2390 return false;
2394 if (format.Name == DataFormats.Rtf) {
2395 document.undo.BeginUserAction (Locale.GetText ("Paste"));
2396 ((RichTextBox)this).SelectedRtf = (string)clip.GetData(DataFormats.Rtf);
2397 document.undo.EndUserAction ();
2398 Modified = true;
2399 return true;
2400 } else if (format.Name == DataFormats.Bitmap) {
2401 document.undo.BeginUserAction (Locale.GetText ("Paste"));
2402 // document.InsertImage (document.caret.line, document.caret.pos, (Image) clip.GetData (DataFormats.Bitmap));
2403 document.MoveCaret (CaretDirection.CharForward);
2404 document.undo.EndUserAction ();
2405 return true;
2406 } else if (format.Name == DataFormats.UnicodeText) {
2407 s = (string)clip.GetData(DataFormats.UnicodeText);
2408 } else if (format.Name == DataFormats.Text) {
2409 s = (string)clip.GetData(DataFormats.Text);
2410 } else {
2411 return false;
2414 if (!obey_length) {
2415 document.undo.BeginUserAction (Locale.GetText ("Paste"));
2416 this.SelectedText = s;
2417 document.undo.EndUserAction ();
2418 } else {
2419 if ((s.Length + (document.Length - SelectedText.Length)) < max_length) {
2420 document.undo.BeginUserAction (Locale.GetText ("Paste"));
2421 this.SelectedText = s;
2422 document.undo.EndUserAction ();
2423 } else if ((document.Length - SelectedText.Length) < max_length) {
2424 document.undo.BeginUserAction (Locale.GetText ("Paste"));
2425 this.SelectedText = s.Substring (0, max_length - (document.Length - SelectedText.Length));
2426 document.undo.EndUserAction ();
2430 Modified = true;
2431 return true;
2434 internal virtual Color ChangeBackColor (Color backColor)
2436 return backColor;
2439 internal override bool IsInputCharInternal (char charCode)
2441 return true;
2443 #endregion // Private Methods
2445 #region Private Classes
2446 internal class LinkRectangle {
2447 private Rectangle link_area_rectangle;
2448 private LineTag link_tag;
2450 public LinkRectangle (Rectangle rect)
2452 link_tag = null;
2453 link_area_rectangle = rect;
2456 public Rectangle LinkAreaRectangle {
2457 get { return link_area_rectangle; }
2458 set { link_area_rectangle = value; }
2461 public LineTag LinkTag {
2462 get { return link_tag; }
2463 set { link_tag = value; }
2466 public void Scroll (int x_change, int y_change)
2468 link_area_rectangle.X += x_change;
2469 link_area_rectangle.Y += y_change;
2472 #endregion
2474 // This is called just before OnTextChanged is called.
2475 internal virtual void OnTextUpdate ()
2479 protected override void OnTextChanged (EventArgs e)
2481 base.OnTextChanged (e);
2484 public virtual int GetLineFromCharIndex (int index)
2486 Line line_out;
2487 LineTag tag_out;
2488 int pos;
2490 document.CharIndexToLineTag (index, out line_out, out tag_out, out pos);
2492 return line_out.LineNo;
2495 protected override void OnMouseUp (MouseEventArgs mevent)
2497 base.OnMouseUp (mevent);