diff options
Diffstat (limited to 'source/ShiftUI/Widgets/TextBoxBase.cs')
| -rw-r--r-- | source/ShiftUI/Widgets/TextBoxBase.cs | 2493 |
1 files changed, 2493 insertions, 0 deletions
diff --git a/source/ShiftUI/Widgets/TextBoxBase.cs b/source/ShiftUI/Widgets/TextBoxBase.cs new file mode 100644 index 0000000..aca2dee --- /dev/null +++ b/source/ShiftUI/Widgets/TextBoxBase.cs @@ -0,0 +1,2493 @@ +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions:c +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com) +// +// Authors: +// Peter Bartok [email protected] +// +// + +// NOT COMPLETE + + +#undef Debug +#undef DebugClick + +using System; +using System.ComponentModel; +using System.ComponentModel.Design; +using System.Drawing; +using System.Drawing.Text; +using System.Text; +using System.Runtime.InteropServices; +using System.Collections; + +namespace ShiftUI +{ + [ComVisible (true)] + [DefaultBindingProperty ("Text")] + [ClassInterface (ClassInterfaceType.AutoDispatch)] + [DefaultEvent("TextChanged")] + //[Designer("ShiftUI.Design.TextBoxBaseDesigner, " + Consts.AssemblySystem_Design)] + public abstract class TextBoxBase : Widget + { + #region Local Variables + internal HorizontalAlignment alignment; + internal bool accepts_tab; + internal bool accepts_return; + internal bool auto_size; + internal bool backcolor_set; + internal CharacterCasing character_casing; + internal bool hide_selection; + int max_length; + internal bool modified; + internal char password_char; + internal bool read_only; + internal bool word_wrap; + internal Document document; + internal LineTag caret_tag; // tag our cursor is in + internal int caret_pos; // position on the line our cursor is in (can be 0 = beginning of line) + internal ImplicitHScrollBar hscroll; + internal ImplicitVScrollBar vscroll; + internal RichTextBoxScrollBars scrollbars; + internal Timer scroll_timer; + internal bool richtext; + internal bool show_selection; // set to true to always show selection, even if no focus is set + internal ArrayList list_links; // currently showing links + private LinkRectangle current_link; // currently hovering link + private bool enable_links; // whether links are enabled + + internal bool has_been_focused; + + internal int selection_length = -1; // set to the user-specified selection length, or -1 if none + internal bool show_caret_w_selection; // TextBox shows the caret when the selection is visible + internal int canvas_width; + internal int canvas_height; + static internal int track_width = 2; // + static internal int track_border = 5; // + internal DateTime click_last; + internal int click_point_x; + internal int click_point_y; + internal CaretSelection click_mode; + internal BorderStyle actual_border_style; + internal bool shortcuts_enabled = true; + #if Debug + internal static bool draw_lines = false; + #endif + + #endregion // Local Variables + + #region Internal Constructor + // Constructor will go when complete, only for testing - pdb + internal TextBoxBase () + { + alignment = HorizontalAlignment.Left; + accepts_return = false; + accepts_tab = false; + auto_size = true; + InternalBorderStyle = BorderStyle.Fixed3D; + actual_border_style = BorderStyle.Fixed3D; + character_casing = CharacterCasing.Normal; + hide_selection = true; + max_length = short.MaxValue; + password_char = '\0'; + read_only = false; + word_wrap = true; + richtext = false; + show_selection = false; + enable_links = false; + list_links = new ArrayList (); + current_link = null; + show_caret_w_selection = (this is TextBox); + document = new Document(this); + document.WidthChanged += new EventHandler(document_WidthChanged); + document.HeightChanged += new EventHandler(document_HeightChanged); + //document.CaretMoved += new EventHandler(CaretMoved); + document.Wrap = false; + click_last = DateTime.Now; + click_mode = CaretSelection.Position; + + MouseDown += new MouseEventHandler(TextBoxBase_MouseDown); + MouseUp += new MouseEventHandler(TextBoxBase_MouseUp); + MouseMove += new MouseEventHandler(TextBoxBase_MouseMove); + SizeChanged += new EventHandler(TextBoxBase_SizeChanged); + FontChanged += new EventHandler(TextBoxBase_FontOrColorChanged); + ForeColorChanged += new EventHandler(TextBoxBase_FontOrColorChanged); + MouseWheel += new MouseEventHandler(TextBoxBase_MouseWheel); + RightToLeftChanged += new EventHandler (TextBoxBase_RightToLeftChanged); + + scrollbars = RichTextBoxScrollBars.None; + + hscroll = new ImplicitHScrollBar(); + hscroll.ValueChanged += new EventHandler(hscroll_ValueChanged); + hscroll.SetStyle (Widgetstyles.Selectable, false); + hscroll.Enabled = false; + hscroll.Visible = false; + hscroll.Maximum = Int32.MaxValue; + + vscroll = new ImplicitVScrollBar(); + vscroll.ValueChanged += new EventHandler(vscroll_ValueChanged); + vscroll.SetStyle (Widgetstyles.Selectable, false); + vscroll.Enabled = false; + vscroll.Visible = false; + vscroll.Maximum = Int32.MaxValue; + + SuspendLayout (); + this.Widgets.AddImplicit (hscroll); + this.Widgets.AddImplicit (vscroll); + ResumeLayout (); + + SetStyle(Widgetstyles.UserPaint | Widgetstyles.StandardClick, false); + SetStyle(Widgetstyles.UseTextForAccessibility, false); + + base.SetAutoSizeMode (AutoSizeMode.GrowAndShrink); + + canvas_width = ClientSize.Width; + canvas_height = ClientSize.Height; + document.ViewPortWidth = canvas_width; + document.ViewPortHeight = canvas_height; + + Cursor = Cursors.IBeam; + } + #endregion // Internal Constructor + + #region Private and Internal Methods + internal string CaseAdjust (string s) + { + if (character_casing == CharacterCasing.Normal) + return s; + if (character_casing == CharacterCasing.Lower) + return s.ToLower(); + return s.ToUpper(); + } + + internal override Size GetPreferredSizeCore (Size proposedSize) + { + return new Size (Width, Height); + } + + internal override void HandleClick (int clicks, MouseEventArgs me) + { + // MS seems to fire the click event in spite of the styles they set + bool click_set = GetStyle (Widgetstyles.StandardClick); + bool doubleclick_set = GetStyle (Widgetstyles.StandardDoubleClick); + + // so explicitly set them to true first + SetStyle (Widgetstyles.StandardClick | Widgetstyles.StandardDoubleClick, true); + + base.HandleClick (clicks, me); + + // then revert to our previous state + if (!click_set) + SetStyle (Widgetstyles.StandardClick, false); + if (!doubleclick_set) + SetStyle (Widgetstyles.StandardDoubleClick, false); + } + + internal override void PaintWidgetBackground (PaintEventArgs pevent) + { + if (!ThemeEngine.Current.TextBoxBaseShouldPaintBackground (this)) + return; + base.PaintWidgetBackground (pevent); + } + #endregion // Private and Internal Methods + + #region Public Instance Properties + [DefaultValue(false)] + [MWFCategory("Behavior")] + public bool AcceptsTab { + get { + return accepts_tab; + } + + set { + if (value != accepts_tab) { + accepts_tab = value; + OnAcceptsTabChanged(EventArgs.Empty); + } + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + [DefaultValue(true)] + [Localizable(true)] + [RefreshProperties(RefreshProperties.Repaint)] + [MWFCategory("Behavior")] + public override bool AutoSize { + get { + return auto_size; + } + + set { + if (value != auto_size) { + auto_size = value; + if (auto_size) { + if (PreferredHeight != Height) { + Height = PreferredHeight; + } + } + } + } + } + + [DispId(-501)] + public override System.Drawing.Color BackColor { + get { + return base.BackColor; + } + set { + backcolor_set = true; + base.BackColor = ChangeBackColor (value); + } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public override System.Drawing.Image BackgroundImage { + get { + return base.BackgroundImage; + } + set { + base.BackgroundImage = value; + } + } + + [DefaultValue(BorderStyle.Fixed3D)] + [DispId(-504)] + [MWFCategory("Appearance")] + public BorderStyle BorderStyle { + get { return actual_border_style; } + set { + if (value == actual_border_style) + return; + + if (actual_border_style != BorderStyle.Fixed3D || value != BorderStyle.Fixed3D) + Invalidate (); + + actual_border_style = value; + document.UpdateMargins (); + + if (value != BorderStyle.Fixed3D) + value = BorderStyle.None; + + InternalBorderStyle = value; + OnBorderStyleChanged(EventArgs.Empty); + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool CanUndo { + get { + return document.undo.CanUndo; + } + } + + [DispId(-513)] + public override System.Drawing.Color ForeColor { + get { + return base.ForeColor; + } + set { + base.ForeColor = value; + } + } + + [DefaultValue(true)] + [MWFCategory("Behavior")] + public bool HideSelection { + get { + return hide_selection; + } + + set { + if (value != hide_selection) { + hide_selection = value; + OnHideSelectionChanged(EventArgs.Empty); + } + document.selection_visible = !hide_selection; + document.InvalidateSelectionArea(); + } + } + + [MergableProperty (false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + //[Editor("ShiftUI.Design.StringArrayEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))] + [Localizable(true)] + [MWFCategory("Appearance")] + public string[] Lines { + get { + int count; + ArrayList lines; + + count = document.Lines; + + // Handle empty document + if ((count == 1) && (document.GetLine (1).text.Length == 0)) { + return new string [0]; + } + + lines = new ArrayList (); + + int i = 1; + while (i <= count) { + Line line; + StringBuilder lt = new StringBuilder (); + + do { + line = document.GetLine (i++); + lt.Append (line.TextWithoutEnding ()); + } while (line.ending == LineEnding.Wrap && i <= count); + + lines.Add (lt.ToString ()); + } + + return (string []) lines.ToArray (typeof (string)); + } + + set { + StringBuilder sb = new StringBuilder (); + + for (int i = 0; i < value.Length; i++) { + // Don't add the last line if it is just an empty line feed + // the line feed is reflected in the previous line's ending + if (i == value.Length - 1 && value[i].Length == 0) + break; + + sb.Append (value[i] + Environment.NewLine); + } + + int newline_length = Environment.NewLine.Length; + + // We want to remove the final new line character + if (sb.Length >= newline_length) + sb.Remove (sb.Length - newline_length, newline_length); + + Text = sb.ToString (); + } + } + + [DefaultValue(32767)] + [Localizable(true)] + [MWFCategory("Behavior")] + public virtual int MaxLength { + get { + if (max_length == (int.MaxValue - 1)) { // We don't distinguish between single and multi-line limits + return 0; + } + return max_length; + } + + set { + if (value != max_length) { + if (value == 0) + value = int.MaxValue - 1; + + max_length = value; + } + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool Modified { + get { + return modified; + } + + set { + if (value != modified) { + modified = value; + OnModifiedChanged(EventArgs.Empty); + } + } + } + + [DefaultValue(false)] + [Localizable(true)] + [RefreshProperties(RefreshProperties.All)] + [MWFCategory("Behavior")] + public virtual bool Multiline { + get { + return document.multiline; + } + + set { + if (value != document.multiline) { + document.multiline = value; + + if (this is TextBox) + SetStyle (Widgetstyles.FixedHeight, !value); + + // SetBoundsCore overrides the Height for multiline if it needs to, + // so we don't need to worry about it here. + SetBoundsCore (Left, Top, Width, ExplicitBounds.Height, BoundsSpecified.None); + + if (Parent != null) + Parent.PerformLayout (); + + OnMultilineChanged(EventArgs.Empty); + } + + if (document.multiline) { + document.Wrap = word_wrap; + document.PasswordChar = ""; + + } else { + document.Wrap = false; + if (this.password_char != '\0') { + if (this is TextBox) + document.PasswordChar = (this as TextBox).PasswordChar.ToString (); + } else { + document.PasswordChar = ""; + } + } + + if (IsHandleCreated) + CalculateDocument (); + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + //[EditorBrowsable(EditorBrowsableState.Advanced)] + // This returns the preferred outer height, not the client height. + public int PreferredHeight { + get { + int clientDelta = Height - ClientSize.Height; + if (BorderStyle != BorderStyle.None) + return Font.Height + 7 + clientDelta; + + // usually in borderless mode the top margin is 0, but + // try to access it, in case it was set manually, as ToolStrip* Widgets do + return Font.Height + TopMargin + clientDelta; + } + } + + [RefreshProperties (RefreshProperties.Repaint)] + [DefaultValue(false)] + [MWFCategory("Behavior")] + public bool ReadOnly { + get { + return read_only; + } + + set { + if (value != read_only) { + read_only = value; + if (!backcolor_set) { + if (read_only) + background_color = SystemColors.Control; + else + background_color = SystemColors.Window; + } + OnReadOnlyChanged(EventArgs.Empty); + Invalidate (); + } + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual string SelectedText { + get { + string retval = document.GetSelection (); + + return retval; + } + + set { + if (value == null) + value = String.Empty; + + document.ReplaceSelection(CaseAdjust(value), false); + + ScrollToCaret(); + OnTextChanged(EventArgs.Empty); + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public virtual int SelectionLength { + get { + int res = document.SelectionLength (); + + return res; + } + + set { + if (value < 0) { + string msg = String.Format ("'{0}' is not a valid value for 'SelectionLength'", value); + throw new ArgumentOutOfRangeException ("SelectionLength", msg); + } + + document.InvalidateSelectionArea (); + if (value != 0) { + int start; + Line line; + LineTag tag; + int pos; + + selection_length = value; + start = document.LineTagToCharIndex (document.selection_start.line, document.selection_start.pos); + document.CharIndexToLineTag (start + value, out line, out tag, out pos); + document.SetSelectionEnd (line, pos, true); + document.PositionCaret (line, pos); + } else { + selection_length = -1; + document.SetSelectionEnd (document.selection_start.line, document.selection_start.pos, true); + document.PositionCaret (document.selection_start.line, document.selection_start.pos); + } + } + } + + [Browsable(false)] + //[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int SelectionStart { + get { + return document.LineTagToCharIndex(document.selection_start.line, + document.selection_start.pos); + } + + set { + if (value < 0) { + string msg = String.Format ("'{0}' is not a valid value for 'SelectionStart'", value); + throw new ArgumentOutOfRangeException ("SelectionStart", msg); + } + + // If SelectionStart has been used, we don't highlight on focus + has_been_focused = true; + + document.InvalidateSelectionArea (); + document.SetSelectionStart (value, false); + if (selection_length > -1) + document.SetSelectionEnd (value + selection_length, true); + else + document.SetSelectionEnd (value, true); + document.PositionCaret (document.selection_start.line, document.selection_start.pos); + ScrollToCaret (); + } + } + + [DefaultValue (true)] + public virtual bool ShortcutsEnabled { + get { return shortcuts_enabled; } + set { shortcuts_enabled = value; } + } + + //[Editor ("System.ComponentModel.Design.MultilineStringEditor, " + Consts.AssemblySystem_Design, + //"System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)] + [Localizable(true)] + public override string Text { + get { + if (document == null || document.Root == null || document.Root.text == null) + return string.Empty; + + StringBuilder sb = new StringBuilder(); + + Line line = null; + for (int i = 1; i <= document.Lines; i++) { + line = document.GetLine (i); + sb.Append(line.text.ToString ()); + } + + return sb.ToString(); + } + + set { + // reset to force a select all next time the box gets focus + has_been_focused = false; + + if (value == Text) + return; + + document.Empty (); + if ((value != null) && (value != "")) { + document.Insert (document.GetLine (1), 0, false, value); + } else { + if (IsHandleCreated) { + document.SetSelectionToCaret (true); + CalculateDocument (); + } + } + + document.PositionCaret (document.GetLine (1), 0); + document.SetSelectionToCaret (true); + + ScrollToCaret (); + + OnTextChanged(EventArgs.Empty); + } + } + + [Browsable(false)] + public virtual int TextLength { + get { + if (document == null || document.Root == null || document.Root.text == null) + return 0; + return Text.Length; + } + } + + [DefaultValue(true)] + [Localizable(true)] + [MWFCategory("Behavior")] + public bool WordWrap { + get { + return word_wrap; + } + + set { + if (value != word_wrap) { + if (document.multiline) { + word_wrap = value; + document.Wrap = value; + } + CalculateDocument (); + } + } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public override ImageLayout BackgroundImageLayout { + get { return base.BackgroundImageLayout; } + set { base.BackgroundImageLayout = value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public new Padding Padding { + get { return base.Padding; } + set { base.Padding = value; } + } + + protected override Cursor DefaultCursor { + get { return Cursors.IBeam; } + } + #endregion // Public Instance Properties + + #region Protected Instance Properties + protected override bool CanEnableIme { + get { + if (ReadOnly || password_char != '\0') + return false; + + return true; + } + } + + protected override CreateParams CreateParams { + get { + return base.CreateParams; + } + } + + protected override System.Drawing.Size DefaultSize { + get { + return new Size(100, 20); + } + } + + // Currently our double buffering breaks our scrolling, so don't let people enable this + //[EditorBrowsable (EditorBrowsableState.Never)] + protected override bool DoubleBuffered { + get { return false; } + set { } + } + + #endregion // Protected Instance Properties + + #region Public Instance Methods + public void AppendText (string text) + { + // Save some cycles and only check the Text if we are one line + bool is_empty = document.Lines == 1 && Text == String.Empty; + + // make sure the caret begins at the end + if (document.caret.line.line_no != document.Lines || + (document.caret.pos) != document.caret.line.TextLengthWithoutEnding ()) { + document.MoveCaret (CaretDirection.CtrlEnd); + } + document.Insert (document.caret.line, document.caret.pos, false, text, document.CaretTag); + document.MoveCaret (CaretDirection.CtrlEnd); + document.SetSelectionToCaret (true); + + if (!is_empty) + ScrollToCaret (); + + // + // Avoid the initial focus selecting all when append text is used + // + has_been_focused = true; + + Modified = false; + OnTextChanged(EventArgs.Empty); + } + + public void Clear () + { + Modified = false; + Text = string.Empty; + } + + public void ClearUndo () + { + document.undo.Clear(); + } + + public void Copy () + { + DataObject o; + + o = new DataObject(DataFormats.Text, SelectedText); + if (this is RichTextBox) + o.SetData(DataFormats.Rtf, ((RichTextBox)this).SelectedRtf); + Clipboard.SetDataObject(o); + } + + public void Cut () + { + DataObject o; + + o = new DataObject(DataFormats.Text, SelectedText); + if (this is RichTextBox) + o.SetData(DataFormats.Rtf, ((RichTextBox)this).SelectedRtf); + Clipboard.SetDataObject (o); + + document.undo.BeginUserAction (String.Format ("Cut")); + document.ReplaceSelection (String.Empty, false); + document.undo.EndUserAction (); + + Modified = true; + OnTextChanged (EventArgs.Empty); + } + + public void Paste () + { + Paste (Clipboard.GetDataObject(), null, false); + } + + public void ScrollToCaret () + { + if (IsHandleCreated) + CaretMoved (this, EventArgs.Empty); + } + + public void Select(int start, int length) + { + SelectionStart = start; + SelectionLength = length; + } + + public void SelectAll () + { + Line last; + + last = document.GetLine(document.Lines); + document.SetSelectionStart(document.GetLine(1), 0, false); + document.SetSelectionEnd(last, last.text.Length, true); + document.PositionCaret (document.selection_end.line, document.selection_end.pos); + selection_length = -1; + + CaretMoved (this, null); + + document.DisplayCaret (); + } + + internal void SelectAllNoScroll () + { + Line last; + + last = document.GetLine(document.Lines); + document.SetSelectionStart(document.GetLine(1), 0, false); + document.SetSelectionEnd(last, last.text.Length, false); + document.PositionCaret (document.selection_end.line, document.selection_end.pos); + selection_length = -1; + + document.DisplayCaret (); + } + + public override string ToString () + { + return String.Concat (base.ToString (), ", Text: ", Text); + } + + [MonoInternalNote ("Deleting is classed as Typing, instead of its own Undo event")] + public void Undo () + { + if (document.undo.Undo ()) { + Modified = true; + OnTextChanged (EventArgs.Empty); + } + } + + public void DeselectAll () + { + SelectionLength = 0; + } + + public virtual char GetCharFromPosition (Point pt) + { + return GetCharFromPositionInternal (pt); + } + + internal virtual char GetCharFromPositionInternal (Point p) + { + int index; + LineTag tag = document.FindCursor (p.X, p.Y, out index); + if (tag == null) + return (char) 0; // Shouldn't happen + + if (index >= tag.Line.text.Length) { + + if (tag.Line.ending == LineEnding.Wrap) { + // If we have wrapped text, we return the first char of the next line + Line line = document.GetLine (tag.Line.line_no + 1); + if (line != null) + return line.text [0]; + + } + + if (tag.Line.line_no == document.Lines) { + // Last line returns the last char + return tag.Line.text [tag.Line.text.Length - 1]; + } + + // This really shouldn't happen + return (char) 0; + } + return tag.Line.text [index]; + } + + public virtual int GetCharIndexFromPosition (Point pt) + { + int line_index; + LineTag tag = document.FindCursor (pt.X, pt.Y, out line_index); + if (tag == null) + return 0; + + if (line_index >= tag.Line.text.Length) { + + if (tag.Line.ending == LineEnding.Wrap) { + // If we have wrapped text, we return the first char of the next line + Line line = document.GetLine (tag.Line.line_no + 1); + if (line != null) + return document.LineTagToCharIndex (line, 0); + } + + if (tag.Line.line_no == document.Lines) { + // Last line returns the last char + return document.LineTagToCharIndex (tag.Line, tag.Line.text.Length - 1); + } + + return 0; + } + + return document.LineTagToCharIndex (tag.Line, line_index); + } + + public virtual Point GetPositionFromCharIndex (int index) + { + int pos; + Line line; + LineTag tag; + + document.CharIndexToLineTag (index, out line, out tag, out pos); + + return new Point ((int) (line.widths [pos] + + line.X + document.viewport_x), + line.Y + document.viewport_y + tag.Shift); + } + + public int GetFirstCharIndexFromLine (int lineNumber) + { + Line line = document.GetLine (lineNumber + 1); + if (line == null) + return -1; + + return document.LineTagToCharIndex (line, 0); + } + + public int GetFirstCharIndexOfCurrentLine () + { + return document.LineTagToCharIndex (document.caret.line, 0); + } + #endregion // Public Instance Methods + + #region Protected Instance Methods + protected override void CreateHandle () + { + CalculateDocument (); + base.CreateHandle (); + document.AlignCaret(); + ScrollToCaret(); + } + + internal virtual void HandleLinkClicked (LinkRectangle link_clicked) + { + } + + protected override bool IsInputKey (Keys keyData) + { + if ((keyData & Keys.Alt) != 0) + return base.IsInputKey(keyData); + + switch (keyData & Keys.KeyCode) { + case Keys.Enter: { + return (accepts_return && document.multiline); + } + + case Keys.Tab: { + if (accepts_tab && document.multiline) + if ((keyData & Keys.Widget) == 0) + return true; + return false; + } + + case Keys.Left: + case Keys.Right: + case Keys.Up: + case Keys.Down: + case Keys.PageUp: + case Keys.PageDown: + case Keys.Home: + case Keys.End: { + return true; + } + } + return false; + } + + protected virtual void OnAcceptsTabChanged(EventArgs e) + { + EventHandler eh = (EventHandler)(Events [AcceptsTabChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnBorderStyleChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [BorderStyleChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnFontChanged (EventArgs e) + { + base.OnFontChanged (e); + + if (auto_size && !document.multiline) { + if (PreferredHeight != Height) { + Height = PreferredHeight; + } + } + } + + protected override void OnHandleCreated (EventArgs e) + { + base.OnHandleCreated (e); + FixupHeight (); + } + + protected override void OnHandleDestroyed (EventArgs e) + { + base.OnHandleDestroyed (e); + } + + protected virtual void OnHideSelectionChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [HideSelectionChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnModifiedChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [ModifiedChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected virtual void OnMultilineChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [MultilineChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override void OnPaddingChanged (EventArgs e) + { + base.OnPaddingChanged (e); + } + + protected virtual void OnReadOnlyChanged (EventArgs e) + { + EventHandler eh = (EventHandler)(Events [ReadOnlyChangedEvent]); + if (eh != null) + eh (this, e); + } + + protected override bool ProcessCmdKey (ref Message msg, Keys keyData) + { + return base.ProcessCmdKey (ref msg, keyData); + } + protected override bool ProcessDialogKey (Keys keyData) + { + // The user can use Ctrl-Tab or Ctrl-Shift-Tab to move Widget focus + // instead of inserting a Tab. However, the focus-moving-tab-stuffs + // doesn't work if Ctrl is pushed, so we remove it before sending it. + if (accepts_tab && (keyData & (Keys.Widget | Keys.Tab)) == (Keys.Widget | Keys.Tab)) + keyData ^= Keys.Widget; + + return base.ProcessDialogKey(keyData); + } + + private bool ProcessKey (Keys keyData) + { + bool control; + bool shift; + + control = (Widget.ModifierKeys & Keys.Widget) != 0; + shift = (Widget.ModifierKeys & Keys.Shift) != 0; + + if (shortcuts_enabled) { + switch (keyData & Keys.KeyCode) { + case Keys.X: + if (control && read_only == false) { + Cut(); + return true; + } + return false; + + case Keys.C: + if (control) { + Copy(); + return true; + } + return false; + + case Keys.V: + if (control && read_only == false) { + return Paste(Clipboard.GetDataObject(), null, true); + } + return false; + + case Keys.Z: + if (control && read_only == false) { + Undo (); + return true; + } + return false; + + case Keys.A: + if (control) { + SelectAll(); + return true; + } + return false; + + case Keys.Insert: + + if (read_only == false) { + if (shift) { + Paste (Clipboard.GetDataObject (), null, true); + return true; + } + + if (control) { + Copy (); + return true; + } + } + + return false; + + case Keys.Delete: + + if (read_only) + break; + + if (shift && read_only == false) { + Cut (); + return true; + } + + if (document.selection_visible) { + document.ReplaceSelection("", false); + } else { + // DeleteChar only deletes on the line, doesn't do the combine + if (document.CaretPosition >= document.CaretLine.TextLengthWithoutEnding ()) { + if (document.CaretLine.LineNo < document.Lines) { + Line line; + + line = document.GetLine(document.CaretLine.LineNo + 1); + + // this line needs to be invalidated before it is combined + // because once it is combined, all it's coordinates will + // have changed + document.Invalidate (line, 0, line, line.text.Length); + document.Combine(document.CaretLine, line); + + document.UpdateView(document.CaretLine, + document.Lines, 0); + + } + } else { + if (!control) { + document.DeleteChar(document.CaretTag.Line, document.CaretPosition, true); + } else { + int end_pos; + + end_pos = document.CaretPosition; + + while ((end_pos < document.CaretLine.Text.Length) && !Document.IsWordSeparator(document.CaretLine.Text[end_pos])) { + end_pos++; + } + + if (end_pos < document.CaretLine.Text.Length) { + end_pos++; + } + document.DeleteChars(document.CaretTag.Line, document.CaretPosition, end_pos - document.CaretPosition); + } + } + } + + document.AlignCaret(); + document.UpdateCaret(); + CaretMoved(this, null); + + Modified = true; + OnTextChanged (EventArgs.Empty); + + return true; + } + } + + switch (keyData & Keys.KeyCode) { + case Keys.Left: { + if (control) { + document.MoveCaret(CaretDirection.WordBack); + } else { + if (!document.selection_visible || shift) { + document.MoveCaret(CaretDirection.CharBack); + } else { + document.MoveCaret(CaretDirection.SelectionStart); + } + } + + if (!shift) { + document.SetSelectionToCaret(true); + } else { + document.SetSelectionToCaret(false); + } + + CaretMoved(this, null); + return true; + } + + case Keys.Right: { + if (control) { + document.MoveCaret(CaretDirection.WordForward); + } else { + if (!document.selection_visible || shift) { + document.MoveCaret(CaretDirection.CharForward); + } else { + document.MoveCaret(CaretDirection.SelectionEnd); + } + } + if (!shift) { + document.SetSelectionToCaret(true); + } else { + document.SetSelectionToCaret(false); + } + + CaretMoved(this, null); + return true; + } + + case Keys.Up: { + if (control) { + if (document.CaretPosition == 0) { + document.MoveCaret(CaretDirection.LineUp); + } else { + document.MoveCaret(CaretDirection.Home); + } + } else { + document.MoveCaret(CaretDirection.LineUp); + } + + if ((Widget.ModifierKeys & Keys.Shift) == 0) { + document.SetSelectionToCaret(true); + } else { + document.SetSelectionToCaret(false); + } + + CaretMoved(this, null); + return true; + } + + case Keys.Down: { + if (control) { + if (document.CaretPosition == document.CaretLine.Text.Length) { + document.MoveCaret(CaretDirection.LineDown); + } else { + document.MoveCaret(CaretDirection.End); + } + } else { + document.MoveCaret(CaretDirection.LineDown); + } + + if ((Widget.ModifierKeys & Keys.Shift) == 0) { + document.SetSelectionToCaret(true); + } else { + document.SetSelectionToCaret(false); + } + + CaretMoved(this, null); + return true; + } + + case Keys.Home: { + if ((Widget.ModifierKeys & Keys.Widget) != 0) { + document.MoveCaret(CaretDirection.CtrlHome); + } else { + document.MoveCaret(CaretDirection.Home); + } + + if ((Widget.ModifierKeys & Keys.Shift) == 0) { + document.SetSelectionToCaret(true); + } else { + document.SetSelectionToCaret(false); + } + + CaretMoved(this, null); + return true; + } + + case Keys.End: { + if ((Widget.ModifierKeys & Keys.Widget) != 0) { + document.MoveCaret(CaretDirection.CtrlEnd); + } else { + document.MoveCaret(CaretDirection.End); + } + + if ((Widget.ModifierKeys & Keys.Shift) == 0) { + document.SetSelectionToCaret(true); + } else { + document.SetSelectionToCaret(false); + } + + CaretMoved(this, null); + return true; + } + + //case Keys.Enter: { + // // ignoring accepts_return, fixes bug #76355 + // if (!read_only && document.multiline && (accepts_return || (FindForm() != null && FindForm().AcceptButton == null) || ((Widget.ModifierKeys & Keys.Widget) != 0))) { + // Line line; + + // if (document.selection_visible) { + // document.ReplaceSelection("\n", false); + // } + + // line = document.CaretLine; + + // document.Split (document.CaretLine, document.CaretTag, document.CaretPosition); + // line.ending = LineEnding.Rich; + // document.InsertString (line, line.text.Length, + // document.LineEndingToString (line.ending)); + // OnTextChanged(EventArgs.Empty); + + // document.UpdateView (line, document.Lines - line.line_no, 0); + // CaretMoved(this, null); + // return true; + // } + // break; + //} + + case Keys.Tab: { + if (!read_only && accepts_tab && document.multiline) { + document.InsertCharAtCaret ('\t', true); + + CaretMoved(this, null); + Modified = true; + OnTextChanged (EventArgs.Empty); + + return true; + } + break; + } + + case Keys.PageUp: { + if ((Widget.ModifierKeys & Keys.Widget) != 0) { + document.MoveCaret(CaretDirection.CtrlPgUp); + } else { + document.MoveCaret(CaretDirection.PgUp); + } + document.DisplayCaret (); + return true; + } + + case Keys.PageDown: { + if ((Widget.ModifierKeys & Keys.Widget) != 0) { + document.MoveCaret(CaretDirection.CtrlPgDn); + } else { + document.MoveCaret(CaretDirection.PgDn); + } + document.DisplayCaret (); + return true; + } + } + + return false; + } + + internal virtual void RaiseSelectionChanged () + { + // Do nothing, overridden in RTB + } + + private void HandleBackspace (bool Widget) + { + bool fire_changed; + + fire_changed = false; + + // delete only deletes on the line, doesn't do the combine + if (document.selection_visible) { + document.undo.BeginUserAction (String.Format ("Delete")); + document.ReplaceSelection("", false); + document.undo.EndUserAction (); + fire_changed = true; + document.SetSelectionToCaret (true); + } else { + document.SetSelectionToCaret (true); + + if (document.CaretPosition == 0) { + if (document.CaretLine.LineNo > 1) { + Line line; + int new_caret_pos; + + line = document.GetLine(document.CaretLine.LineNo - 1); + new_caret_pos = line.TextLengthWithoutEnding (); + + // Invalidate the old line position before we do the combine + document.Invalidate (line, 0, line, line.text.Length); + document.Combine(line, document.CaretLine); + + document.UpdateView(line, document.Lines - line.LineNo, 0); + document.PositionCaret(line, new_caret_pos); + document.SetSelectionToCaret (true); + document.UpdateCaret(); + fire_changed = true; + } + } else { + if (!Widget || document.CaretPosition == 0) { + + // Move before we delete because the delete will change positions around + // if we cross a wrap border + LineTag tag = document.CaretTag; + int pos = document.CaretPosition; + document.MoveCaret (CaretDirection.CharBack); + document.DeleteChar (tag.Line, pos, false); + document.SetSelectionToCaret (true); + } else { + int start_pos; + + + start_pos = document.CaretPosition - 1; + while ((start_pos > 0) && !Document.IsWordSeparator(document.CaretLine.Text[start_pos - 1])) { + start_pos--; + } + + document.undo.BeginUserAction (String.Format ("Delete")); + document.DeleteChars(document.CaretTag.Line, start_pos, document.CaretPosition - start_pos); + document.undo.EndUserAction (); + document.PositionCaret(document.CaretLine, start_pos); + document.SetSelectionToCaret (true); + } + document.UpdateCaret(); + fire_changed = true; + } + } + + CaretMoved (this, null); + + if (fire_changed) { + Modified = true; + OnTextChanged(EventArgs.Empty); + } + } + + private void HandleEnter () + { + // ignoring accepts_return, fixes bug #76355 + if (!read_only && document.multiline && (accepts_return || (FindForm() != null && FindForm().AcceptButton == null) || ((Widget.ModifierKeys & Keys.Widget) != 0))) { + Line line; + + if (document.selection_visible) + document.ReplaceSelection ("", false); + + line = document.CaretLine; + + document.Split (document.CaretLine, document.CaretTag, document.CaretPosition); + line.ending = document.StringToLineEnding (Environment.NewLine); + document.InsertString (line, line.text.Length, document.LineEndingToString (line.ending)); + + document.UpdateView (line, document.Lines - line.line_no, 0); + CaretMoved (this, null); + Modified = true; + OnTextChanged (EventArgs.Empty); + } + } + + protected override void SetBoundsCore (int x, int y, int width, int height, BoundsSpecified specified) + { + // Make sure we don't get sized bigger than we want to be + + if (!richtext) { + if (!document.multiline) { + if (height != PreferredHeight) { + // If the specified has Height, we need to store that in the + // ExplicitBounds because we are going to override it + if ((specified & BoundsSpecified.Height) != 0) { + Rectangle r = ExplicitBounds; + r.Height = height; + ExplicitBounds = r; + specified &= ~BoundsSpecified.Height; + } + + height = PreferredHeight; + } + } + } + + base.SetBoundsCore (x, y, width, height, specified); + } + + protected override void WndProc (ref Message m) + { + switch ((Msg)m.Msg) { + case Msg.WM_KEYDOWN: { + if (ProcessKeyMessage(ref m) || ProcessKey((Keys)m.WParam.ToInt32() | XplatUI.State.ModifierKeys)) { + m.Result = IntPtr.Zero; + return; + } + DefWndProc (ref m); + return; + } + + case Msg.WM_CHAR: { + int ch; + + if (ProcessKeyMessage(ref m)) { + m.Result = IntPtr.Zero; + return; + } + + if (read_only) { + return; + } + + m.Result = IntPtr.Zero; + + ch = m.WParam.ToInt32(); + + if (ch == 127) { + HandleBackspace(true); + } else if (ch >= 32) { + if (document.selection_visible) { + document.ReplaceSelection("", false); + } + + char c = (char)m.WParam; + switch (character_casing) { + case CharacterCasing.Upper: + c = Char.ToUpper((char) m.WParam); + break; + case CharacterCasing.Lower: + c = Char.ToLower((char) m.WParam); + break; + } + + if (document.Length < max_length) { + document.InsertCharAtCaret(c, true); + OnTextUpdate (); + CaretMoved (this, null); + Modified = true; + OnTextChanged(EventArgs.Empty); + + } else { + XplatUI.AudibleAlert(AlertType.Default); + } + return; + } else if (ch == 8) { + HandleBackspace(false); + } else if (ch == 13) + HandleEnter (); + + return; + } + + case Msg.WM_SETFOCUS: + base.WndProc(ref m); + document.CaretHasFocus (); + break; + + case Msg.WM_KILLFOCUS: + base.WndProc(ref m); + document.CaretLostFocus (); + break; + + case Msg.WM_NCPAINT: + if (!ThemeEngine.Current.TextBoxBaseHandleWmNcPaint (this, ref m)) + base.WndProc(ref m); + break; + + default: + base.WndProc(ref m); + return; + } + } + + #endregion // Protected Instance Methods + + #region Events + static object AcceptsTabChangedEvent = new object (); + static object AutoSizeChangedEvent = new object (); + static object BorderStyleChangedEvent = new object (); + static object HideSelectionChangedEvent = new object (); + static object ModifiedChangedEvent = new object (); + static object MultilineChangedEvent = new object (); + static object ReadOnlyChangedEvent = new object (); + static object HScrolledEvent = new object (); + static object VScrolledEvent = new object (); + + public event EventHandler AcceptsTabChanged { + add { Events.AddHandler (AcceptsTabChangedEvent, value); } + remove { Events.RemoveHandler (AcceptsTabChangedEvent, value); } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler AutoSizeChanged { + add { Events.AddHandler (AutoSizeChangedEvent, value); } + remove { Events.RemoveHandler (AutoSizeChangedEvent, value); } + } + + public event EventHandler BorderStyleChanged { + add { Events.AddHandler (BorderStyleChangedEvent, value); } + remove { Events.RemoveHandler (BorderStyleChangedEvent, value); } + } + + public event EventHandler HideSelectionChanged { + add { Events.AddHandler (HideSelectionChangedEvent, value); } + remove { Events.RemoveHandler (HideSelectionChangedEvent, value); } + } + + public event EventHandler ModifiedChanged { + add { Events.AddHandler (ModifiedChangedEvent, value); } + remove { Events.RemoveHandler (ModifiedChangedEvent, value); } + } + + public event EventHandler MultilineChanged { + add { Events.AddHandler (MultilineChangedEvent, value); } + remove { Events.RemoveHandler (MultilineChangedEvent, value); } + } + + public event EventHandler ReadOnlyChanged { + add { Events.AddHandler (ReadOnlyChangedEvent, value); } + remove { Events.RemoveHandler (ReadOnlyChangedEvent, value); } + } + + internal event EventHandler HScrolled { + add { Events.AddHandler (HScrolledEvent, value); } + remove { Events.RemoveHandler (HScrolledEvent, value); } + } + + internal event EventHandler VScrolled { + add { Events.AddHandler (VScrolledEvent, value); } + remove { Events.RemoveHandler (VScrolledEvent, value); } + } + + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageChanged { + add { base.BackgroundImageChanged += value; } + remove { base.BackgroundImageChanged -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + public new event EventHandler BackgroundImageLayoutChanged { + add { base.BackgroundImageLayoutChanged += value; } + remove { base.BackgroundImageLayoutChanged -= value; } + } + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event MouseEventHandler MouseClick { + add { base.MouseClick += value; } + remove { base.MouseClick -= value; } + } + + [Browsable (false)] + //[EditorBrowsable (EditorBrowsableState.Never)] + //[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] + public new event EventHandler PaddingChanged { + add { base.PaddingChanged += value; } + remove { base.PaddingChanged -= value; } + } + + [Browsable (true)] + //[EditorBrowsable (EditorBrowsableState.Always)] + public new event EventHandler Click { + add { base.Click += value; } + remove { base.Click -= value; } + } + + // XXX should this not manipulate base.Paint? +#pragma warning disable 0067 + [MonoTODO] + [Browsable(false)] + //[EditorBrowsable(EditorBrowsableState.Never)] + public new event PaintEventHandler Paint; +#pragma warning restore 0067 + + #endregion // Events + + #region Private Methods + internal Document Document { + get { + return document; + } + + set { + document = value; + } + } + + internal bool EnableLinks { + get { return enable_links; } + set { + enable_links = value; + + document.EnableLinks = value; + } + } + + internal override bool ScaleChildrenInternal { + get { return false; } + } + + internal bool ShowSelection { + get { + if (show_selection || !hide_selection) { + return true; + } + + return has_focus; + } + + set { + if (show_selection == value) + return; + + show_selection = value; + // Currently InvalidateSelectionArea is commented out so do a full invalidate + document.InvalidateSelectionArea(); + } + } + + internal int TopMargin { + get { + return document.top_margin; + } + set { + document.top_margin = value; + } + } + + #region UIA Framework Properties + + internal ScrollBar UIAHScrollBar { + get { return hscroll; } + } + + internal ScrollBar UIAVScrollBar { + get { return vscroll; } + } + + #endregion UIA Framework Properties + + internal Graphics CreateGraphicsInternal () + { + if (IsHandleCreated) + return base.CreateGraphics(); + + return DeviceContext; + } + + internal override void OnPaintInternal (PaintEventArgs pevent) + { + Draw (pevent.Graphics, pevent.ClipRectangle); + + // + // OnPaint does not get raised on MS (see bug #80639) + // + pevent.Handled = true; + } + + internal void Draw (Graphics g, Rectangle clippingArea) + { + ThemeEngine.Current.TextBoxBaseFillBackground (this, g, clippingArea); + + // Draw the viewable document + document.Draw(g, clippingArea); + } + + private void FixupHeight () + { + if (!richtext) { + if (!document.multiline) { + if (PreferredHeight != Height) { + Height = PreferredHeight; + } + } + } + } + + private bool IsDoubleClick (MouseEventArgs e) + { + TimeSpan interval = DateTime.Now - click_last; + if (interval.TotalMilliseconds > SystemInformation.DoubleClickTime) + return false; + Size dcs = SystemInformation.DoubleClickSize; + if (e.X < click_point_x - dcs.Width / 2 || e.X > click_point_x + dcs.Width / 2) + return false; + if (e.Y < click_point_y - dcs.Height / 2 || e.Y > click_point_y + dcs.Height / 2) + return false; + return true; + } + + private void TextBoxBase_MouseDown (object sender, MouseEventArgs e) + { + bool dbliclick = false; + + if (e.Button == MouseButtons.Left) { + + // Special case when shift key is pressed and + // left mouse is clicked.. set selection from + // current cursor to mouse + if ((Widget.ModifierKeys & Keys.Shift) > 0) { + document.PositionCaret (e.X + document.ViewPortX, e.Y + document.ViewPortY); + document.SetSelectionToCaret (false); + document.DisplayCaret (); + return; + } + + dbliclick = IsDoubleClick (e); + + if (current_link != null) { + HandleLinkClicked (current_link); + return; + } + + //ensure nothing is selected anymore BEFORE we + //position the caret, so the caret is recreated + //(caret is only visible when nothing is selected) + if (document.selection_visible && dbliclick == false) { + document.SetSelectionToCaret (true); + click_mode = CaretSelection.Position; + } + + document.PositionCaret(e.X + document.ViewPortX, e.Y + document.ViewPortY); + + if (dbliclick) { + switch (click_mode) { + case CaretSelection.Position: + SelectWord (); + click_mode = CaretSelection.Word; + break; + case CaretSelection.Word: + + if (this is TextBox) { + document.SetSelectionToCaret (true); + click_mode = CaretSelection.Position; + break; + } + + document.ExpandSelection (CaretSelection.Line, false); + click_mode = CaretSelection.Line; + break; + case CaretSelection.Line: + + // Gotta do this first because Exanding to a word + // from a line doesn't really work + document.SetSelectionToCaret (true); + + SelectWord (); + click_mode = CaretSelection.Word; + break; + } + } else { + document.SetSelectionToCaret (true); + click_mode = CaretSelection.Position; + } + + click_point_x = e.X; + click_point_y = e.Y; + click_last = DateTime.Now; + } + + if ((e.Button == MouseButtons.Middle) && XplatUI.RunningOnUnix) { + Document.Marker marker; + + marker.tag = document.FindCursor(e.X + document.ViewPortX, e.Y + document.ViewPortY, out marker.pos); + marker.line = marker.tag.Line; + marker.height = marker.tag.Height; + + document.SetSelection(marker.line, marker.pos, marker.line, marker.pos); + Paste (Clipboard.GetDataObject (true), null, true); + } + } + + private void TextBoxBase_MouseUp (object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) { + if (click_mode == CaretSelection.Position) { + document.SetSelectionToCaret(false); + document.DisplayCaret(); + + // Only raise if there is text. + if (Text.Length > 0) + RaiseSelectionChanged (); + } + + if (scroll_timer != null) { + scroll_timer.Enabled = false; + } + return; + } + } + + private void SizeWidgets () + { + if (hscroll.Visible) { + //vscroll.Maximum += hscroll.Height; + canvas_height = ClientSize.Height - hscroll.Height; + } else { + canvas_height = ClientSize.Height; + } + + if (vscroll.Visible) { + //hscroll.Maximum += vscroll.Width; + canvas_width = ClientSize.Width - vscroll.Width; + + if (GetInheritedRtoL () == RightToLeft.Yes) { + document.OffsetX = vscroll.Width; + } else { + document.OffsetX = 0; + } + + } else { + canvas_width = ClientSize.Width; + document.OffsetX = 0; + } + + document.ViewPortWidth = canvas_width; + document.ViewPortHeight = canvas_height; + } + + private void PositionWidgets () + { + if (canvas_height < 1 || canvas_width < 1) + return; + + int hmod = vscroll.Visible ? vscroll.Width : 0; + int vmod = hscroll.Visible ? hscroll.Height : 0; + + if (GetInheritedRtoL () == RightToLeft.Yes) { + hscroll.Bounds = new Rectangle (ClientRectangle.Left + hmod, + Math.Max(0, ClientRectangle.Height - hscroll.Height), + ClientSize.Width, + hscroll.Height); + + vscroll.Bounds = new Rectangle (ClientRectangle.Left, + ClientRectangle.Top, + vscroll.Width, + Math.Max(0, ClientSize.Height - (vmod))); + } else { + hscroll.Bounds = new Rectangle (ClientRectangle.Left, + Math.Max(0, ClientRectangle.Height - hscroll.Height), + Math.Max(0, ClientSize.Width - hmod), + hscroll.Height); + + vscroll.Bounds = new Rectangle ( + Math.Max(0, ClientRectangle.Right - vscroll.Width), + ClientRectangle.Top, + vscroll.Width, + Math.Max(0, ClientSize.Height - vmod)); + } + } + + internal RightToLeft GetInheritedRtoL () + { + for (Widget c = this; c != null; c = c.Parent) + if (c.RightToLeft != RightToLeft.Inherit) + return c.RightToLeft; + return RightToLeft.No; + } + + private void TextBoxBase_SizeChanged (object sender, EventArgs e) + { + if (IsHandleCreated) + CalculateDocument (); + } + + private void TextBoxBase_RightToLeftChanged (object o, EventArgs e) + { + if (IsHandleCreated) + CalculateDocument (); + } + + private void TextBoxBase_MouseWheel (object sender, MouseEventArgs e) + { + if (!vscroll.Enabled) + return; + + if (e.Delta < 0) + vscroll.Value = Math.Min (vscroll.Value + SystemInformation.MouseWheelScrollLines * 5, + Math.Max (0, vscroll.Maximum - document.ViewPortHeight + 1)); + else + vscroll.Value = Math.Max (0, vscroll.Value - SystemInformation.MouseWheelScrollLines * 5); + } + + internal virtual void SelectWord () + { + StringBuilder s = document.caret.line.text; + int start = document.caret.pos; + int end = document.caret.pos; + + if (s.Length < 1) { + if (document.caret.line.line_no >= document.Lines) + return; + Line line = document.GetLine (document.caret.line.line_no + 1); + document.PositionCaret (line, 0); + return; + } + + if (start > 0) { + start--; + end--; + } + + // skip whitespace until we hit a word + while (start > 0 && s [start] == ' ') + start--; + if (start > 0) { + while (start > 0 && (s [start] != ' ')) + start--; + if (s [start] == ' ') + start++; + } + + if (s [end] == ' ') { + while (end < s.Length && s [end] == ' ') + end++; + } else { + while (end < s.Length && s [end] != ' ') + end++; + while (end < s.Length && s [end] == ' ') + end++; + } + + document.SetSelection (document.caret.line, start, document.caret.line, end); + document.PositionCaret (document.selection_end.line, document.selection_end.pos); + document.DisplayCaret(); + } + + internal void CalculateDocument() + { + CalculateScrollBars (); + document.RecalculateDocument (CreateGraphicsInternal ()); + + + if (document.caret.line != null && document.caret.line.Y < document.ViewPortHeight) { + // The window has probably been resized, making the entire thing visible, so + // we need to set the scroll position back to zero. + vscroll.Value = 0; + } + + Invalidate(); + } + + internal void CalculateScrollBars () + { + // FIXME - need separate calculations for center and right alignment + SizeWidgets (); + + if (document.Width >= document.ViewPortWidth) { + hscroll.SetValues (0, Math.Max (1, document.Width), -1, + document.ViewPortWidth < 0 ? 0 : document.ViewPortWidth); + if (document.multiline) + hscroll.Enabled = true; + } else { + hscroll.Enabled = false; + hscroll.Maximum = document.ViewPortWidth; + } + + if (document.Height >= document.ViewPortHeight) { + vscroll.SetValues (0, Math.Max (1, document.Height), -1, + document.ViewPortHeight < 0 ? 0 : document.ViewPortHeight); + if (document.multiline) + vscroll.Enabled = true; + } else { + vscroll.Enabled = false; + vscroll.Maximum = document.ViewPortHeight; + } + + if (!WordWrap) { + switch (scrollbars) { + case RichTextBoxScrollBars.Both: + case RichTextBoxScrollBars.Horizontal: + if (richtext) + hscroll.Visible = hscroll.Enabled; + else + hscroll.Visible = this.Multiline; + break; + case RichTextBoxScrollBars.ForcedBoth: + case RichTextBoxScrollBars.ForcedHorizontal: + hscroll.Visible = true; + break; + default: + hscroll.Visible = false; + break; + } + } else { + hscroll.Visible = false; + } + + switch (scrollbars) { + case RichTextBoxScrollBars.Both: + case RichTextBoxScrollBars.Vertical: + if (richtext) + vscroll.Visible = vscroll.Enabled; + else + vscroll.Visible = this.Multiline; + break; + case RichTextBoxScrollBars.ForcedBoth: + case RichTextBoxScrollBars.ForcedVertical: + vscroll.Visible = true; + break; + default: + vscroll.Visible = false; + break; + } + + PositionWidgets (); + + SizeWidgets (); //Update sizings now we've decided whats visible + } + + private void document_WidthChanged (object sender, EventArgs e) + { + CalculateScrollBars(); + } + + private void document_HeightChanged (object sender, EventArgs e) + { + CalculateScrollBars(); + } + + private void ScrollLinks (int xChange, int yChange) + { + foreach (LinkRectangle link in list_links) + link.Scroll (xChange, yChange); + } + + private void hscroll_ValueChanged (object sender, EventArgs e) + { + int old_viewport_x; + + old_viewport_x = document.ViewPortX; + document.ViewPortX = this.hscroll.Value; + + // + // Before scrolling we want to destroy the caret, then draw a new one after the scroll + // the reason for this is that scrolling changes the coordinates of the caret, and we + // will get tracers if we don't + // + if (Focused) + document.CaretLostFocus (); + + if (vscroll.Visible) { + if (GetInheritedRtoL () == RightToLeft.Yes) { + XplatUI.ScrollWindow (this.Handle, new Rectangle (vscroll.Width, 0, ClientSize.Width - vscroll.Width, ClientSize.Height), old_viewport_x - this.hscroll.Value, 0, false); + } else { + XplatUI.ScrollWindow (this.Handle, new Rectangle (0, 0, ClientSize.Width - vscroll.Width, ClientSize.Height), old_viewport_x - this.hscroll.Value, 0, false); + } + } else { + XplatUI.ScrollWindow(this.Handle, ClientRectangle, old_viewport_x - this.hscroll.Value, 0, false); + } + + ScrollLinks (old_viewport_x - this.hscroll.Value, 0); + + if (Focused) + document.CaretHasFocus (); + + EventHandler eh = (EventHandler)(Events [HScrolledEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + + private void vscroll_ValueChanged (object sender, EventArgs e) + { + int old_viewport_y; + + old_viewport_y = document.ViewPortY; + document.ViewPortY = this.vscroll.Value; + + // + // Before scrolling we want to destroy the caret, then draw a new one after the scroll + // the reason for this is that scrolling changes the coordinates of the caret, and we + // will get tracers if we don't + // + if (Focused) + document.CaretLostFocus (); + + if (hscroll.Visible) { + XplatUI.ScrollWindow(this.Handle, new Rectangle(0, 0, ClientSize.Width, ClientSize.Height - hscroll.Height), 0, old_viewport_y - this.vscroll.Value, false); + } else { + XplatUI.ScrollWindow(this.Handle, ClientRectangle, 0, old_viewport_y - this.vscroll.Value, false); + } + + ScrollLinks (0, old_viewport_y - this.vscroll.Value); + + if (Focused) + document.CaretHasFocus (); + + EventHandler eh = (EventHandler)(Events [VScrolledEvent]); + if (eh != null) + eh (this, EventArgs.Empty); + } + + private void TextBoxBase_MouseMove (object sender, MouseEventArgs e) + { + // FIXME - handle auto-scrolling if mouse is to the right/left of the window + if (e.Button == MouseButtons.Left && Capture) { + if (!ClientRectangle.Contains (e.X, e.Y)) { + if (scroll_timer == null) { + scroll_timer = new Timer (); + scroll_timer.Interval = 100; + scroll_timer.Tick += new EventHandler (ScrollTimerTickHandler); + } + + if (!scroll_timer.Enabled) { + scroll_timer.Start (); + + // Force the first tick + ScrollTimerTickHandler (null, EventArgs.Empty); + } + } + + document.PositionCaret(e.X + document.ViewPortX, e.Y + document.ViewPortY); + if (click_mode == CaretSelection.Position) { + document.SetSelectionToCaret(false); + document.DisplayCaret(); + } + } + + //search through link boxes to see if the mouse is over one of them + + bool found_link = false; + foreach (LinkRectangle link in list_links) { + if (link.LinkAreaRectangle.Contains (e.X, e.Y)) { + XplatUI.SetCursor (window.Handle, Cursors.Hand.handle); + + found_link = true; + current_link = link; + break; + } + } + + if (found_link == false) { + XplatUI.SetCursor (window.Handle, DefaultCursor.handle); + current_link = null; + } + } + + private void TextBoxBase_FontOrColorChanged (object sender, EventArgs e) + { + Line line; + + document.SuspendRecalc (); + // Font changes apply to the whole document + for (int i = 1; i <= document.Lines; i++) { + line = document.GetLine(i); + if (LineTag.FormatText(line, 1, line.text.Length, Font, ForeColor, + Color.Empty, FormatSpecified.Font | FormatSpecified.Color)) + document.RecalculateDocument (CreateGraphicsInternal (), line.LineNo, line.LineNo, false); + } + document.ResumeRecalc (false); + + // Make sure the caret height is matching the new font height + document.AlignCaret(); + } + + private void ScrollTimerTickHandler (object sender, EventArgs e) + { + Point pt = Cursor.Position; + + pt = PointToClient (pt); + + if (pt.X < ClientRectangle.Left) { + document.MoveCaret(CaretDirection.CharBackNoWrap); + document.SetSelectionToCaret(false); + + CaretMoved(this, null); + } else if (pt.X > ClientRectangle.Right) { + document.MoveCaret(CaretDirection.CharForwardNoWrap); + document.SetSelectionToCaret(false); + + CaretMoved(this, null); + } else if (pt.Y > ClientRectangle.Bottom) { + document.MoveCaret(CaretDirection.LineDown); + document.SetSelectionToCaret(false); + + CaretMoved(this, null); + } else if (pt.Y < ClientRectangle.Top) { + document.MoveCaret(CaretDirection.LineUp); + document.SetSelectionToCaret(false); + + CaretMoved(this, null); + } + } + + /// <summary>Ensure the caret is always visible</summary> + internal void CaretMoved (object sender, EventArgs e) + { + Point pos; + int height; + + if (!IsHandleCreated || canvas_width < 1 || canvas_height < 1) + return; + + document.MoveCaretToTextTag (); + pos = document.Caret; + + //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); + + + // Horizontal scrolling: + // If the caret moves to the left outside the visible area, we jump the document into view, not just one + // character, but 1/3 of the width of the document + // If the caret moves to the right outside the visible area, we scroll just enough to keep the caret visible + + // Handle horizontal scrolling + if (document.CaretLine.alignment == HorizontalAlignment.Left) { + // Check if we moved out of view to the left + if (pos.X < (document.ViewPortX)) { + do { + if ((hscroll.Value - document.ViewPortWidth / 3) >= hscroll.Minimum) { + hscroll.SafeValueSet (hscroll.Value - document.ViewPortWidth / 3); + } else { + hscroll.Value = hscroll.Minimum; + } + } while (hscroll.Value > pos.X); + } + + // Check if we moved out of view to the right + if ((pos.X >= (document.ViewPortWidth + document.ViewPortX)) && (hscroll.Value != hscroll.Maximum)) { + if ((pos.X - document.ViewPortWidth + 1) <= hscroll.Maximum) { + if (pos.X - document.ViewPortWidth >= 0) { + hscroll.SafeValueSet (pos.X - document.ViewPortWidth + 1); + } else { + hscroll.Value = 0; + } + } else { + hscroll.Value = hscroll.Maximum; + } + } + } else if (document.CaretLine.alignment == HorizontalAlignment.Right) { +// hscroll.Value = pos.X; + +// if ((pos.X > (this.canvas_width + document.ViewPortX)) && (hscroll.Enabled && (hscroll.Value != hscroll.Maximum))) { +// hscroll.Value = hscroll.Maximum; +// } + } else { + // FIXME - implement center cursor alignment + } + + if (Text.Length > 0) + RaiseSelectionChanged (); + + if (!document.multiline) + return; + + // Handle vertical scrolling + height = document.CaretLine.Height + 1; + + if (pos.Y < document.ViewPortY) + vscroll.SafeValueSet (pos.Y); + if ((pos.Y + height) > (document.ViewPortY + canvas_height)) + vscroll.Value = Math.Min (vscroll.Maximum, pos.Y - canvas_height + height); + } + + internal bool Paste (IDataObject clip, DataFormats.Format format, bool obey_length) + { + string s; + + if (clip == null) + return false; + + if (format == null) { + if ((this is RichTextBox) && clip.GetDataPresent(DataFormats.Rtf)) { + format = DataFormats.GetFormat(DataFormats.Rtf); + } else if ((this is RichTextBox) && clip.GetDataPresent (DataFormats.Bitmap)) { + format = DataFormats.GetFormat (DataFormats.Bitmap); + } else if (clip.GetDataPresent(DataFormats.UnicodeText)) { + format = DataFormats.GetFormat(DataFormats.UnicodeText); + } else if (clip.GetDataPresent(DataFormats.Text)) { + format = DataFormats.GetFormat(DataFormats.Text); + } else { + return false; + } + } else { + if ((format.Name == DataFormats.Rtf) && !(this is RichTextBox)) { + return false; + } + + if (!clip.GetDataPresent(format.Name)) { + return false; + } + } + + if (format.Name == DataFormats.Rtf) { + document.undo.BeginUserAction (String.Format ("Paste")); + ((RichTextBox)this).SelectedRtf = (string)clip.GetData(DataFormats.Rtf); + document.undo.EndUserAction (); + Modified = true; + return true; + } else if (format.Name == DataFormats.Bitmap) { + document.undo.BeginUserAction (String.Format ("Paste")); + // document.InsertImage (document.caret.line, document.caret.pos, (Image) clip.GetData (DataFormats.Bitmap)); + document.MoveCaret (CaretDirection.CharForward); + document.undo.EndUserAction (); + return true; + } else if (format.Name == DataFormats.UnicodeText) { + s = (string)clip.GetData(DataFormats.UnicodeText); + } else if (format.Name == DataFormats.Text) { + s = (string)clip.GetData(DataFormats.Text); + } else { + return false; + } + + if (!obey_length) { + document.undo.BeginUserAction (String.Format ("Paste")); + this.SelectedText = s; + document.undo.EndUserAction (); + } else { + if ((s.Length + (document.Length - SelectedText.Length)) < max_length) { + document.undo.BeginUserAction (String.Format ("Paste")); + this.SelectedText = s; + document.undo.EndUserAction (); + } else if ((document.Length - SelectedText.Length) < max_length) { + document.undo.BeginUserAction (String.Format ("Paste")); + this.SelectedText = s.Substring (0, max_length - (document.Length - SelectedText.Length)); + document.undo.EndUserAction (); + } + } + + Modified = true; + return true; + } + + internal virtual Color ChangeBackColor (Color backColor) + { + return backColor; + } + + internal override bool IsInputCharInternal (char charCode) + { + return true; + } + #endregion // Private Methods + + #region Private Classes + internal class LinkRectangle { + private Rectangle link_area_rectangle; + private LineTag link_tag; + + public LinkRectangle (Rectangle rect) + { + link_tag = null; + link_area_rectangle = rect; + } + + public Rectangle LinkAreaRectangle { + get { return link_area_rectangle; } + set { link_area_rectangle = value; } + } + + public LineTag LinkTag { + get { return link_tag; } + set { link_tag = value; } + } + + public void Scroll (int x_change, int y_change) + { + link_area_rectangle.X += x_change; + link_area_rectangle.Y += y_change; + } + } + #endregion + + // This is called just before OnTextChanged is called. + internal virtual void OnTextUpdate () + { + } + + protected override void OnTextChanged (EventArgs e) + { + base.OnTextChanged (e); + } + + public virtual int GetLineFromCharIndex (int index) + { + Line line_out; + LineTag tag_out; + int pos; + + document.CharIndexToLineTag (index, out line_out, out tag_out, out pos); + + return line_out.LineNo; + } + + protected override void OnMouseUp (MouseEventArgs mevent) + { + base.OnMouseUp (mevent); + } + } +} |
