Merge pull request #1047 from BDisp/textview-unicode-support

Fixes #93. Audit TextView just like we did TextField to ensure proper treatment of Unicode
This commit is contained in:
Charlie Kindel
2020-12-27 14:01:17 -07:00
committed by GitHub
2 changed files with 268 additions and 216 deletions

View File

@@ -115,8 +115,6 @@ namespace Terminal.Gui {
get => base.Frame;
set {
base.Frame = value;
var w = base.Frame.Width;
first = point > w ? point - w : 0;
Adjust ();
}
}
@@ -157,7 +155,7 @@ namespace Terminal.Gui {
TextChanged?.Invoke (oldText);
if (point > text.Count) {
point = Math.Max (DisplaySize (text, 0).size - 1, 0);
point = Math.Max (TextModel.DisplaySize (text, 0).size - 1, 0);
}
Adjust ();
@@ -195,7 +193,7 @@ namespace Terminal.Gui {
if (idx == point)
break;
var cols = Rune.ColumnWidth (text [idx]);
col = SetCol (col, Frame.Width - 1, cols);
col = TextModel.SetCol (col, Frame.Width - 1, cols);
}
Move (col, 0);
}
@@ -227,7 +225,7 @@ namespace Terminal.Gui {
if (col + cols <= width) {
Driver.AddRune ((Rune)(Secret ? '*' : rune));
}
col = SetCol (col, width, cols);
col = TextModel.SetCol (col, width, cols);
if (idx + 1 < tcount && col + Rune.ColumnWidth (text [idx + 1]) > width) {
break;
}
@@ -241,45 +239,15 @@ namespace Terminal.Gui {
PositionCursor ();
}
static int SetCol (int col, int width, int cols)
{
if (col + cols <= width) {
col += cols;
}
return col;
}
// Returns the size and length in a range of the string.
(int size, int length) DisplaySize (List<Rune> t, int start = -1, int end = -1, bool checkNextRune = true)
{
if (t == null || t.Count == 0) {
return (0, 0);
}
int size = 0;
int len = 0;
int tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end;
int i = start == -1 ? 0 : start;
for (; i < tcount; i++) {
var rune = t [i];
size += Rune.ColumnWidth (rune);
len += Rune.RuneLen (rune);
if (checkNextRune && i == tcount - 1 && t.Count > tcount && Rune.ColumnWidth (t [i + 1]) > 1) {
size += Rune.ColumnWidth (t [i + 1]);
len += Rune.RuneLen (t [i + 1]);
}
}
return (size, len);
}
void Adjust ()
{
int offB = OffSetBackground ();
if (point < first) {
first = point;
} else if (first + point - (Frame.Width + offB) == 0 ||
DisplaySize (text, first, point).size >= Frame.Width + offB) {
first = Math.Max (CalculateFirst (text, first, point, Frame.Width - 1 + offB), 0);
TextModel.DisplaySize (text, first, point).size >= Frame.Width + offB) {
first = Math.Max (TextModel.CalculateLeftColumn (text, first,
point, Frame.Width - 1 + offB, point), 0);
}
SetNeedsDisplay ();
}
@@ -294,33 +262,6 @@ namespace Terminal.Gui {
return offB;
}
int CalculateFirst (List<Rune> t, int start, int end, int width)
{
if (t == null) {
return 0;
}
(var dSize, _) = DisplaySize (t, start, end);
if (dSize < width) {
return start;
}
int size = 0;
int tcount = end > t.Count - 1 ? t.Count - 1 : end;
int col = 0;
for (int i = tcount; i > start; i--) {
var rune = t [i];
var s = Rune.ColumnWidth (rune);
size += s;
if (size >= dSize - width) {
col = tcount - i + start;
if (start == 0 || col == start || (point == t.Count && (point - col > width))) {
col++;
}
break;
}
}
return col;
}
void SetText (List<Rune> newText)
{
Text = ustring.Make (newText);
@@ -779,7 +720,7 @@ namespace Terminal.Gui {
{
// We could also set the cursor position.
int x;
var pX = GetPointFromX (text, first, ev.X);
var pX = TextModel.GetColFromX (text, first, ev.X);
if (text.Count == 0) {
x = pX - ev.OfX;
} else {
@@ -792,7 +733,7 @@ namespace Terminal.Gui {
{
int pX = x;
if (getX) {
pX = GetPointFromX (text, first, x);
pX = TextModel.GetColFromX (text, first, x);
}
if (first + pX > text.Count) {
point = text.Count;
@@ -805,23 +746,6 @@ namespace Terminal.Gui {
return point;
}
int GetPointFromX (List<Rune> t, int start, int x)
{
if (x < 0) {
return x;
}
int size = start;
var pX = x + start;
for (int i = start; i < t.Count; i++) {
var r = t [i];
size += Rune.ColumnWidth (r);
if (i == pX || (size > pX)) {
return i - start;
}
}
return t.Count - start;
}
void PrepareSelection (int x, int direction = 0)
{
x = x + first < 0 ? 0 : x;
@@ -897,9 +821,9 @@ namespace Terminal.Gui {
ustring actualText = Text;
int selStart = SelectedLength < 0 ? SelectedLength + SelectedStart : SelectedStart;
int selLength = Math.Abs (SelectedLength);
(var _, var len) = DisplaySize (text, 0, selStart, false);
(var _, var len2) = DisplaySize (text, selStart, selStart + selLength, false);
(var _, var len3) = DisplaySize (text, selStart + selLength, actualText.RuneCount, false);
(var _, var len) = TextModel.DisplaySize (text, 0, selStart, false);
(var _, var len2) = TextModel.DisplaySize (text, selStart, selStart + selLength, false);
(var _, var len3) = TextModel.DisplaySize (text, selStart + selLength, actualText.RuneCount, false);
Text = actualText[0, len] +
actualText[len + len2, len + len2 + len3];
ClearAllSelection ();
@@ -919,9 +843,9 @@ namespace Terminal.Gui {
SetSelectedStartSelectedLength ();
int selStart = start == -1 ? CursorPosition : start;
ustring actualText = Text;
(int _, int len) = DisplaySize (text, 0, selStart, false);
(var _, var len2) = DisplaySize (text, selStart, selStart + length, false);
(var _, var len3) = DisplaySize (text, selStart + length, actualText.RuneCount, false);
(int _, int len) = TextModel.DisplaySize (text, 0, selStart, false);
(var _, var len2) = TextModel.DisplaySize (text, selStart, selStart + length, false);
(var _, var len3) = TextModel.DisplaySize (text, selStart + length, actualText.RuneCount, false);
ustring cbTxt = Clipboard.Contents ?? "";
Text = actualText [0, len] +
cbTxt +

View File

@@ -171,6 +171,101 @@ namespace Terminal.Gui {
{
lines.RemoveAt (pos);
}
/// <summary>
/// Returns the maximum line length of the visible lines.
/// </summary>
/// <param name="first">The first line.</param>
/// <param name="last">The last line.</param>
public int GetMaxVisibleLine (int first, int last)
{
int maxLength = 0;
last = last < lines.Count ? last : lines.Count;
for (int i = first; i < last; i++) {
var l = GetLine (i).Count;
if (l > maxLength) {
maxLength = l;
}
}
return maxLength;
}
internal static int SetCol (int col, int width, int cols)
{
if (col + cols <= width) {
col += cols;
}
return col;
}
internal static int GetColFromX (List<Rune> t, int start, int x)
{
if (x < 0) {
return x;
}
int size = start;
var pX = x + start;
for (int i = start; i < t.Count; i++) {
var r = t [i];
size += Rune.ColumnWidth (r);
if (i == pX || (size > pX)) {
return i - start;
}
}
return t.Count - start;
}
// Returns the size and length in a range of the string.
internal static (int size, int length) DisplaySize (List<Rune> t, int start = -1, int end = -1, bool checkNextRune = true)
{
if (t == null || t.Count == 0) {
return (0, 0);
}
int size = 0;
int len = 0;
int tcount = end == -1 ? t.Count : end > t.Count ? t.Count : end;
int i = start == -1 ? 0 : start;
for (; i < tcount; i++) {
var rune = t [i];
size += Rune.ColumnWidth (rune);
len += Rune.RuneLen (rune);
if (checkNextRune && i == tcount - 1 && t.Count > tcount && Rune.ColumnWidth (t [i + 1]) > 1) {
size += Rune.ColumnWidth (t [i + 1]);
len += Rune.RuneLen (t [i + 1]);
}
}
return (size, len);
}
// Returns the left column in a range of the string.
internal static int CalculateLeftColumn (List<Rune> t, int start, int end, int width, int currentColumn)
{
if (t == null) {
return 0;
}
(var dSize, _) = TextModel.DisplaySize (t, start, end);
if (dSize < width) {
return start;
}
int size = 0;
int tcount = end > t.Count - 1 ? t.Count - 1 : end;
int col = 0;
for (int i = tcount; i > start; i--) {
var rune = t [i];
var s = Rune.ColumnWidth (rune);
size += s;
if (size >= dSize - width) {
col = tcount - i + start;
if (start == 0 || col == start || (currentColumn == t.Count && (currentColumn - col > width))) {
col++;
}
break;
}
}
return col;
}
}
/// <summary>
@@ -356,6 +451,15 @@ namespace Terminal.Gui {
}
}
///<inheritdoc/>
public override Rect Frame {
get => base.Frame;
set {
base.Frame = value;
Adjust ();
}
}
/// <summary>
/// Loads the contents of the file into the <see cref="TextView"/>.
/// </summary>
@@ -421,11 +525,22 @@ namespace Terminal.Gui {
}
var line = model.GetLine (currentRow);
var retreat = 0;
var col = 0;
if (line.Count > 0) {
retreat = Math.Max ((SpecialRune (line [Math.Max (CurrentColumn - leftColumn - 1, 0)])
? 1 : 0), 0);
for (int idx = leftColumn < 0 ? 0 : leftColumn; idx < line.Count; idx++) {
if (idx == CurrentColumn)
break;
var cols = Rune.ColumnWidth (line [idx]);
col += cols - 1;
}
}
var ccol = CurrentColumn - leftColumn - retreat + col;
if (leftColumn <= CurrentColumn && ccol < Frame.Width
&& topRow <= CurrentRow && CurrentRow - topRow < Frame.Height) {
Move (ccol, CurrentRow - topRow);
}
Move (CurrentColumn - leftColumn - retreat, CurrentRow - topRow);
}
void ClearRegion (int left, int top, int right, int bottom)
@@ -569,10 +684,12 @@ namespace Terminal.Gui {
}
Move (bounds.Left, row);
for (int col = bounds.Left; col < right; col++) {
var lineCol = leftColumn + col;
var col = 0;
for (int idx = bounds.Left; idx < right; idx++) {
var lineCol = leftColumn + idx;
var rune = lineCol >= lineRuneCount ? ' ' : line [lineCol];
if (selecting && PointInSelection (col, row)) {
var cols = Rune.ColumnWidth (rune);
if (selecting && PointInSelection (idx, row)) {
ColorSelection ();
} else {
ColorNormal ();
@@ -581,6 +698,7 @@ namespace Terminal.Gui {
if (!SpecialRune (rune)) {
AddRune (col, row, rune);
}
col = TextModel.SetCol (col, bounds.Right, cols);
}
}
PositionCursor ();
@@ -641,19 +759,25 @@ namespace Terminal.Gui {
void InsertText (ustring text)
{
if (ustring.IsNullOrEmpty (text)) {
return;
}
var lines = TextModel.StringToRunes (text);
if (lines.Count == 0)
if (lines.Count == 0) {
return;
}
var line = GetCurrentLine ();
// Optmize single line
// Optimize single line
if (lines.Count == 1) {
line.InsertRange (currentColumn, lines [0]);
currentColumn += lines [0].Count;
if (currentColumn - leftColumn > Frame.Width)
if (currentColumn - leftColumn > Frame.Width) {
leftColumn = currentColumn - Frame.Width + 1;
}
SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, currentRow - topRow + 1));
return;
}
@@ -666,26 +790,18 @@ namespace Terminal.Gui {
// First line is inserted at the current location, the rest is appended
line.InsertRange (currentColumn, lines [0]);
for (int i = 1; i < lines.Count; i++)
for (int i = 1; i < lines.Count; i++) {
model.AddLine (currentRow + i, lines [i]);
}
var last = model.GetLine (currentRow + lines.Count - 1);
var lastp = last.Count;
last.InsertRange (last.Count, rest);
// Now adjjust column and row positions
// Now adjust column and row positions
currentRow += lines.Count - 1;
currentColumn = lastp;
if (currentRow - topRow > Frame.Height) {
topRow = currentRow - Frame.Height + 1;
if (topRow < 0)
topRow = 0;
}
if (currentColumn < leftColumn)
leftColumn = currentColumn;
if (currentColumn - leftColumn >= Frame.Width)
leftColumn = currentColumn - Frame.Width + 1;
SetNeedsDisplay ();
Adjust ();
}
// The column we are tracking, or -1 if we are not tracking any column
@@ -707,38 +823,63 @@ namespace Terminal.Gui {
void Adjust ()
{
var offB = OffSetBackground ();
var line = GetCurrentLine ();
bool need = false;
if (currentColumn < leftColumn) {
currentColumn = leftColumn;
leftColumn = currentColumn;
need = true;
}
if (currentColumn - leftColumn > Frame.Width) {
leftColumn = currentColumn - Frame.Width + 1;
} else if (currentColumn - leftColumn > Frame.Width + offB.width ||
TextModel.DisplaySize (line, leftColumn, currentColumn).size >= Frame.Width + offB.width) {
leftColumn = Math.Max (TextModel.CalculateLeftColumn (line, leftColumn,
currentColumn, Frame.Width - 1 + offB.width, currentColumn), 0);
need = true;
}
if (currentRow < topRow) {
topRow = currentRow;
need = true;
}
if (currentRow - topRow > Frame.Height) {
topRow = currentRow - Frame.Height + 1;
} else if (currentRow - topRow >= Frame.Height + offB.height) {
topRow = Math.Min (Math.Max (currentRow - Frame.Height + 1, 0), currentRow);
need = true;
}
if (need)
if (need) {
SetNeedsDisplay ();
else
} else {
PositionCursor ();
}
}
(int width, int height) OffSetBackground ()
{
int w = 0;
int h = 0;
if (SuperView?.Frame.Right - Frame.Right < 0) {
w = SuperView.Frame.Right - Frame.Right - 1;
}
if (SuperView?.Frame.Bottom - Frame.Bottom < 0) {
h = SuperView.Frame.Bottom - Frame.Bottom - 1;
}
return (w, h);
}
/// <summary>
/// Will scroll the <see cref="TextView"/> to display the specified row at the top
/// Will scroll the <see cref="TextView"/> to display the specified row at the top if <paramref name="isRow"/> is true or
/// will scroll the <see cref="TextView"/> to display the specified column at the left if <paramref name="isRow"/> is false.
/// </summary>
/// <param name="row">Row that should be displayed at the top, if the value is negative it will be reset to zero</param>
public void ScrollTo (int row)
/// <param name="idx">Row that should be displayed at the top or Column that should be displayed at the left,
/// if the value is negative it will be reset to zero</param>
/// <param name="isRow">If true (default) the <paramref name="idx"/> is a row, column otherwise.</param>
public void ScrollTo (int idx, bool isRow = true)
{
if (row < 0)
row = 0;
topRow = row > model.Count ? model.Count - 1 : row;
if (idx < 0) {
idx = 0;
}
if (isRow) {
topRow = idx > model.Count - 1 ? model.Count - 1 : idx;
} else {
var maxlength = model.GetMaxVisibleLine (topRow, topRow + Frame.Height);
leftColumn = idx > maxlength - 1 ? maxlength - 1 : idx;
}
SetNeedsDisplay ();
}
@@ -786,7 +927,7 @@ namespace Terminal.Gui {
break;
case Key.PageUp:
case ((int)'v' + Key.AltMask):
case ((int)'V' + Key.AltMask):
int nPageUpShift = Frame.Height - 1;
if (currentRow > 0) {
if (columnTrack == -1)
@@ -816,53 +957,33 @@ namespace Terminal.Gui {
var currentLine = GetCurrentLine ();
if (currentColumn < currentLine.Count) {
currentColumn++;
if (currentColumn >= leftColumn + Frame.Width) {
leftColumn++;
SetNeedsDisplay ();
}
PositionCursor ();
} else {
if (currentRow + 1 < model.Count) {
currentRow++;
currentColumn = 0;
leftColumn = 0;
if (currentRow >= topRow + Frame.Height) {
topRow++;
}
SetNeedsDisplay ();
PositionCursor ();
}
break;
}
Adjust ();
break;
case Key.B | Key.CtrlMask:
case Key.CursorLeft:
if (currentColumn > 0) {
currentColumn--;
if (currentColumn < leftColumn) {
leftColumn--;
SetNeedsDisplay ();
}
PositionCursor ();
} else {
if (currentRow > 0) {
currentRow--;
if (currentRow < topRow) {
topRow--;
SetNeedsDisplay ();
}
currentLine = GetCurrentLine ();
currentColumn = currentLine.Count;
int prev = leftColumn;
leftColumn = currentColumn - Frame.Width + 1;
if (leftColumn < 0)
leftColumn = 0;
if (prev != leftColumn)
SetNeedsDisplay ();
PositionCursor ();
}
}
Adjust ();
break;
case Key.Delete:
@@ -890,10 +1011,7 @@ namespace Terminal.Gui {
model.RemoveLine (currentRow);
currentRow--;
currentColumn = prevCount;
leftColumn = currentColumn - Frame.Width + 1;
if (leftColumn < 0)
leftColumn = 0;
SetNeedsDisplay ();
Adjust ();
}
break;
@@ -901,11 +1019,7 @@ namespace Terminal.Gui {
case Key.Home:
case Key.A | Key.CtrlMask:
currentColumn = 0;
if (currentColumn < leftColumn) {
leftColumn = 0;
SetNeedsDisplay ();
} else
PositionCursor ();
Adjust ();
break;
case Key.DeleteChar:
case Key.D | Key.CtrlMask: // Delete
@@ -932,12 +1046,7 @@ namespace Terminal.Gui {
currentLine = GetCurrentLine ();
currentColumn = currentLine.Count;
int pcol = leftColumn;
leftColumn = currentColumn - Frame.Width + 1;
if (leftColumn < 0)
leftColumn = 0;
if (pcol != leftColumn)
SetNeedsDisplay ();
PositionCursor ();
Adjust ();
break;
case Key.K | Key.CtrlMask: // kill-to-end
@@ -978,11 +1087,7 @@ namespace Terminal.Gui {
selectionStartRow = currentRow;
break;
case ((int)'w' + Key.AltMask):
SetClipboard (GetRegion ());
selecting = false;
break;
case ((int)'W' + Key.AltMask):
case Key.W | Key.CtrlMask:
SetClipboard (GetRegion ());
if (!isReadOnly)
@@ -990,7 +1095,8 @@ namespace Terminal.Gui {
selecting = false;
break;
case (Key)((int)'b' + Key.AltMask):
case Key.CtrlMask | Key.CursorLeft:
case (Key)((int)'B' + Key.AltMask):
var newPos = WordBackward (currentColumn, currentRow);
if (newPos.HasValue) {
currentColumn = newPos.Value.col;
@@ -1000,7 +1106,8 @@ namespace Terminal.Gui {
break;
case (Key)((int)'f' + Key.AltMask):
case Key.CtrlMask | Key.CursorRight:
case (Key)((int)'F' + Key.AltMask):
newPos = WordForward (currentColumn, currentRow);
if (newPos.HasValue) {
currentColumn = newPos.Value.col;
@@ -1012,7 +1119,6 @@ namespace Terminal.Gui {
case Key.Enter:
if (isReadOnly)
break;
var orow = currentRow;
currentLine = GetCurrentLine ();
restCount = currentLine.Count - currentColumn;
rest = currentLine.GetRange (currentColumn, restCount);
@@ -1063,11 +1169,12 @@ namespace Terminal.Gui {
return true;
}
private void MoveUp ()
void MoveUp ()
{
if (currentRow > 0) {
if (columnTrack == -1)
if (columnTrack == -1) {
columnTrack = currentColumn;
}
currentRow--;
if (currentRow < topRow) {
topRow--;
@@ -1078,11 +1185,12 @@ namespace Terminal.Gui {
}
}
private void MoveDown ()
void MoveDown ()
{
if (currentRow + 1 < model.Count) {
if (columnTrack == -1)
if (columnTrack == -1) {
columnTrack = currentColumn;
}
currentRow++;
if (currentRow >= topRow + Frame.Height) {
topRow++;
@@ -1090,6 +1198,8 @@ namespace Terminal.Gui {
}
TrackColumn ();
PositionCursor ();
} else if (currentRow > Frame.Height) {
Adjust ();
}
}
@@ -1186,28 +1296,31 @@ namespace Terminal.Gui {
{
var col = fromCol;
var row = fromRow;
var line = GetCurrentLine ();
var rune = RuneAt (col, row);
try {
var rune = RuneAt (col, row);
var srow = row;
if (Rune.IsPunctuation (rune) || Rune.IsWhiteSpace (rune)) {
while (MoveNext (ref col, ref row, out rune)) {
if (Rune.IsLetterOrDigit (rune))
break;
}
while (MoveNext (ref col, ref row, out rune)) {
if (!Rune.IsLetterOrDigit (rune))
break;
}
} else {
while (MoveNext (ref col, ref row, out rune)) {
if (!Rune.IsLetterOrDigit (rune))
break;
var srow = row;
if (Rune.IsPunctuation (rune) || Rune.IsWhiteSpace (rune)) {
while (MoveNext (ref col, ref row, out rune)) {
if (Rune.IsLetterOrDigit (rune))
break;
}
while (MoveNext (ref col, ref row, out rune)) {
if (!Rune.IsLetterOrDigit (rune))
break;
}
} else {
while (MoveNext (ref col, ref row, out rune)) {
if (!Rune.IsLetterOrDigit (rune))
break;
}
}
if (fromCol != col || fromRow != row)
return (col, row);
return null;
} catch (Exception) {
return null;
}
if (fromCol != col || fromRow != row)
return (col, row);
return null;
}
(int col, int row)? WordBackward (int fromCol, int fromRow)
@@ -1217,27 +1330,30 @@ namespace Terminal.Gui {
var col = fromCol;
var row = fromRow;
var line = GetCurrentLine ();
var rune = RuneAt (col, row);
try {
var rune = RuneAt (col, row);
if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) {
while (MovePrev (ref col, ref row, out rune)) {
if (Rune.IsLetterOrDigit (rune))
break;
}
while (MovePrev (ref col, ref row, out rune)) {
if (!Rune.IsLetterOrDigit (rune))
break;
}
} else {
while (MovePrev (ref col, ref row, out rune)) {
if (!Rune.IsLetterOrDigit (rune))
break;
if (Rune.IsPunctuation (rune) || Rune.IsSymbol (rune) || Rune.IsWhiteSpace (rune)) {
while (MovePrev (ref col, ref row, out rune)) {
if (Rune.IsLetterOrDigit (rune))
break;
}
while (MovePrev (ref col, ref row, out rune)) {
if (!Rune.IsLetterOrDigit (rune))
break;
}
} else {
while (MovePrev (ref col, ref row, out rune)) {
if (!Rune.IsLetterOrDigit (rune))
break;
}
}
if (fromCol != col || fromRow != row)
return (col, row);
return null;
} catch (Exception) {
return null;
}
if (fromCol != col || fromRow != row)
return (col, row);
return null;
}
///<inheritdoc/>
@@ -1265,18 +1381,30 @@ namespace Terminal.Gui {
currentRow = ev.Y + topRow;
}
var r = GetCurrentLine ();
if (ev.X - leftColumn >= r.Count)
var idx = TextModel.GetColFromX (r, leftColumn, ev.X);
if (idx - leftColumn >= r.Count) {
currentColumn = r.Count - leftColumn;
else
currentColumn = ev.X - leftColumn;
} else {
currentColumn = idx + leftColumn;
}
}
PositionCursor ();
} else if (ev.Flags == MouseFlags.WheeledDown) {
lastWasKill = false;
MoveDown ();
columnTrack = currentColumn;
ScrollTo (topRow + 1);
} else if (ev.Flags == MouseFlags.WheeledUp) {
lastWasKill = false;
MoveUp ();
columnTrack = currentColumn;
ScrollTo (topRow - 1);
} else if (ev.Flags == MouseFlags.WheeledRight) {
lastWasKill = false;
columnTrack = currentColumn;
ScrollTo (leftColumn + 1, false);
} else if (ev.Flags == MouseFlags.WheeledLeft) {
lastWasKill = false;
columnTrack = currentColumn;
ScrollTo (leftColumn - 1, false);
}
return true;