// NOT COMPLETE
+// There's still plenty of things missing, I've got most of it planned, just hadn't had
+// the time to write it all yet.
+// Stuff missing (in no particular order):
+// - Align text after RecalculateLine
+// - Implement tag types to support wrapping (ie have a 'newline' tag), for images, etc.
+// - Wrap and recalculate lines
+// - Implement CaretPgUp/PgDown
+// - Finish selection calculations (invalidate only changed, more ways to select)
+// - Implement C&P
+
+
#undef Debug
using System;
using System.Text;
namespace System.Windows.Forms {
- public enum LineColor {
+ internal enum LineColor {
Red = 0,
Black = 1
}
- public enum CaretDirection {
+ internal enum CaretDirection {
CharForward, // Move a char to the right
CharBack, // Move a char to the left
LineUp, // Move a line up
}
// Being cloneable should allow for nice line and document copies...
- public class Line : ICloneable, IComparable {
+ internal class Line : ICloneable, IComparable {
#region Local Variables
// Stuff that matters for our line
internal StringBuilder text; // Characters for the line
internal int Y; // Baseline
internal int height; // Height of the line (height of tallest tag)
internal int ascent; // Ascent of the line (ascent of the tallest tag)
+ internal HorizontalAlignment alignment; // Alignment of the line
+ internal int align_shift; // Pixel shift caused by the alignment
// Stuff that's important for the tree
internal Line parent; // Our parent line
- public Line left; // Line with smaller line number
- public Line right; // Line with higher line number
+ internal Line left; // Line with smaller line number
+ internal Line right; // Line with higher line number
internal LineColor color; // We're doing a black/red tree. this is the node color
internal int DEFAULT_TEXT_LEN; //
internal static StringFormat string_format; // For calculating widths/heights
#endregion // Local Variables
#region Constructors
- public Line() {
+ internal Line() {
color = LineColor.Red;
left = null;
right = null;
parent = null;
text = null;
recalc = true;
+ alignment = HorizontalAlignment.Left;
if (string_format == null) {
string_format = new StringFormat(StringFormat.GenericTypographic);
}
}
- public Line(int LineNo, string Text, Font font) : this() {
+ internal Line(int LineNo, string Text, Font font, Brush color) : this() {
space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
text = new StringBuilder(Text, space);
widths = new float[space + 1];
tags = new LineTag(this, 1, text.Length);
tags.font = font;
+ tags.color = color;
}
- public Line(int LineNo, string Text, LineTag tag) : this() {
+ internal Line(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) : this() {
+ space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
+
+ text = new StringBuilder(Text, space);
+ line_no = LineNo;
+ alignment = align;
+
+ widths = new float[space + 1];
+ tags = new LineTag(this, 1, text.Length);
+ tags.font = font;
+ tags.color = color;
+ }
+
+ internal Line(int LineNo, string Text, LineTag tag) : this() {
space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN;
text = new StringBuilder(Text, space);
#endregion // Constructors
- #region Public Properties
- public int Height {
+ #region Internal Properties
+ internal int Height {
get {
return height;
}
}
}
- public int LineNo {
+ internal int LineNo {
get {
return line_no;
}
}
}
- public string Text {
+ internal string Text {
get {
return text.ToString();
}
text = new StringBuilder(value, value.Length > DEFAULT_TEXT_LEN ? value.Length : DEFAULT_TEXT_LEN);
}
}
+
+ internal HorizontalAlignment Alignment {
+ get {
+ return alignment;
+ }
+
+ set {
+ if (alignment != value) {
+ alignment = value;
+ recalc = true;
+ }
+ }
+ }
#if no
- public StringBuilder Text {
+ internal StringBuilder Text {
get {
return text;
}
}
}
#endif
- #endregion // Public Properties
+ #endregion // Internal Properties
- #region Public Methods
+ #region Internal Methods
// Make sure we always have enoughs space in text and widths
- public void Grow(int minimum) {
+ internal void Grow(int minimum) {
int length;
float[] new_widths;
}
}
- public void Streamline() {
+ internal void Streamline() {
LineTag current;
LineTag next;
}
// Find the tag on a line based on the character position
- public LineTag FindTag(int pos) {
+ internal LineTag FindTag(int pos) {
LineTag tag;
if (pos == 0) {
// Go through all tags on a line and recalculate all size-related values
// returns true if lineheight changed
//
- public bool RecalculateLine(Graphics g) {
+ internal bool RecalculateLine(Graphics g) {
LineTag tag;
int pos;
int len;
if (pos == (tag.start-1 + tag.length)) {
// We just found the end of our current tag
tag.height = (int)tag.font.Height;
+
// Check if we're the tallest on the line (so far)
if (tag.height > this.height) {
this.height = tag.height; // Yep; make sure the line knows
LineTag t;
// We have a tag that has a taller ascent than the line;
+
t = tags;
while (t != tag) {
- t.shift += tag.ascent - this.ascent;
+ t.shift = tag.ascent - t.ascent;
t = t.next;
}
tag = tag.next;
if (tag != null) {
tag.width = 0;
+ tag.shift = 0;
}
}
}
}
return false;
}
- #endregion // Public Methods
+ #endregion // Internal Methods
#region Administrative
public int CompareTo(object obj) {
return clone;
}
- public object CloneLine() {
+ internal object CloneLine() {
Line clone;
clone = new Line();
return false;
}
+ public override int GetHashCode() {\r
+ return base.GetHashCode ();\r
+ }\r
public override string ToString() {
return "Line " + line_no;
#endregion // Administrative
}
- public class Document : ICloneable, IEnumerable {
+ internal class Document : ICloneable, IEnumerable {
#region Structures
internal struct Marker {
internal Line line;
#region Local Variables
private Line document;
private int lines;
- private static Line sentinel;
+ private Line sentinel;
private Line last_found;
private int document_id;
private Random random = new Random();
internal bool multiline;
-
- private Line selection_start_line;
- private int selection_start_pos;
- private Line selection_end_line;
- private int selection_end_pos;
+ internal bool wrap;
internal Marker caret;
internal Marker selection_start;
internal Marker selection_end;
+ internal bool selection_visible;
internal int viewport_x;
internal int viewport_y; // The visible area of the document
#endregion // Local Variables
#region Constructors
- public Document(Control owner) {
+ internal Document(Control owner) {
lines = 0;
this.owner = owner;
last_found = sentinel;
// We always have a blank line
- Add(1, "", owner.Font);
+ Add(1, "", owner.Font, new SolidBrush(owner.ForeColor));
this.RecalculateDocument(owner.CreateGraphics());
PositionCaret(0, 0);
lines=1;
+ selection_visible = false;
+ selection_start.line = this.document;
+ selection_start.pos = 0;
+ selection_end.line = this.document;
+ selection_end.pos = 0;
+
+
+
// Default selection is empty
document_id = random.Next();
}
#endregion
- #region Public Properties
- public Line Root {
+ #region Internal Properties
+ internal Line Root {
get {
return document;
}
}
}
- public int Lines {
+ internal int Lines {
get {
return lines;
}
}
- public Line CaretLine {
+ internal Line CaretLine {
get {
return caret.line;
}
}
- public int CaretPosition {
+ internal int CaretPosition {
get {
return caret.pos;
}
}
- public LineTag CaretTag {
+ internal LineTag CaretTag {
get {
return caret.tag;
}
}
- public int ViewPortX {
+ internal int ViewPortX {
get {
return viewport_x;
}
}
}
- public int ViewPortY {
+ internal int ViewPortY {
get {
return viewport_y;
}
}
}
- #endregion // Public Properties
+ internal int Width {
+ get {
+ return this.document_x;
+ }
+ }
+
+ internal int Height {
+ get {
+ return this.document_y;
+ }
+ }
+
+ #endregion // Internal Properties
#region Private Methods
// For debugging
}
- public void UpdateView(Line line, int pos) {
- int prev_width;
-
- // This is an optimization; we need to invalidate
- prev_width = (int)line.widths[line.text.Length];
-
+ internal void UpdateView(Line line, int pos) {
if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no, true)) {
// Lineheight changed, invalidate the rest of the document
if ((line.Y - viewport_y) >=0 ) {
owner.Invalidate();
}
} else {
- owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x, line.Y - viewport_y, (int)owner.Width, line.height));
+ owner.Invalidate(new Rectangle((int)line.widths[pos] - viewport_x - 1, line.Y - viewport_y, (int)owner.Width, line.height));
}
}
// Update display from line, down line_count lines; pos is unused, but required for the signature
- public void UpdateView(Line line, int line_count, int pos) {
- int prev_width;
-
- // This is an optimization; we need to invalidate
- prev_width = (int)line.widths[line.text.Length];
-
+ internal void UpdateView(Line line, int line_count, int pos) {
if (RecalculateDocument(owner.CreateGraphics(), line.line_no, line.line_no + line_count - 1, true)) {
// Lineheight changed, invalidate the rest of the document
if ((line.Y - viewport_y) >=0 ) {
}
#endregion // Private Methods
- #region Public Methods
- public void PositionCaret(Line line, int pos) {
+ #region Internal Methods
+ // Clear the document and reset state
+ internal void Empty() {
+
+ document = sentinel;
+ last_found = sentinel;
+ lines = 0;
+
+ // We always have a blank line
+ Add(1, "", owner.Font, new SolidBrush(owner.ForeColor));
+ this.RecalculateDocument(owner.CreateGraphics());
+ PositionCaret(0, 0);
+
+ selection_visible = false;
+ selection_start.line = this.document;
+ selection_start.pos = 0;
+ selection_end.line = this.document;
+ selection_end.pos = 0;
+
+ viewport_x = 0;
+ viewport_y = 0;
+
+ document_x = 0;
+ document_y = 0;
+ }
+
+ internal void PositionCaret(Line line, int pos) {
caret.tag = line.FindTag(pos);
caret.line = line;
caret.pos = pos;
XplatUI.DestroyCaret(owner.Handle);
XplatUI.CreateCaret(owner.Handle, 2, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
+ XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y);
}
- public void PositionCaret(int x, int y) {
- caret.tag = FindCursor(x, y, out caret.pos);
+ internal void PositionCaret(int x, int y) {
+ caret.tag = FindCursor(x + viewport_x, y + viewport_y, out caret.pos);
caret.line = caret.tag.line;
caret.height = caret.tag.height;
XplatUI.DestroyCaret(owner.Handle);
XplatUI.CreateCaret(owner.Handle, 2, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
+ XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y);
}
- public void CaretHasFocus() {
+ internal void CaretHasFocus() {
if (caret.tag != null) {
XplatUI.CreateCaret(owner.Handle, 2, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
+ XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y);
XplatUI.CaretVisible(owner.Handle, true);
}
}
- public void CaretLostFocus() {
+ internal void CaretLostFocus() {
XplatUI.DestroyCaret(owner.Handle);
}
- public void AlignCaret() {
+ internal void AlignCaret() {
caret.tag = LineTag.FindTag(caret.line, caret.pos);
caret.height = caret.tag.height;
XplatUI.CreateCaret(owner.Handle, 2, caret.height);
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
+ XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y);
XplatUI.CaretVisible(owner.Handle, true);
}
- public void UpdateCaret() {
+ internal void UpdateCaret() {
if (caret.tag.height != caret.height) {
caret.height = caret.tag.height;
XplatUI.CreateCaret(owner.Handle, 2, caret.height);
}
- XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos], caret.tag.line.Y + caret.tag.shift);
+ XplatUI.SetCaretPos(owner.Handle, (int)caret.tag.line.widths[caret.pos] + caret.line.align_shift - viewport_x, caret.line.Y + caret.tag.shift - viewport_y);
XplatUI.CaretVisible(owner.Handle, true);
}
- public void DisplayCaret() {
+ internal void DisplayCaret() {
XplatUI.CaretVisible(owner.Handle, true);
}
- public void HideCaret() {
+ internal void HideCaret() {
XplatUI.CaretVisible(owner.Handle, false);
}
- public void MoveCaret(CaretDirection direction) {
+ internal void MoveCaret(CaretDirection direction) {
switch(direction) {
case CaretDirection.CharForward: {
caret.pos++;
case CaretDirection.WordBack: {
if (caret.pos > 0) {
- int len;
-
- len = caret.line.text.Length;
-
caret.pos--;
while ((caret.pos > 0) && (caret.line.text.ToString(caret.pos, 1) == " ")) {
}
// Draw the document
- public void Draw(Graphics g, Rectangle clip, Brush brush) {
+ internal void Draw(Graphics g, Rectangle clip) {
Line line; // Current line being drawn
LineTag tag; // Current tag being drawn
int start; // First line to draw
int end; // Last line to draw
string s; // String representing the current line
int line_no; //
+ Brush hilight;
+ Brush hilight_text;
// First, figure out from what line to what line we need to draw
start = GetLineByPixel(clip.Top - viewport_y, false).line_no;
Console.WriteLine("Started drawing: {0}s {1}ms", n.Second, n.Millisecond);
#endif
+ hilight = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHilight);
+ hilight_text = ThemeEngine.Current.ResPool.GetSolidBrush(ThemeEngine.Current.ColorHilightText);
+
while (line_no <= end) {
line = GetLine(line_no);
tag = line.tags;
s = line.text.ToString();
while (tag != null) {
if (((tag.X + tag.width) > (clip.Left - viewport_x)) || (tag.X < (clip.Right - viewport_x))) {
- g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, brush, tag.X - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
+ // Check for selection
+ if ((!selection_visible) || (!owner.has_focus) || (line_no < selection_start.line.line_no) || (line_no > selection_end.line.line_no)) {
+ // regular drawing
+ g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
+ } else {
+ // we might have to draw our selection
+ if ((line_no != selection_start.line.line_no) && (line_no != selection_end.line.line_no)) {
+ g.FillRectangle(hilight, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, line.widths[tag.start + tag.length - 1], tag.height);
+ g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, hilight_text, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
+ } else {
+ bool highlight;
+ bool partial;
+
+ highlight = false;
+ partial = false;
+
+ // Check the partial drawings first
+ if ((selection_start.tag == tag) && (selection_end.tag == tag)) {
+ partial = true;
+
+ // First, the regular part
+ g.DrawString(s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
+
+ // Now the highlight
+ g.FillRectangle(hilight, line.widths[selection_start.pos] + line.align_shift, line.Y + tag.shift - viewport_y, line.widths[selection_end.pos] - line.widths[selection_start.pos], tag.height);
+ g.DrawString(s.Substring(selection_start.pos, selection_end.pos - selection_start.pos), tag.font, hilight_text, line.widths[selection_start.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
+
+ // And back to the regular
+ g.DrawString(s.Substring(selection_end.pos, tag.length - selection_end.pos), tag.font, tag.color, line.widths[selection_end.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
+ } else if (selection_start.tag == tag) {
+ partial = true;
+
+ // The highlighted part
+ g.FillRectangle(hilight, line.widths[selection_start.pos] + line.align_shift, line.Y + tag.shift - viewport_y, line.widths[tag.start + tag.length - 1] - line.widths[selection_start.pos], tag.height);
+ g.DrawString(s.Substring(selection_start.pos, tag.length - selection_start.pos), tag.font, hilight_text, line.widths[selection_start.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
+
+ // The regular part
+ g.DrawString(s.Substring(tag.start - 1, selection_start.pos - tag.start + 1), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
+ } else if (selection_end.tag == tag) {
+ partial = true;
+
+ // The highlighted part
+ g.FillRectangle(hilight, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, line.widths[selection_end.pos], tag.height);
+ g.DrawString(s.Substring(tag.start - 1, selection_end.pos - tag.start + 1), tag.font, hilight_text, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
+
+ // The regular part
+ g.DrawString(s.Substring(selection_end.pos, tag.length - selection_end.pos), tag.font, tag.color, line.widths[selection_end.pos] + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
+ } else {
+ // no partially selected tags here, simple checks...
+ if (selection_start.line == line) {
+ if ((tag.start + tag.length - 1) > selection_start.pos) {
+ highlight = true;
+ }
+ }
+ if (selection_end.line == line) {
+ if ((tag.start + tag.length - 1) < selection_start.pos) {
+ highlight = true;
+ }
+ }
+ }
+
+ if (!partial) {
+ if (highlight) {
+ g.FillRectangle(hilight, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, line.widths[tag.start + tag.length - 1], tag.height);
+ g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, hilight_text, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
+ } else {
+ g.DrawString(s.Substring(tag.start-1, tag.length), tag.font, tag.color, tag.X + line.align_shift - viewport_x, line.Y + tag.shift - viewport_y, StringFormat.GenericTypographic);
+ }
+ }
+ }
+
+ }
}
tag = tag.next;
// Inserts a character at the given position
- public void InsertChar(Line line, int pos, char ch) {
+ internal void InsertString(Line line, int pos, string s) {
+ InsertString(line.FindTag(pos), pos, s);
+ }
+
+ // Inserts a string at the given position
+ internal void InsertString(LineTag tag, int pos, string s) {
+ Line line;
+ int len;
+
+ len = s.Length;
+
+ line = tag.line;
+ line.text.Insert(pos, s);
+ tag.length += len;
+
+ tag = tag.next;
+ while (tag != null) {
+ tag.start += len;
+ tag = tag.next;
+ }
+ line.Grow(len);
+ line.recalc = true;
+
+ UpdateView(line, pos);
+ }
+
+ // Inserts a string at the caret position
+ internal void InsertStringAtCaret(string s, bool move_caret) {
+ LineTag tag;
+ int len;
+
+ len = s.Length;
+
+ caret.line.text.Insert(caret.pos, s);
+ caret.tag.length += len;
+
+ if (caret.tag.next != null) {
+ tag = caret.tag.next;
+ while (tag != null) {
+ tag.start += len;
+ tag = tag.next;
+ }
+ }
+ caret.line.Grow(len);
+ caret.line.recalc = true;
+
+ UpdateView(caret.line, caret.pos);
+ if (move_caret) {
+ caret.pos += len;
+ UpdateCaret();
+ }
+ }
+
+
+
+ // Inserts a character at the given position
+ internal void InsertChar(Line line, int pos, char ch) {
InsertChar(line.FindTag(pos), pos, ch);
}
// Inserts a character at the given position
- public void InsertChar(LineTag tag, int pos, char ch) {
+ internal void InsertChar(LineTag tag, int pos, char ch) {
Line line;
line = tag.line;
UpdateView(line, pos);
}
- // Inserts a character at the given position
- public void InsertCharAtCaret(char ch, bool move_caret) {
+ // Inserts a character at the current caret position
+ internal void InsertCharAtCaret(char ch, bool move_caret) {
LineTag tag;
caret.line.text.Insert(caret.pos, ch);
}
}
-
- // Inserts a character at the given position; it will not delete past line limits
- public void DeleteChar(LineTag tag, int pos, bool forward) {
+ // Inserts n characters at the given position; it will not delete past line limits
+ internal void DeleteChars(LineTag tag, int pos, int count) {
Line line;
bool streamline;
+ streamline = false;
+ line = tag.line;
+
+ if (pos == line.text.Length) {
+ return;
+ }
+
+ line.text.Remove(pos, count);
+
+ // Check if we're crossing tag boundaries
+ if ((pos + count) > (tag.start + tag.length)) {
+ int left;
+
+ // We have to delete cross tag boundaries
+ streamline = true;
+ left = count;
+
+ left -= pos - tag.start;
+ tag.length -= pos - tag.start;
+
+ tag = tag.next;
+ while ((tag != null) && (left > 0)) {
+ if (tag.length > left) {
+ tag.length -= left;
+ left = 0;
+ } else {
+ left -= tag.length;
+ tag.length = 0;
+
+ tag = tag.next;
+ }
+ }
+ } else {
+ // We got off easy, same tag
+
+ tag.length -= count;
+ }
+
+ tag = tag.next;
+ while (tag != null) {
+ tag.start -= count;
+ tag = tag.next;
+ }
+
+ line.recalc = true;
+ if (streamline) {
+ line.Streamline();
+ }
+
+ UpdateView(line, pos);
+ }
+
+
+ // Deletes a character at or after the given position (depending on forward); it will not delete past line limits
+ internal void DeleteChar(LineTag tag, int pos, bool forward) {
+ Line line;
+ bool streamline;
+
streamline = false;
line = tag.line;
tag = tag.next;
}
line.recalc = true;
- line.Streamline();
+ if (streamline) {
+ line.Streamline();
+ }
UpdateView(line, pos);
}
// Combine two lines
- public void Combine(int FirstLine, int SecondLine) {
+ internal void Combine(int FirstLine, int SecondLine) {
Combine(GetLine(FirstLine), GetLine(SecondLine));
}
- public void Combine(Line first, Line second) {
+ internal void Combine(Line first, Line second) {
LineTag last;
int shift;
}
// Split the line at the position into two
- public void Split(int LineNo, int pos) {
+ internal void Split(int LineNo, int pos) {
Line line;
LineTag tag;
Split(line, tag, pos);
}
- public void Split(Line line, int pos) {
+ internal void Split(Line line, int pos) {
LineTag tag;
tag = LineTag.FindTag(line, pos);
Split(line, tag, pos);
}
- public void Split(Line line, LineTag tag, int pos) {
+ internal void Split(Line line, LineTag tag, int pos) {
LineTag new_tag;
Line new_line;
// cover the easy case first
if (pos == line.text.Length) {
- Add(line.line_no + 1, "", tag.font);
+ Add(line.line_no + 1, "", line.alignment, tag.font, tag.color);
return;
}
// We need to move the rest of the text into the new line
- Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), tag.font);
+ Add(line.line_no + 1, line.text.ToString(pos, line.text.Length - pos), line.alignment, tag.font, tag.color);
// Now transfer our tags from this line to the next
new_line = GetLine(line.line_no + 1);
if (tag == line.tags) {
new_tag = new LineTag(line, 1, 0);
new_tag.font = tag.font;
+ new_tag.color = tag.color;
line.tags = new_tag;
}
new_tag = new LineTag(new_line, 1, tag.start - 1 + tag.length - pos);
new_tag.next = tag.next;
new_tag.font = tag.font;
+ new_tag.color = tag.color;
new_line.tags = new_tag;
if (new_tag.next != null) {
new_tag.next.previous = new_tag;
// Adds a line of text, with given font.
// Bumps any line at that line number that already exists down
- public void Add(int LineNo, string Text, Font font) {
+ internal void Add(int LineNo, string Text, Font font, Brush color) {
+ Add(LineNo, Text, HorizontalAlignment.Left, font, color);
+ }
+
+ internal void Add(int LineNo, string Text, HorizontalAlignment align, Font font, Brush color) {
Line add;
Line line;
int line_no;
}
}
- add = new Line(LineNo, Text, font);
+ add = new Line(LineNo, Text, align, font, color);
line = document;
while (line != sentinel) {
lines++;
}
- public virtual void Clear() {
+ internal virtual void Clear() {
lines = 0;
document = sentinel;
}
return clone;
}
- public void Delete(int LineNo) {
+ internal void Delete(int LineNo) {
if (LineNo>lines) {
return;
}
Delete(GetLine(LineNo));
}
- public void Delete(Line line1) {
+ internal void Delete(Line line1) {
Line line2;// = new Line();
Line line3;
}
// Set our selection markers
- public void SetSelection(Line start, int start_pos, Line end, int end_pos) {
- selection_start_line = start;
- selection_start_pos = start_pos;
- selection_end_line = end;
- selection_end_pos = end_pos;
+ internal void Invalidate(Line start, int start_pos, Line end, int end_pos) {
+ Line l1;
+ Line l2;
+ int p1;
+ int p2;
+
+ // figure out what's before what so the logic below is straightforward
+ if (start.line_no < end.line_no) {
+ l1 = start;
+ p1 = start_pos;
+
+ l2 = end;
+ p2 = end_pos;
+ } else if (start.line_no > end.line_no) {
+ l1 = end;
+ p1 = end_pos;
+
+ l2 = start;
+ p2 = start_pos;
+ } else {
+ if (start_pos < end_pos) {
+ l1 = start;
+ p1 = start_pos;
+
+ l2 = end;
+ p2 = end_pos;
+ } else {
+ l1 = end;
+ p1 = end_pos;
+
+ l2 = start;
+ p2 = start_pos;
+ }
+
+ owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, (int)l2.widths[p2] - (int)l1.widths[p1], l1.height));
+ return;
+ }
+
+ // Three invalidates:
+ // First line from start
+ owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, owner.Width, l1.height));
+
+ // lines inbetween
+ if ((l1.line_no + 1) < l2.line_no) {
+ int y;
+
+ y = GetLine(l1.line_no + 1).Y;
+ owner.Invalidate(new Rectangle(0 - viewport_x, y - viewport_y, owner.Width, GetLine(l2.line_no - 1).Y - y - viewport_y));
+ }
+
+ // Last line to end
+ owner.Invalidate(new Rectangle((int)l1.widths[p1] - viewport_x, l1.Y - viewport_y, owner.Width, l1.height));
+
+
}
- public void SetSelection(Line start, int start_pos) {
- selection_start_line = start;
- selection_start_pos = start_pos;
- selection_end_line = start;
- selection_end_pos = start_pos;
+ // It's nothing short of pathetic to always invalidate the whole control
+ // I will find time to finish the optimization and make it invalidate deltas only
+ internal void SetSelectionToCaret(bool start) {
+ if (start) {
+ selection_start.line = caret.line;
+ selection_start.tag = caret.tag;
+ selection_start.pos = caret.pos;
+
+ // start always also selects end
+ selection_end.line = caret.line;
+ selection_end.tag = caret.tag;
+ selection_end.pos = caret.pos;
+ } else {
+ if (caret.line.line_no <= selection_end.line.line_no) {
+ if ((caret.line != selection_end.line) || (caret.pos < selection_end.pos)) {
+ selection_start.line = caret.line;
+ selection_start.tag = caret.tag;
+ selection_start.pos = caret.pos;
+ } else {
+ selection_end.line = caret.line;
+ selection_end.tag = caret.tag;
+ selection_end.pos = caret.pos;
+ }
+ } else {
+ selection_end.line = caret.line;
+ selection_end.tag = caret.tag;
+ selection_end.pos = caret.pos;
+ }
+ }
+
+
+ if ((selection_start.line == selection_end.line) && (selection_start.pos == selection_end.pos)) {
+ selection_visible = false;
+ } else {
+ selection_visible = true;
+ }
+ owner.Invalidate();
+ }
+
+#if buggy
+ internal void SetSelection(Line start, int start_pos, Line end, int end_pos) {
+ // Ok, this is ugly, bad and slow, but I don't have time right now to optimize it.
+ if (selection_visible) {
+ // Try to only invalidate what's changed so we don't redraw the whole thing
+ if ((start != selection_start.line) || (start_pos != selection_start.pos) && ((end == selection_end.line) && (end_pos == selection_end.pos))) {
+ Invalidate(start, start_pos, selection_start.line, selection_start.pos);
+ } else if ((end != selection_end.line) || (end_pos != selection_end.pos) && ((start == selection_start.line) && (start_pos == selection_start.pos))) {
+ Invalidate(end, end_pos, selection_end.line, selection_end.pos);
+ } else {
+ // both start and end changed
+ Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
+ }
+ }
+ selection_start.line = start;
+ selection_start.pos = start_pos;
+if (start != null) {
+ selection_start.tag = LineTag.FindTag(start, start_pos);
+}
+
+ selection_end.line = end;
+ selection_end.pos = end_pos;
+if (end != null) {
+ selection_end.tag = LineTag.FindTag(end, end_pos);
+}
+
+ if (((start == end) && (start_pos == end_pos)) || start == null || end == null) {
+ selection_visible = false;
+ } else {
+ selection_visible = true;
+
+ }
+ }
+#endif
+ // Make sure that start is always before end
+ private void FixupSelection() {
+ if (selection_start.line.line_no > selection_end.line.line_no) {
+ Line line;
+ int pos;
+ LineTag tag;
+
+ line = selection_start.line;
+ tag = selection_start.tag;
+ pos = selection_start.pos;
+
+ selection_start.line = selection_end.line;
+ selection_start.tag = selection_end.tag;
+ selection_start.pos = selection_end.pos;
+
+ selection_end.line = line;
+ selection_end.tag = tag;
+ selection_end.pos = pos;
+
+ return;
+ }
+
+ if ((selection_start.line == selection_end.line) && (selection_start.pos > selection_end.pos)) {
+ int pos;
+
+ pos = selection_start.pos;
+ selection_start.pos = selection_end.pos;
+ selection_end.pos = pos;
+ Console.WriteLine("flipped: sel start: {0} end: {1}", selection_start.pos, selection_end.pos);
+ return;
+ }
+
+ return;
+ }
+
+ internal void SetSelectionStart(Line start, int start_pos) {
+ selection_start.line = start;
+ selection_start.pos = start_pos;
+ selection_start.tag = LineTag.FindTag(start, start_pos);
+
+ FixupSelection();
+
+ if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
+ selection_visible = true;
+ }
+
+ if (selection_visible) {
+ Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
+ }
+
+ }
+
+ internal void SetSelectionEnd(Line end, int end_pos) {
+ selection_end.line = end;
+ selection_end.pos = end_pos;
+ selection_end.tag = LineTag.FindTag(end, end_pos);
+
+ FixupSelection();
+
+ if ((selection_end.line != selection_start.line) || (selection_end.pos != selection_start.pos)) {
+ selection_visible = true;
+ }
+
+ if (selection_visible) {
+ Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
+ }
+ }
+
+ internal void SetSelection(Line start, int start_pos) {
+ if (selection_visible) {
+ Invalidate(selection_start.line, selection_start.pos, selection_end.line, selection_end.pos);
+ }
+
+
+ selection_start.line = start;
+ selection_start.pos = start_pos;
+ selection_start.tag = LineTag.FindTag(start, start_pos);
+
+ selection_end.line = start;
+ selection_end.tag = selection_start.tag;
+ selection_end.pos = start_pos;
+
+ selection_visible = false;
+ }
+
+ internal void InvalidateSelectionArea() {
+ // implement me
+ }
+
+ // Return the current selection, as string
+ internal string GetSelection() {
+ // We return String.Empty if there is no selection
+ if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
+ return string.Empty;
+ }
+
+ if (!multiline || (selection_start.line == selection_end.line)) {
+ return selection_start.line.text.ToString(selection_start.pos, selection_end.pos - selection_start.pos);
+ } else {
+ StringBuilder sb;
+ int i;
+ int start;
+ int end;
+
+ sb = new StringBuilder();
+ start = selection_start.line.line_no;
+ end = selection_end.line.line_no;
+
+ sb.Append(selection_start.line.text.ToString(selection_start.pos, selection_start.line.text.Length - selection_start.pos) + Environment.NewLine);
+
+ if ((start + 1) < end) {
+ for (i = start + 1; i < end; i++) {
+ sb.Append(GetLine(i).text.ToString() + Environment.NewLine);
+ }
+ }
+
+ sb.Append(selection_end.line.text.ToString(0, selection_end.pos));
+
+ return sb.ToString();
+ }
+ }
+
+ internal void ReplaceSelection(string s) {
+ // The easiest is to break the lines where the selection tags are and delete those lines
+ if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
+ // Nothing to delete, simply insert
+ InsertString(selection_start.tag, selection_start.pos, s);
+ }
+
+ if (!multiline || (selection_start.line == selection_end.line)) {
+ DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
+
+ // The tag might have been removed, we need to recalc it
+ selection_start.tag = selection_start.line.FindTag(selection_start.pos);
+
+ InsertString(selection_start.tag, selection_start.pos, s);
+ } else {
+ int i;
+ int start;
+ int end;
+ int base_line;
+ string[] ins;
+ int insert_lines;
+
+ start = selection_start.line.line_no;
+ end = selection_end.line.line_no;
+
+ // Delete first line
+ DeleteChars(selection_start.tag, selection_start.pos, selection_end.pos - selection_start.pos);
+
+ start++;
+ if (start < end) {
+ for (i = end - 1; i >= start; i++) {
+ Delete(i);
+ }
+ }
+
+ // Delete last line
+ DeleteChars(selection_end.line.tags, 0, selection_end.pos);
+
+ ins = s.Split(new char[] {'\n'});
+
+ for (int j = 0; j < ins.Length; j++) {
+ if (ins[j].EndsWith("\r")) {
+ ins[j] = ins[j].Substring(0, ins[j].Length - 1);
+ }
+ }
+
+ insert_lines = ins.Length;
+
+ // Bump the text at insertion point a line down if we're inserting more than one line
+ if (insert_lines > 1) {
+ Split(selection_start.line, selection_start.pos);
+
+ // Reminder of start line is now in startline+1
+
+ // if the last line does not end with a \n we will insert the last line in front of the just moved text
+ if (s.EndsWith("\n")) {
+ insert_lines--; // We don't want to insert the last line as part of the loop anymore
+
+ InsertString(GetLine(selection_start.line.line_no + 1), 0, ins[insert_lines - 1]);
+ }
+ }
+
+ // Insert the first line
+ InsertString(selection_start.line, selection_start.pos, ins[0]);
+
+ if (insert_lines > 1) {
+ base_line = selection_start.line.line_no + 1;
+
+ for (i = 1; i < insert_lines; i++) {
+ Add(base_line + i, ins[i], selection_start.line.alignment, selection_start.tag.font, selection_start.tag.color);
+ }
+ }
+ }
+ selection_end.line = selection_start.line;
+ selection_end.pos = selection_start.pos;
+ selection_end.tag = selection_start.tag;
+ selection_visible = false;
+ PositionCaret(selection_start.line, selection_start.pos);
+ InvalidateSelectionArea();
+ }
+
+ internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) {
+ Line line;
+ LineTag tag;
+ int i;
+ int chars;
+ int start;
+
+ chars = 0;
+
+ for (i = 1; i < lines; i++) {
+ line = GetLine(i);
+
+ start = chars;
+ chars += line.text.Length + 2; // We count the trailing \n as a char
+
+ if (index <= chars) {
+ // we found the line
+ tag = line.tags;
+
+ while (tag != null) {
+ if (index < (start + tag.start + tag.length)) {
+ line_out = line;
+ tag_out = tag;
+ pos = index - start;
+ return;
+ }
+ if (tag.next == null) {
+ Line next_line;
+
+ next_line = GetLine(line.line_no + 1);
+
+ if (next_line != null) {
+ line_out = next_line;
+ tag_out = next_line.tags;
+ pos = 0;
+ return;
+ } else {
+ line_out = line;
+ tag_out = tag;
+ pos = line_out.text.Length;
+ return;
+ }
+ }
+ tag = tag.next;
+ }
+ }
+ }
+
+ line_out = GetLine(lines);
+ tag = line_out.tags;
+ while (tag.next != null) {
+ tag = tag.next;
+ }
+ tag_out = tag;
+ pos = line_out.text.Length;
+ }
+
+ internal int LineTagToCharIndex(Line line, int pos) {
+ int i;
+ int length;
+
+ // Count first and last line
+ length = 0;
+
+ // Count the lines in the middle
+
+ for (i = 1; i < line.line_no; i++) {
+ length += GetLine(i).text.Length + 2; // Add one for the \n at the end of the line
+ }
+
+ length += pos;
+
+ return length;
+ }
+
+ internal int SelectionLength() {
+ if ((selection_start.pos == selection_end.pos) && (selection_start.line == selection_end.line)) {
+ return 0;
+ }
+
+ if (!multiline || (selection_start.line == selection_end.line)) {
+ return selection_end.pos - selection_start.pos;
+ } else {
+ int i;
+ int start;
+ int end;
+ int length;
+
+ // Count first and last line
+ length = selection_start.line.text.Length - selection_start.pos + selection_end.pos;
+
+ // Count the lines in the middle
+ start = selection_start.line.line_no + 1;
+ end = selection_end.line.line_no;
+
+ if (start < end) {
+ for (i = start; i < end; i++) {
+ length += GetLine(i).text.Length;
+ }
+ }
+
+ return length;
+ }
+
+
}
// Give it a Line number and it returns the Line object at with that line number
- public Line GetLine(int LineNo) {
+ internal Line GetLine(int LineNo) {
Line line = document;
while (line != sentinel) {
// Give it a Y pixel coordinate and it returns the Line covering that Y coordinate
///
- public Line GetLineByPixel(int y, bool exact) {
+ internal Line GetLineByPixel(int y, bool exact) {
Line line = document;
Line last = null;
}
// Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
- public LineTag FindTag(int x, int y, out int index, bool exact) {
+ internal LineTag FindTag(int x, int y, out int index, bool exact) {
Line line;
LineTag tag;
}
tag = line.tags;
+ // Alignment adjustment
+ x += line.align_shift;
+
while (true) {
if (x >= tag.X && x < (tag.X+tag.width)) {
int end;
}
// Give it x/y pixel coordinates and it returns the Tag at that position; optionally the char position is returned in index
- public LineTag FindCursor(int x, int y, out int index) {
+ internal LineTag FindCursor(int x, int y, out int index) {
Line line;
LineTag tag;
line = GetLineByPixel(y, false);
tag = line.tags;
+ // Adjust for alignment
+ x += line.align_shift;
+
while (true) {
if (x >= tag.X && x < (tag.X+tag.width)) {
int end;
}
}
+ internal void RecalculateAlignments() {
+ Line line;
+ int line_no;
+
+ line_no = 1;
+
+ while (line_no <= lines) {
+ line = GetLine(line_no);
+
+ if (line.alignment != HorizontalAlignment.Left) {
+ if (line.alignment == HorizontalAlignment.Center) {
+ line.align_shift = (document_x - (int)line.widths[line.text.Length]) / 2;
+ } else {
+ line.align_shift = document_x - (int)line.widths[line.text.Length];
+ }
+ }
+
+ line_no++;
+ }
+ return;
+ }
+
// Calculate formatting for the whole document
- public bool RecalculateDocument(Graphics g) {
+ internal bool RecalculateDocument(Graphics g) {
return RecalculateDocument(g, 1, this.lines, false);
}
// Calculate formatting starting at a certain line
- public bool RecalculateDocument(Graphics g, int start) {
+ internal bool RecalculateDocument(Graphics g, int start) {
return RecalculateDocument(g, start, this.lines, false);
}
// Calculate formatting within two given line numbers
- public bool RecalculateDocument(Graphics g, int start, int end) {
+ internal bool RecalculateDocument(Graphics g, int start, int end) {
return RecalculateDocument(g, start, end, false);
}
// With optimize on, returns true if line heights changed
- public bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
+ internal bool RecalculateDocument(Graphics g, int start, int end, bool optimize) {
Line line;
int line_no;
int Y;
line_no = start;
if (optimize) {
bool changed;
+ bool alignment_recalc;
changed = false;
+ alignment_recalc = false;
while (line_no <= end) {
line = GetLine(line_no++);
// If the height changed, all subsequent lines change
end = this.lines;
}
+
+ if (line.widths[line.text.Length] > this.document_x) {
+ this.document_x = (int)line.widths[line.text.Length];
+ alignment_recalc = true;
+ }
+
+ // Calculate alignment
+ if (line.alignment != HorizontalAlignment.Left) {
+ if (line.alignment == HorizontalAlignment.Center) {
+ line.align_shift = (document_x - (int)line.widths[line.text.Length]) / 2;
+ } else {
+ line.align_shift = document_x - (int)line.widths[line.text.Length];
+ }
+ }
}
Y += line.height;
}
+ if (alignment_recalc) {
+ RecalculateAlignments();
+ }
+
return changed;
} else {
while (line_no <= end) {
line = GetLine(line_no++);
line.Y = Y;
line.RecalculateLine(g);
+ if (line.widths[line.text.Length] > this.document_x) {
+ this.document_x = (int)line.widths[line.text.Length];
+ }
+
+ // Calculate alignment
+ if (line.alignment != HorizontalAlignment.Left) {
+ if (line.alignment == HorizontalAlignment.Center) {
+ line.align_shift = (document_x - (int)line.widths[line.text.Length]) / 2;
+ } else {
+ line.align_shift = document_x - (int)line.widths[line.text.Length];
+ }
+ }
+
Y += line.height;
}
+ RecalculateAlignments();
return true;
}
}
- public bool SetCursor(int x, int y) {
+ internal bool SetCursor(int x, int y) {
return true;
}
- public int Size() {
+ internal int Size() {
return lines;
}
- #endregion // Public Methods
+ #endregion // Internal Methods
#region Administrative
public IEnumerator GetEnumerator() {
#endregion // Administrative
}
- public class LineTag {
+ internal class LineTag {
#region Local Variables;
// Payload; formatting
internal Font font; // System.Drawing.Font object for this tag
+ internal Brush color; // System.Drawing.Brush object
// Payload; text
internal int start; // start, in chars; index into Line.text
internal int ascent; // Ascent of the font for this tag
internal int shift; // Shift down for this tag, to stay on baseline
+ internal int soft_break; // Tag is 'broken soft' and continues in the next line
+
// Administrative
internal Line line; // The line we're on
internal LineTag next; // Next tag on the same line
#endregion;
#region Constructors
- public LineTag(Line line, int start, int length) {
+ internal LineTag(Line line, int start, int length) {
this.line = line;
this.start = start;
this.length = length;
}
#endregion // Constructors
- #region Public Methods
+ #region Internal Methods
//
// Applies 'font' to characters starting at 'start' for 'length' chars
// Removes any previous tags overlapping the same area
// returns true if lineheight has changed
//
- public static bool FormatText(Line line, int start, int length, Font font) {
+ internal static bool FormatText(Line line, int start, int length, Font font, Brush color) {
LineTag tag;
LineTag start_tag;
- LineTag end_tag;
int end;
- int state;
- int left;
bool retval = false; // Assume line-height doesn't change
// Too simple?
tag = line.tags;
end = start + length;
- state = 0;
// Common special case
if ((start == 1) && (length == tag.length)) {
+ tag.ascent = 0;
tag.font = font;
+ tag.color = color;
return retval;
}
start_tag = FindTag(line, start);
- end_tag = FindTag(line, end);
tag = new LineTag(line, start, length);
tag.font = font;
+ tag.color = color;
if (start == 1) {
line.tags = tag;
} else {
tag.next = new LineTag(line, start_tag.start, start_tag.length);
tag.next.font = start_tag.font;
+ tag.next.color = start_tag.color;
if (start_tag.next != null) {
tag.next.next = start_tag.next;
tag.previous = start_tag;
start_tag.next = tag;
-#if crap
+#if nope
if (tag.next.start > (tag.start + tag.length)) {
tag.next.length += tag.next.start - (tag.start + tag.length);
tag.next.start = tag.start + tag.length;
//
// Finds the tag that describes the character at position 'pos' on 'line'
//
- public static LineTag FindTag(Line line, int pos) {
+ internal static LineTag FindTag(Line line, int pos) {
LineTag tag = line.tags;
// Beginning of line is a bit special
//
// Combines 'this' tag with 'other' tag.
//
- public bool Combine(LineTag other) {
+ internal bool Combine(LineTag other) {
if (!this.Equals(other)) {
return false;
}
//
// Remove 'this' tag ; to be called when formatting is to be removed
//
- public bool Remove() {
+ internal bool Remove() {
if ((this.start == 1) && (this.next == null)) {
// We cannot remove the only tag
return false;
other = (LineTag)obj;
- if (this.font.Equals(other.font)) { // FIXME add checking for things like link or type later
+ if (this.font.Equals(other.font) && this.color.Equals(other.color)) { // FIXME add checking for things like link or type later
return true;
}
return false;
}
+ public override int GetHashCode() {\r
+ return base.GetHashCode ();\r
+ }\r
+
public override string ToString() {
return "Tag starts at index " + this.start + "length " + this.length + " text: " + this.line.Text.Substring(this.start-1, this.length) + "Font " + this.font.ToString();
}
- #endregion // Public Methods
+ #endregion // Internal Methods
}
}