New tests.
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / RichTextBox.cs
index 7ee21a6314c8e872a6c9e638f05b0adc8dfe5dcf..1db7f29a519e0819d7696faeb9cff1a41f001da9 100644 (file)
@@ -48,6 +48,7 @@ namespace System.Windows.Forms {
                internal bool           auto_word_select;
                internal int            bullet_indent;
                internal bool           detect_urls;
+               private bool            reuse_line;     // Sometimes we are loading text with already available lines
                internal int            margin_right;
                internal float          zoom;
                private StringBuilder   rtf_line;
@@ -76,7 +77,7 @@ namespace System.Windows.Forms {
                        auto_size = false;
                        auto_word_select = false;
                        bullet_indent = 0;
-                       max_length = Int32.MaxValue;
+                       base.MaxLength = Int32.MaxValue;
                        margin_right = 0;
                        zoom = 1;
                        base.Multiline = true;
@@ -132,6 +133,11 @@ namespace System.Windows.Forms {
                        return backColor;
                }
 
+               internal override void RaiseSelectionChanged()
+               {
+                       OnSelectionChanged (EventArgs.Empty);
+               }
+               
                private void RichTextBox_LostFocus(object sender, EventArgs e) {
                        Invalidate();
                }
@@ -224,7 +230,7 @@ namespace System.Windows.Forms {
                }
 
 #if NET_2_0
-               [MonoTODO ("Stub")]
+               [MonoTODO ("Stub, does nothing")]
                [DefaultValue (false)]
                public bool EnableAutoDragDrop {
                        get { return enable_auto_drag_drop; }
@@ -269,7 +275,7 @@ namespace System.Windows.Forms {
                }
 
 #if NET_2_0
-               [MonoTODO ("Stub")]
+               [MonoTODO ("Stub, does nothing")]
                [Browsable (false)]
                [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
                public RichTextBoxLanguageOptions LanguageOption {
@@ -280,13 +286,8 @@ namespace System.Windows.Forms {
 
                [DefaultValue(Int32.MaxValue)]
                public override int MaxLength {
-                       get {
-                               return base.max_length;
-                       }
-
-                       set {
-                               base.max_length = value;
-                       }
+                       get { return base.MaxLength; }
+                       set { base.MaxLength = value; }
                }
 
                [DefaultValue(true)]
@@ -302,7 +303,6 @@ namespace System.Windows.Forms {
 
                [Browsable(false)]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-               [MonoTODO]
                public string RedoActionName {
                        get {
                                return document.undo.RedoActionName;
@@ -310,7 +310,7 @@ namespace System.Windows.Forms {
                }
 
 #if NET_2_0
-               [MonoTODO ("Stub")]
+               [MonoTODO ("Stub, does nothing")]
                [Browsable (false)]
                [DefaultValue (true)]
                [EditorBrowsable (EditorBrowsableState.Never)]
@@ -322,7 +322,8 @@ namespace System.Windows.Forms {
 
                [DefaultValue(0)]
                [Localizable(true)]
-               [MonoTODO("Teach TextControl.RecalculateLine to consider the right margin as well")]
+               [MonoTODO ("Stub, does nothing")]
+               [MonoInternalNote ("Teach TextControl.RecalculateLine to consider the right margin as well")]
                public int RightMargin {
                        get {
                                return margin_right;
@@ -407,10 +408,24 @@ namespace System.Windows.Forms {
                                sel_start = document.LineTagToCharIndex(document.selection_start.line, document.selection_start.pos);
 
                                data = new MemoryStream(Encoding.ASCII.GetBytes(value), false);
-                               InsertRTFFromStream(data, document.selection_start.pos, document.selection_start.line.line_no, out x, out y, out chars);
+                               int cursor_x = document.selection_start.pos;
+                               int cursor_y = document.selection_start.line.line_no;
+
+                               // The RFT parser by default, when finds our x cursor in 0, it thinks if needs to
+                               // add a new line; but in *this* scenario the line is already created, so force it to reuse it.
+                               // Hackish, but works without touching the heart of the buggy parser.
+                               if (cursor_x == 0)
+                                       reuse_line = true;
+
+                               InsertRTFFromStream(data, cursor_x, cursor_y, out x, out y, out chars);
                                data.Close();
 
-                               document.CharIndexToLineTag(sel_start + chars + (y - document.selection_start.line.line_no) * 2, out line, out tag, out sel_start);
+                               int nl_length = document.LineEndingLength (XplatUI.RunningOnUnix ? LineEnding.Rich : LineEnding.Hard);
+                               document.CharIndexToLineTag(sel_start + chars + (y - document.selection_start.line.line_no) * nl_length, 
+                                               out line, out tag, out sel_start);
+                               if (sel_start >= line.text.Length)
+                                       sel_start = line.text.Length -1;
+
                                document.SetSelection(line, sel_start);
                                document.PositionCaret(line, sel_start);
                                document.DisplayCaret();
@@ -428,6 +443,8 @@ namespace System.Windows.Forms {
                        }
 
                        set {
+                               // TextBox/TextBoxBase don't set Modified in this same property
+                               Modified = true;
                                base.SelectedText = value;
                        }
                }
@@ -487,7 +504,7 @@ namespace System.Windows.Forms {
                }
 
 #if NET_2_0
-               [MonoTODO ("Stub")]
+               [MonoTODO ("Stub, does nothing")]
                [Browsable (false)]
                [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
                public Color SelectionBackColor {
@@ -499,7 +516,7 @@ namespace System.Windows.Forms {
                [Browsable(false)]
                [DefaultValue(false)]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-               [MonoTODO]
+               [MonoTODO ("Stub, does nothing")]
                public bool SelectionBullet {
                        get {
                                return false;
@@ -512,7 +529,7 @@ namespace System.Windows.Forms {
                [Browsable(false)]
                [DefaultValue(0)]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-               [MonoTODO]
+               [MonoTODO ("Stub, does nothing")]
                public int SelectionCharOffset {
                        get {
                                return 0;
@@ -641,7 +658,7 @@ namespace System.Windows.Forms {
                [Browsable(false)]
                [DefaultValue(0)]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-               [MonoTODO]
+               [MonoTODO ("Stub, does nothing")]
                public int SelectionHangingIndent {
                        get {
                                return 0;
@@ -654,7 +671,7 @@ namespace System.Windows.Forms {
                [Browsable(false)]
                [DefaultValue(0)]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-               [MonoTODO]
+               [MonoTODO ("Stub, does nothing")]
                public int SelectionIndent {
                        get {
                                return 0;
@@ -679,7 +696,7 @@ namespace System.Windows.Forms {
                [Browsable(false)]
                [DefaultValue(false)]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-               [MonoTODO]
+               [MonoTODO ("Stub, does nothing")]
                public bool SelectionProtected {
                        get {
                                return false;
@@ -692,7 +709,7 @@ namespace System.Windows.Forms {
                [Browsable(false)]
                [DefaultValue(0)]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-               [MonoTODO]
+               [MonoTODO ("Stub, does nothing")]
                public int SelectionRightIndent {
                        get {
                                return 0;
@@ -704,7 +721,7 @@ namespace System.Windows.Forms {
 
                [Browsable(false)]
                [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
-               [MonoTODO]
+               [MonoTODO ("Stub, does nothing")]
                public int[] SelectionTabs {
                        get {
                                return new int[0];
@@ -732,7 +749,7 @@ namespace System.Windows.Forms {
                }
 
                [DefaultValue(false)]
-               [MonoTODO]
+               [MonoTODO ("Stub, does nothing")]
                public bool ShowSelectionMargin {
                        get {
                                return false;
@@ -1006,6 +1023,11 @@ namespace System.Windows.Forms {
                                        sb.Append (buffer, 0, charsRead);
                                        charsRead = sr.Read (buffer, 0, buffer.Length);
                                }
+
+                               // Remove the EOF converted to an extra EOL by the StreamReader
+                               if (sb.Length > 0 && sb [sb.Length - 1] == '\n')
+                                       sb.Remove (sb.Length - 1, 1);
+
                                base.Text = sb.ToString();
                                return;
                        }
@@ -1017,13 +1039,8 @@ namespace System.Windows.Forms {
                        ScrollToCaret ();
                }
 
-               [MonoTODO("Make smarter RTF detection?")]
                public void LoadFile(string path) {
-                       if (path.EndsWith(".rtf")) {
-                               LoadFile(path, RichTextBoxStreamType.RichText);
-                       } else {
-                               LoadFile(path, RichTextBoxStreamType.PlainText);
-                       }
+                       LoadFile (path, RichTextBoxStreamType.RichText);
                }
 
                public void LoadFile(string path, RichTextBoxStreamType fileType) {
@@ -1082,7 +1099,9 @@ namespace System.Windows.Forms {
                                        }
 
                                        for (i = 1; i < document.Lines; i++) {
-                                               bytes = encoding.GetBytes(document.GetLine(i).text.ToString() + Environment.NewLine);
+                                               // Normalize the new lines to the system ones
+                                               string line_text = document.GetLine (i).TextWithoutEnding () + Environment.NewLine;
+                                               bytes = encoding.GetBytes(line_text);
                                                data.Write(bytes, 0, bytes.Length);
                                        }
                                        bytes = encoding.GetBytes(document.GetLine(document.Lines).text.ToString());
@@ -1190,7 +1209,7 @@ namespace System.Windows.Forms {
                                eh (this, e);
                }
 
-               [MonoTODO("Determine when to call this")]
+               [MonoTODO ("Stub, never called")]
                protected virtual void OnImeChange(EventArgs e) {
                        EventHandler eh = (EventHandler)(Events [ImeChangeEvent]);
                        if (eh != null)
@@ -1354,7 +1373,7 @@ namespace System.Windows.Forms {
                        remove { base.QueryContinueDrag -= value; }
                }
 
-               [MonoTODO("Currently does not ever fire")]
+               [MonoTODO ("Event never raised")]
                public event EventHandler SelectionChanged {
                        add { Events.AddHandler (SelectionChangedEvent, value); }
                        remove { Events.RemoveHandler (SelectionChangedEvent, value); }
@@ -1424,7 +1443,7 @@ namespace System.Windows.Forms {
                        }
                }
 
-               [MonoTODO("Add QuadJust support for justified alignment")]
+               [MonoInternalNote ("Add QuadJust support for justified alignment")]
                private void HandleControl(RTF.RTF rtf) {
                        switch(rtf.Major) {
                                case RTF.Major.Unicode: {
@@ -1742,11 +1761,11 @@ namespace System.Windows.Forms {
 
                        rtf_chars += rtf_line.Length;
 
-
-
-                       if (rtf_cursor_x == 0) {
-                               if (newline && rtf_line.ToString ().EndsWith ("\n") == false)
-                                       rtf_line.Append ("\n");
+                       // Try to re-use if we are told so - this usually happens when we are inserting a flow of rtf text
+                       // with an already alive line.
+                       if (rtf_cursor_x == 0 && !reuse_line) {
+                               if (newline && rtf_line.ToString ().EndsWith (Environment.NewLine) == false)
+                                       rtf_line.Append (Environment.NewLine);
 
                                document.Add (rtf_cursor_y, rtf_line.ToString (), rtf_style.rtf_rtfalign, font, rtf_style.rtf_color,
                                                                newline ? LineEnding.Rich : LineEnding.Wrap);
@@ -1766,13 +1785,14 @@ namespace System.Windows.Forms {
                                                        FormatSpecified.Font | FormatSpecified.Color);
                                }
                                if (newline) {
-                                       document.Split (line, rtf_cursor_x + length);
                                        line = document.GetLine (rtf_cursor_y);
                                        line.ending = LineEnding.Rich;
 
-                                       if (line.Text.EndsWith ("\n") == false)
-                                               line.Text += "\n";
+                                       if (line.Text.EndsWith (Environment.NewLine) == false)
+                                               line.Text += Environment.NewLine;
                                }
+
+                               reuse_line = false; // sanity assignment - in this case we have already re-used one line.
                        }
 
                        if (newline) {
@@ -1928,9 +1948,55 @@ namespace System.Windows.Forms {
                        }
                }
 
-               [MonoTODO("Emit unicode and other special characters properly")]
+               static readonly char [] ReservedRTFChars = new char [] { '\\', '{', '}' };
+
                private void EmitRTFText(StringBuilder rtf, string text) {
-                       rtf.Append(text);
+                       int start = rtf.Length;
+                       int count = text.Length;
+
+                       // First emit simple unicode chars as escaped
+                       EmitEscapedUnicode (rtf, text);
+
+                       // This method emits user text *only*, so it's safe to escape any reserved rtf chars
+                       // Escape '\' first, since it is used later to escape the other chars
+                       if (text.IndexOfAny (ReservedRTFChars) > -1) {
+                               rtf.Replace ("\\", "\\\\", start, count);
+                               rtf.Replace ("{", "\\{", start, count);
+                               rtf.Replace ("}", "\\}", start, count);
+                       }
+               }
+
+               // The chars to be escaped use "\'" + its hexadecimal value.
+               private void EmitEscapedUnicode (StringBuilder sb, string text)
+               {
+                       int pos;
+                       int start = 0;
+
+                       while ((pos = IndexOfNonAscii (text, start)) > -1) {
+                               sb.Append (text, start, pos - start);
+
+                               int n = (int)text [pos];
+                               sb.Append ("\\'");
+                               sb.Append (n.ToString ("X"));
+
+                               start = pos + 1;
+                       }
+
+                       // Append remaining (maybe all) the text value.
+                       if (start < text.Length)
+                               sb.Append (text, start, text.Length - start);
+               }
+
+               // MS seems to be escaping values larger than 0x80
+               private int IndexOfNonAscii (string text, int startIndex)
+               {
+                       for (int i = startIndex; i < text.Length; i++) {
+                               int n = (int)text [i];
+                               if (n < 0 || n >= 0x80)
+                                       return i;
+                       }
+
+                       return -1;
                }
 
                // start_pos and end_pos are 0-based
@@ -2007,7 +2073,7 @@ namespace System.Windows.Forms {
                        sb.Append(String.Format("\\deff{0}", fonts.IndexOf(this.Font.Name)));
 
                        // Default Language 
-                       sb.Append("\\deflang1033\n");   // FIXME - always 1033?
+                       sb.Append("\\deflang1033" + Environment.NewLine);       // FIXME - always 1033?
 
                        // Emit the font table
                        sb.Append("{\\fonttbl");
@@ -2018,7 +2084,8 @@ namespace System.Windows.Forms {
                                sb.Append((string)fonts[i]);            // Font name
                                sb.Append(";}");                        // }
                        }
-                       sb.Append("}\n");
+                       sb.Append("}");
+                       sb.Append(Environment.NewLine);
 
                        // Emit the color table (if needed)
                        if ((colors.Count > 1) || ((((Color)colors[0]).R != this.ForeColor.R) || (((Color)colors[0]).G != this.ForeColor.G) || (((Color)colors[0]).B != this.ForeColor.B))) {
@@ -2029,7 +2096,8 @@ namespace System.Windows.Forms {
                                        sb.Append(String.Format("\\blue{0}", ((Color)colors[i]).B));
                                        sb.Append(";");
                                }
-                               sb.Append("}\n");
+                               sb.Append("}");
+                               sb.Append(Environment.NewLine);
                        }
 
                        sb.Append("{\\*\\generator Mono RichTextBox;}");
@@ -2088,14 +2156,16 @@ namespace System.Windows.Forms {
                                }
                                if (pos >= line.text.Length) {
                                        if (line.ending != LineEnding.Wrap) {
-                                               sb.Append("\\par\n");
+                                               sb.Append("\\par");
+                                               sb.Append(Environment.NewLine);
                                        }
                                }
                                pos = 0;
                                line_no++;
                        }
 
-                       sb.Append("}\n");
+                       sb.Append("}");
+                       sb.Append(Environment.NewLine);
 
                        return sb;
                }