Fix for the issue of getting occasional -5875 error on the server when
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / LineTag.cs
1 // Permission is hereby granted, free of charge, to any person obtaining\r
2 // a copy of this software and associated documentation files (the\r
3 // "Software"), to deal in the Software without restriction, including\r
4 // without limitation the rights to use, copy, modify, merge, publish,\r
5 // distribute, sublicense, and/or sell copies of the Software, and to\r
6 // permit persons to whom the Software is furnished to do so, subject to\r
7 // the following conditions:\r
8 // \r
9 // The above copyright notice and this permission notice shall be\r
10 // included in all copies or substantial portions of the Software.\r
11 // \r
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
19 //\r
20 // Copyright (c) 2004-2006 Novell, Inc. (http://www.novell.com)\r
21 //\r
22 // Authors:\r
23 //      Peter Bartok    pbartok@novell.com\r
24 //\r
25 //\r
26 \r
27 using System;\r
28 using System.Collections;\r
29 using System.Drawing;\r
30 using System.Drawing.Text;\r
31 using System.Text;\r
32 \r
33 namespace System.Windows.Forms\r
34 {\r
35         internal class LineTag\r
36         {\r
37                 #region Local Variables\r
38                 // Formatting\r
39                 private Font            font;           // System.Drawing.Font object for this tag\r
40                 private Color           color;          // The font color for this tag\r
41                 private Color           back_color;     // In 2.0 tags can have background colours.\r
42 \r
43                 // Payload; text\r
44                 private int             start;          // start, in chars; index into Line.text\r
45 \r
46                 // Drawing support\r
47                 private int             height;         // Height in pixels of the text this tag describes\r
48                 private int             ascent;         // Ascent of the font for this tag\r
49                 private int             descent;        // Descent of the font for this tag\r
50                 private int             shift;          // Shift down for this tag, to stay on baseline\r
51 \r
52                 // Administrative\r
53                 private Line            line;           // The line we're on\r
54                 private LineTag         next;           // Next tag on the same line\r
55                 private LineTag         previous;       // Previous tag on the same line\r
56                 #endregion\r
57 \r
58                 #region Constructors\r
59                 public LineTag (Line line, int start)\r
60                 {\r
61                         this.line = line;\r
62                         this.start = start;\r
63                 }\r
64                 #endregion      // Constructors\r
65 \r
66                 #region Public Properties\r
67                 public int Ascent {\r
68                         get { return ascent; }\r
69                 }\r
70                 \r
71                 public Color BackColor {\r
72                         get { return back_color; }\r
73                         set { back_color = value; }\r
74                 }\r
75                 \r
76                 public Color Color {\r
77                         get { return color; }\r
78                         set { color = value; }\r
79                 }\r
80                 \r
81                 public int Descent {\r
82                         get { return descent; }\r
83                 }\r
84 \r
85                 public int End {\r
86                         get { return start + Length; }\r
87                 }\r
88 \r
89                 public Font Font {\r
90                         get { return font; }\r
91                         set { \r
92                                 if (font != value) {\r
93                                         font = value;           \r
94                                         height = font.Height;\r
95                                         XplatUI.GetFontMetrics (Hwnd.bmp_g, font, out ascent, out descent);\r
96                                         line.recalc = true;\r
97                                 }\r
98                         }\r
99                 }\r
100 \r
101                 public int Height {\r
102                         get { return height; }\r
103                         set { height = value; }\r
104                 }\r
105 \r
106                 public virtual bool IsTextTag {\r
107                         get { return true; }\r
108                 }\r
109 \r
110                 public int Length {\r
111                         get {\r
112                                 int res = 0;\r
113                                 if (next != null)\r
114                                         res = next.start - start;\r
115                                 else\r
116                                         res = line.text.Length - (start - 1);\r
117 \r
118                                 return res > 0 ? res : 0;\r
119                         }\r
120                 }\r
121 \r
122                 public Line Line {\r
123                         get { return line; }\r
124                         set { line = value; }\r
125                 }\r
126 \r
127                 public LineTag Next {\r
128                         get { return next; }\r
129                         set { next = value; }\r
130                 }\r
131 \r
132                 public LineTag Previous {\r
133                         get { return previous; }\r
134                         set { previous = value; }\r
135                 }\r
136 \r
137                 public int Shift {\r
138                         get { return shift; }\r
139                         set { shift = value; }\r
140                 }\r
141 \r
142                 public int Start {\r
143                         get { return start; }\r
144                         set { start = value; }\r
145                 }\r
146 \r
147                 public int TextEnd {\r
148                         get { return start + TextLength; }\r
149                 }\r
150 \r
151                 public int TextLength {\r
152                         get {\r
153                                 int res = 0;\r
154                                 if (next != null)\r
155                                         res = next.start - start;\r
156                                 else\r
157                                         res = line.TextLengthWithoutEnding () - (start - 1);\r
158 \r
159                                 return res > 0 ? res : 0;\r
160                         }\r
161                 }\r
162 \r
163                 public float Width {\r
164                         get {\r
165                                 if (Length == 0)\r
166                                         return 0;\r
167                                 return line.widths [start + Length - 1] - (start != 0 ? line.widths [start - 1] : 0);\r
168                         }\r
169                 }\r
170 \r
171                 public float X {\r
172                         get {\r
173                                 if (start == 0)\r
174                                         return line.X;\r
175                                 return line.X + line.widths [start - 1];\r
176                         }\r
177                 }\r
178                 #endregion\r
179                 \r
180                 #region Public Methods\r
181                 ///<summary>Break a tag into two with identical attributes; pos is 1-based; returns tag starting at &gt;pos&lt; or null if end-of-line</summary>\r
182                 public LineTag Break (int pos)\r
183                 {\r
184                         LineTag new_tag;\r
185 \r
186                         // Sanity\r
187                         if (pos == this.start)\r
188                                 return this;\r
189                         else if (pos >= (start + Length))\r
190                                 return null;\r
191 \r
192                         new_tag = new LineTag(line, pos);\r
193                         new_tag.CopyFormattingFrom (this);\r
194 \r
195                         new_tag.next = this.next;\r
196                         this.next = new_tag;\r
197                         new_tag.previous = this;\r
198 \r
199                         if (new_tag.next != null)\r
200                                 new_tag.next.previous = new_tag;\r
201 \r
202                         return new_tag;\r
203                 }\r
204 \r
205                 /// <summary>Combines 'this' tag with 'other' tag</summary>\r
206                 public bool Combine (LineTag other)\r
207                 {\r
208                         if (!this.Equals (other))\r
209                                 return false;\r
210 \r
211                         this.next = other.next;\r
212                         \r
213                         if (this.next != null)\r
214                                 this.next.previous = this;\r
215 \r
216                         return true;\r
217                 }\r
218 \r
219                 public void CopyFormattingFrom (LineTag other)\r
220                 {\r
221                         Font = other.font;\r
222                         color = other.color;\r
223                         back_color = other.back_color;\r
224                 }\r
225 \r
226                 public void Delete ()\r
227                 {\r
228                         // If we are the only tag, we can't be deleted\r
229                         if (previous == null && next == null)\r
230                                 return;\r
231                                 \r
232                         // If we are the last tag, deletion is easy\r
233                         if (next == null) {\r
234                                 previous.next = null;\r
235                                 return;\r
236                         }\r
237                         \r
238                         // Easy cases gone, little tougher, delete ourself\r
239                         // Update links, and start\r
240                         next.previous = null;\r
241                         \r
242                         LineTag loop = next;\r
243                         \r
244                         while (loop != null) {\r
245                                 loop.Start -= Length;\r
246                                 loop = loop.next;                       \r
247                         }\r
248                         \r
249                         return;\r
250                 }\r
251                 \r
252                 public virtual void Draw (Graphics dc, Color color, float x, float y, int start, int end)\r
253                 {\r
254                         TextBoxTextRenderer.DrawText (dc, line.text.ToString (start, end).Replace ("\r", string.Empty), font, color, x, y, false);\r
255                 }\r
256 \r
257                 public virtual void Draw (Graphics dc, Color color, float xoff, float y, int start, int end, string text)\r
258                 {\r
259                         while (start < end) {\r
260                                 int tab_index = text.IndexOf ("\t", start);\r
261                                 if (tab_index == -1)\r
262                                         tab_index = end;\r
263 \r
264                                 TextBoxTextRenderer.DrawText (dc, text.Substring (start, tab_index - start).Replace ("\r", string.Empty), font, color, xoff + line.widths[start], y, false);\r
265 \r
266                                 // non multilines get the unknown char \r
267                                 if (!line.document.multiline && tab_index != end)\r
268                                         TextBoxTextRenderer.DrawText (dc, "\u0013", font, color, xoff + line.widths[tab_index], y, true);\r
269 \r
270                                 start = tab_index + 1;\r
271                         }\r
272                 }\r
273 \r
274                 /// <summary>Checks if 'this' tag describes the same formatting options as 'obj'</summary>\r
275                 public override bool Equals (object obj)\r
276                 {\r
277                         LineTag other;\r
278 \r
279                         if (obj == null)\r
280                                 return false;\r
281 \r
282                         if (!(obj is LineTag))\r
283                                 return false;\r
284 \r
285                         if (obj == this)\r
286                                 return true;\r
287 \r
288                         other = (LineTag)obj;\r
289 \r
290                         if (other.IsTextTag != IsTextTag)\r
291                                 return false;\r
292 \r
293                         if (this.font.Equals (other.font) && this.color.Equals (other.color))   // FIXME add checking for things like link or type later\r
294                                 return true;\r
295 \r
296                         return false;\r
297                 }\r
298 \r
299                 /// <summary>Finds the tag that describes the character at position 'pos' on 'line'</summary>\r
300                 public static LineTag FindTag (Line line, int pos)\r
301                 {\r
302                         LineTag tag = line.tags;\r
303 \r
304                         // Beginning of line is a bit special\r
305                         if (pos == 0)\r
306                                 return tag;     // Not sure if we should get the final tag here\r
307 \r
308                         while (tag != null) {\r
309                                 if ((tag.start <= pos) && (pos <= tag.End))\r
310                                         return GetFinalTag (tag);\r
311 \r
312                                 tag = tag.next;\r
313                         }\r
314 \r
315                         return null;\r
316                 }\r
317 \r
318                 /// <summary>Applies 'font' and 'brush' to characters starting at 'start' for 'length' chars; \r
319                 /// Removes any previous tags overlapping the same area; \r
320                 /// returns true if lineheight has changed</summary>\r
321                 /// <param name="start">1-based character position on line</param>\r
322                 public static bool FormatText (Line line, int start, int length, Font font, Color color, Color back_color, FormatSpecified specified)\r
323                 {\r
324                         LineTag tag;\r
325                         LineTag start_tag;\r
326                         LineTag end_tag;\r
327                         int end;\r
328                         bool retval = false;            // Assume line-height doesn't change\r
329 \r
330                         // Too simple?\r
331                         if (((FormatSpecified.Font & specified) == FormatSpecified.Font) && font.Height != line.height)\r
332                                 retval = true;\r
333 \r
334                         line.recalc = true;             // This forces recalculation of the line in RecalculateDocument\r
335 \r
336                         // A little sanity, not sure if it's needed, might be able to remove for speed\r
337                         if (length > line.text.Length)\r
338                                 length = line.text.Length;\r
339 \r
340                         tag = line.tags;\r
341                         end = start + length;\r
342 \r
343                         // Common special case\r
344                         if ((start == 1) && (length == tag.Length)) {\r
345                                 tag.ascent = 0;\r
346                                 SetFormat (tag, font, color, back_color, specified);\r
347                                 return retval;\r
348                         }\r
349 \r
350                         start_tag = FindTag (line, start);\r
351                         tag = start_tag.Break (start);\r
352 \r
353                         while (tag != null && tag.End <= end) {\r
354                                 SetFormat (tag, font, color, back_color, specified);\r
355                                 tag = tag.next;\r
356                         }\r
357 \r
358                         if (tag != null && tag.End == end)\r
359                                 return retval;\r
360 \r
361                         /// Now do the last tag\r
362                         end_tag = FindTag (line, end);\r
363 \r
364                         if (end_tag != null) {\r
365                                 end_tag.Break (end);\r
366                                 SetFormat (end_tag, font, color, back_color, specified);\r
367                         }\r
368 \r
369                         return retval;\r
370                 }\r
371 \r
372                 // Gets the character at the x-coordinate.  Index is based from the\r
373                 // line, not the start of the tag.\r
374                 public int GetCharIndex (int x)\r
375                 {\r
376                         int low = start;\r
377                         int high = low + Length;\r
378 \r
379                         if (Length == 0)\r
380                                 return start;\r
381                                 \r
382                         if (x < line.widths[low])\r
383                                 return low - 1;\r
384                                 \r
385                         if (x > line.widths[line.TextLengthWithoutEnding ()])\r
386                                 return line.TextWithoutEnding ().Length;\r
387                                 \r
388                         while (low < high - 1) {\r
389                                 int mid = (high + low) / 2;\r
390                                 float width = line.widths[mid];\r
391 \r
392                                 if (width < x)\r
393                                         low = mid;\r
394                                 else\r
395                                         high = mid;\r
396                         }\r
397 \r
398                         float char_width = line.widths[high] - line.widths[low];\r
399 \r
400                         if ((x - line.widths[low]) >= (char_width / 2))\r
401                                 return high;\r
402                         else\r
403                                 return low;     \r
404                 }\r
405                 \r
406                 // There can be multiple tags at the same position, we want to make\r
407                 // sure we are using the very last tag at the given position\r
408                 public static LineTag GetFinalTag (LineTag tag)\r
409                 {\r
410                         LineTag res = tag;\r
411 \r
412                         while (res.Length == 0 && res.next != null && res.next.Length == 0)\r
413                                 res = res.next;\r
414 \r
415                         return res;\r
416                 }\r
417 \r
418                 public override int GetHashCode ()\r
419                 {\r
420                         return base.GetHashCode ();\r
421                 }\r
422                 \r
423                 internal virtual int MaxHeight ()\r
424                 {\r
425                         return font.Height;\r
426                 }\r
427 \r
428                 private static void SetFormat (LineTag tag, Font font, Color color, Color back_color, FormatSpecified specified)\r
429                 {\r
430                         if ((FormatSpecified.Font & specified) == FormatSpecified.Font)\r
431                                 tag.Font = font;\r
432                         if ((FormatSpecified.Color & specified) == FormatSpecified.Color)\r
433                                 tag.color = color;\r
434                         if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) {\r
435                                 tag.back_color = back_color;\r
436                         }\r
437                         // Console.WriteLine ("setting format:   {0}  {1}   new color {2}", color.Color, specified, tag.color.Color);\r
438                 }\r
439 \r
440                 public virtual SizeF SizeOfPosition (Graphics dc, int pos)\r
441                 {\r
442                         if (pos >= line.TextLengthWithoutEnding () && line.document.multiline)\r
443                                 return SizeF.Empty;\r
444 \r
445                         string text = line.text.ToString (pos, 1);\r
446                         switch ((int) text [0]) {\r
447                         case '\t':\r
448                                 if (!line.document.multiline)\r
449                                         goto case 10;\r
450                                 SizeF res = TextBoxTextRenderer.MeasureText (dc, " ", font); \r
451                                 res.Width *= 8.0F;\r
452                                 return res;\r
453                         case 10:\r
454                         case 13:\r
455                                 return TextBoxTextRenderer.MeasureText (dc, "\u000D", font);\r
456                         }\r
457                         \r
458                         return TextBoxTextRenderer.MeasureText (dc, text, font);\r
459                 }\r
460 \r
461                 public virtual string Text ()\r
462                 {\r
463                         return line.text.ToString (start - 1, Length);\r
464                 }\r
465 \r
466                 public override string ToString ()\r
467                 {\r
468                         if (Length > 0)\r
469                                 return string.Format ("{0} Tag starts at index: {1}, length: {2}, text: {3}, font: {4}", GetType (), start, Length, Text (), font.ToString ());\r
470                                 \r
471                         return string.Format ("Zero Length tag at index: {0}", start);\r
472                 }\r
473                 #endregion      // Internal Methods\r
474         }\r
475 }\r