--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/fed/src/lrtextview.cpp Wed Jun 23 15:52:26 2010 +0100
@@ -0,0 +1,1120 @@
+// lrtextview.cpp
+//
+// Copyright (c) 2009 - 2010 Accenture. All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of the "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Accenture - Initial contribution
+//
+
+#include "lrtextview.h"
+#include <e32cons.h>
+#include "filebuffer.h"
+#include <fshell/common.mmh>
+#include <fshell/ltkutils.h>
+
+const TInt KLastCtrlChar = 0x1f; //Last control char in ASCII
+const TInt KCtrlHTab = 0x09; //Horizontal Tab
+
+CLRTextView* CLRTextView::NewL(MConsoleProvider& aConsoleProvider, CFedBufferBase& aBuffer)
+ {
+ CLRTextView* self = new (ELeave) CLRTextView(aConsoleProvider, aBuffer);
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ CleanupStack::Pop(self);
+ return self;
+ }
+
+CLRTextView::~CLRTextView()
+ {
+ delete iLineData;
+ delete iMarkFlasher;
+ }
+
+CLRTextView::CLRTextView(MConsoleProvider& aConsoleProvider, CFedBufferBase& aBuffer)
+ : CTextView(aConsoleProvider, aBuffer), iMarkDocPos(-1), iOldNextLine(-1)
+ {
+ }
+
+void CLRTextView::ConstructL()
+ {
+ iLineData = new(ELeave) CLineData;
+ CTextView::ConstructL();
+ iMarkFlasher = CPeriodic::NewL(CActive::EPriorityStandard);
+ }
+
+//MDeferredClose
+TBool CLRTextView::TryCloseL()
+ {
+ if (!iBuffer.Modified())
+ {
+ return ETrue; // Ok to close now
+ }
+ else
+ {
+ TKeyCode ch = iConsoleProvider.Query(_L("Save changes to file? (yes/no/cancel) "), _L("ync\x1B"));
+ if (ch == 'c' || ch == EKeyEscape) return EFalse;
+ if (ch == 'y')
+ {
+ TInt err = Save();
+ if (err) return EFalse; // Don't allow exit if the save failed
+ }
+ return ETrue;
+ }
+ }
+
+TInt CLRTextView::Save()
+ {
+ if (!iBuffer.Editable() || iLineIsErrorMessage) return KErrNotSupported;
+
+ CancelMark();
+ TBool wasOpen = iBuffer.IsOpen();
+ TInt err = KErrNone;
+ const TDesC& name = iBuffer.Title();
+ if (name.Length())
+ {
+ iConsoleProvider.InfoPrint(_L("Saving"));
+ TRAP(err, iBuffer.SaveL(name, ETrue));
+ HandleSaveResult(err, wasOpen, iBuffer.Title());
+ return err;
+ }
+ else
+ {
+ return SaveAs();
+ }
+ }
+
+TInt CLRTextView::SaveAs()
+ {
+ if (!iBuffer.Editable() || iLineIsErrorMessage) return KErrNotSupported;
+
+ CancelMark();
+ TBool wasOpen = iBuffer.IsOpen();
+ TFileName name = iBuffer.Title();
+ TInt err = KErrCancel;
+ TBool go = iConsoleProvider.QueryFilename(_L("Save as: "), name);
+ if (go)
+ {
+ iConsoleProvider.InfoPrint(_L("Saving"));
+ TRAP(err, iBuffer.SaveL(name, EFalse));
+ if (err == KErrAlreadyExists)
+ {
+ if (iConsoleProvider.Query(_L("File already exists. Replace? (yes/no) "), _L("yn")) == 'y')
+ {
+ iConsoleProvider.InfoPrint(_L("Saving"));
+ TRAP(err, iBuffer.SaveL(name, ETrue));
+ }
+ }
+ HandleSaveResult(err, wasOpen, iBuffer.Title());
+ }
+ return err;
+ }
+
+void CLRTextView::HandleSaveResult(TInt aError, TBool aWasOpen, const TDesC& aName)
+ {
+ if (aError == KErrNone)
+ {
+ TRAP_IGNORE(DoRedrawL()); // Not only can QueryFilename mess up the screen, saving the file could change the line endings and thus change the character counts, meaning we need to recalculate & redraw
+ iConsoleProvider.InfoPrint(_L("Save succeeded"));
+ }
+ else if (aWasOpen && !iBuffer.IsOpen())
+ {
+ // An error occurred after the original file was closed. This is fatal so we need to put up a dialog to that effect
+ iLine.Format(_L("Temp file @ %S"), &aName);
+ iLineIsErrorMessage = ETrue;
+ UpdateStatus();
+ iConsoleProvider.InfoPrint(_L("Failed to rename temp file (%d)"), aError);
+ }
+ else
+ {
+ // Failed to save, but we didn't mess with the original so we can carry on using it
+ TRAP_IGNORE(DoRedrawL()); // Not only can QueryFilename mess up the screen, saving the file could change the line endings and thus change the character counts, meaning we need to recalculate & redraw
+ switch (aError)
+ {
+ case KErrDiskFull:
+ iConsoleProvider.InfoPrint(_L("Disk full, couldn't save file"));
+ break;
+ case KErrNoMemory:
+ iConsoleProvider.InfoPrint(_L("Out of memory!"));
+ break;
+ case KErrPathNotFound:
+ iConsoleProvider.InfoPrint(_L("KErrPathNotFound. Directory doesn't exist?"));
+ break;
+ case KErrGeneral:
+ iConsoleProvider.InfoPrint(_L("Couldn't save file. No idea why"));
+ break;
+ case KErrBadName:
+ iConsoleProvider.InfoPrint(_L("Bad file name"));
+ break;
+ case KErrAccessDenied:
+ iConsoleProvider.InfoPrint(_L("Access denied. Read-only file or disk?"));
+ break;
+ default:
+ iConsoleProvider.InfoPrint(_L("Unable to save file: %d"), aError);
+ break;
+ }
+ }
+ }
+
+//MKeyConsumer
+TBool CLRTextView::ConsumeKeyL(const TKeyCode& aCode)
+ {
+ TBool handled = ETrue;
+ switch(aCode)
+ {
+ case EKeyUpArrow:
+ MoveCursor(0, -1); break;
+ case EKeyDownArrow:
+ MoveCursor(0, 1); break;
+ case EKeyLeftArrow:
+ MoveCursor(-1, 0); break;
+ case EKeyRightArrow:
+ MoveCursor(1, 0); break;
+ case EKeyPageUp:
+ case CTRL('u'):
+ MoveCursor(0, -iWindow.iHeight); break;
+ case EKeyPageDown:
+ case CTRL('d'):
+ MoveCursor(0, iWindow.iHeight); break;
+ case EKeyHome:
+ UpdateCursor(TPoint(0, iCursor.iY));
+ break;
+ case CTRL('t'):
+ iCursor.SetXY(0, 0);
+ RequestData(ETrue, 0); break;
+ case CTRL('b'):
+ GoToLine(KMaxTInt); break;
+ case EKeyEnd:
+ {
+ TInt pos = iLineData->LastValidPosition(iCursor.iY);
+ UpdateCursor(TPoint(pos, iCursor.iY));
+ break;
+ }
+ case EKeyBackspace:
+ DeleteTextL(-1);
+ break;
+ case EKeyDelete:
+ DeleteTextL(1);
+ break;
+ case CTRL('s'):
+ Save();
+ break;
+ case CTRL('a'):
+ SaveAs();
+ break;
+ case CTRL('k'):
+ DeleteCurrentLineL();
+ break;
+ case CTRL('g'):
+ GoToLine();
+ break;
+ case CTRL('f'):
+ Find();
+ break;
+ case CTRL('v'):
+ PasteL();
+ break;
+ case CTRL('c'):
+ if (MarkActive()) CopyOrCutL(EFalse);
+ else SetMark();
+ break;
+ case CTRL('x'):
+ if (MarkActive()) CopyOrCutL(ETrue);
+ else SetMark();
+ break;
+ default:
+ handled = EFalse;
+ break;
+ }
+
+ if (!handled && (aCode == EKeyEnter || aCode == EKeyTab || TChar(aCode).IsPrint()))
+ {
+ TBuf<1> buf;
+ buf.Append(aCode);
+ InsertTextL(buf);
+ handled = ETrue;
+ }
+ return handled;
+}
+
+void CLRTextView::MoveCursor(TInt aX, TInt aY)
+ {
+ if (aX) ASSERT(aY == 0);
+ if (aY) ASSERT(aX == 0); // Can't handle moving in both axes at once
+
+ TInt line = iCursor.iY;
+ TInt dir = (aX > 0) ? 1 : -1;
+ TInt newX = iCursor.iX;
+ while (aX)
+ {
+ newX += dir;
+ if (newX < 0)
+ {
+ // Move to end of previous line
+ newX = iWindow.iWidth; // Validate cursor will fix this, in the case where this causes us to scroll up a line
+ aY--;
+ break; // We don't really support seeking further than one line either way
+ }
+ else if (newX >= iWindow.iWidth-1)
+ {
+ // Move to start of next line
+ newX = 0;
+ aY++;
+ break; // We don't really support seeking further than one line either way
+ }
+
+ if (iLineData->IsScreenPositionValid(line, newX))
+ {
+ aX -= dir;
+ }
+ }
+
+ if (line + aY < 0)
+ {
+ // Scroll up
+ SeekData(iLineData->DocumentPosition(0), aY);
+ }
+ else if (line + aY >= iWindow.iHeight)
+ {
+ // Scroll down
+ SeekData(iLineData->DocumentPosition(0), aY);
+ }
+ else
+ {
+ // Just update cursor
+ if (iLineData->IsScreenPositionValid(line + aY, 0))
+ {
+ // If the line len is -1 it means the doc doesn't extend as far as this line, therefore we shouldn't allow the cursor to be moved to it
+ UpdateCursor(TPoint(newX, iCursor.iY + aY));
+ }
+ }
+ }
+
+void CLRTextView::DoResizeL(const TWindow& /*aOldWindow*/)
+ {
+ DoRedrawL();
+ }
+
+void CLRTextView::DoRedrawL()
+ {
+ iLineData->EnsureCapacityL(iWindow.iWidth, iWindow.iHeight);
+ ValidateCursor();
+ RequestData(ETrue, iLineData->DocumentPosition(0));
+ }
+
+void CLRTextView::DoDrawL()
+ {
+ DoDrawL(iRecursiveUpdateFlag ? *iRecursiveUpdateFlag : ETrue);
+ }
+
+void CLRTextView::DoDrawL(TBool aUpdateCursorAndStatus)
+ {
+ if (iDrawPoint == TPoint(iWindow.iX, iWindow.iY))
+ {
+ iLineData->SetDocumentPositionForLine(0, iRange.iLocation);
+ iLineData->SetFileLineNumberForLine(0, iRangeStartLineNumber);
+ }
+
+ ASSERT(iRange.iLength == iDes.Length());
+ HideCursor(); // Don't show the cursor while we're moving it around drawing - will be shown again by UpdateCursor
+ for (iCurrentDrawDocIndex = 0; iCurrentDrawDocIndex < iDes.Length(); iCurrentDrawDocIndex++)
+ {
+ TUint16 c = iDes[iCurrentDrawDocIndex];
+ if(iDrawPoint.iY >= iWindow.NextY())
+ {
+ break;
+ }
+ if(c <= KLastCtrlChar)
+ HandleControlChar();
+ else
+ AppendChar(c);
+ }
+
+ TBool moreData = !iBuffer.DocumentPositionIsEof(iRange.Max());
+ TBool filledScreen = iDrawPoint.iY >= iWindow.NextY();
+ if (!filledScreen)
+ {
+ WriteChars();
+ LineFinished();
+ }
+ if (!moreData)
+ {
+ iDrawPoint.iY++; // Start from the line *after* the line we were on
+ // In the case where we've finished writing but haven't filled the screen, we need to blank the rest of it (because to avoid flicker we don't do ClearScreens)
+ while (iDrawPoint.iY < iWindow.NextY())
+ {
+ iDrawPoint.iX = iWindow.iX;
+ iLine.Copy(_L("^"));
+ WriteChars();
+ TInt lineNum = iDrawPoint.iY - iWindow.iY;
+ iLineData->SetDocumentPositionForLine(lineNum+1, iLineData->DocumentPosition(lineNum));
+ iLineData->SetFileLineNumberForLine(lineNum, -1);
+ iLineData->LineFinishedAt(lineNum, -1);
+ iDrawPoint.iY++;
+ }
+ }
+
+ if (filledScreen || !moreData)
+ {
+ // Finished - null iDes and restore cursor
+ iDes.Set(NULL, 0);
+ if (aUpdateCursorAndStatus)
+ {
+ UpdateCursor(iCursor);
+ }
+ }
+ else
+ {
+ TBool flagSet = EFalse;
+ if (!iRecursiveUpdateFlag)
+ {
+ iRecursiveUpdateFlag = &aUpdateCursorAndStatus;
+ flagSet = ETrue;
+ }
+ // More data needed to fill the screen
+ RequestData(EFalse, iRange.Max());
+ if (flagSet) iRecursiveUpdateFlag = NULL;
+ }
+}
+
+void CLRTextView::ValidateCursor()
+ {
+ CTextView::ValidateCursor();
+ TInt maxX = iWindow.iWidth - 1;
+ TInt line = iCursor.iY;
+ while (iCursor.iX > 0 && !iLineData->IsScreenPositionValid(line, iCursor.iX))
+ {
+ // In case the cursor ended up in the dead space in the middle of a tab character
+ iCursor.iX--;
+ }
+ while (line > 0 && !iLineData->IsScreenPositionValid(line, 0))
+ {
+ // In case the cursor has ended up in dead space
+ iCursor.iY--;
+ line--;
+ }
+ maxX = iLineData->LastValidPosition(line);
+ if(iCursor.iX > maxX)
+ iCursor.iX = maxX;
+ }
+
+void CLRTextView::HandleControlChar()
+ {
+ TUint8 c = iDes[iCurrentDrawDocIndex];
+ if (c == KCtrlHTab)
+ {
+ TInt pos = iDrawPoint.iX - iWindow.iX + iLine.Length();
+ TInt numToWrite = TabWidth() - (pos % TabWidth()); // Tabs align to 4-column boundaries rather than just outputting 4 spaces
+ numToWrite = Min(numToWrite, iWindow.iWidth-1-pos); // In case the tab causes a line continuation, we want to make sure the tab ends at the end of the line so the following character will start at column zero
+ for(TInt i=0; i<numToWrite; i++)
+ {
+ TInt line = iDrawPoint.iY - iWindow.iY;
+ TInt col = iDrawPoint.iX - iWindow.iX + iLine.Length();
+ AppendChar(' ');
+ if (i > 0) iLineData->SetPositionIsValid(line, col, EFalse); // i==0 (ie the first screen position of the tab) is a valid place to move the cursor to
+ }
+ }
+ else if (c == '\r')
+ {
+ if (iCurrentDrawDocIndex+1 < iDes.Length() && iDes[iCurrentDrawDocIndex+1] == '\n') iCurrentDrawDocIndex++; // Skip over the CR to the LF
+ WriteLine();
+ }
+ else if (c == '\n')
+ {
+ WriteLine();
+ }
+ else
+ {
+ // unknown control char - so escape it
+ AppendChar('.');
+ }
+ }
+
+void CLRTextView::AppendChar(TUint16 aChar)
+ {
+ iLine.Append(TChar(aChar));
+ iLineData->SetPositionIsValid(iDrawPoint.iY - iWindow.iY, iDrawPoint.iX - iWindow.iX + iLine.Length(), ETrue);
+ if(iDrawPoint.iX + iLine.Length() == iWindow.NextX()-1)
+ {
+ WriteChars();
+ WriteLineContinue();
+ GoToNextLine();
+ }
+ }
+
+//Writes the current line on the screen and moves to the next verse and line
+void CLRTextView::WriteLine()
+ {
+ TInt thisLine = iDrawPoint.iY-iWindow.iY;
+ WriteChars();
+ GoToNextLine();
+ iLineData->SetFileLineNumberForLine(thisLine+1, iLineData->FileLineNumber(thisLine) + 1);
+ if (iOldNextLine != -1 && thisLine+1 == iOldNextLine && iLineData->DocumentPosition(iOldNextLine) == iOldNextLineDocPos + iPredictedOldNextLineDocPosDelta)
+ {
+ // We have drawn everything we need to, and the drawing didn't cause the former next line to move
+ iDrawPoint.iY = iWindow.NextY();
+ UpdateDocumentPositionsStartingAtLine(iOldNextLine+1, iPredictedOldNextLineDocPosDelta);
+ }
+ iOldNextLine = -1;
+ }
+
+//Writes the current line on the screen
+void CLRTextView::WriteChars()
+ {
+ /* As an performance optimisation, we avoid calling ClearScreen when redrawing. Therefore we must ensure we
+ * write to the entire line to prevent artifacts.
+ *
+ * There are 2 approaches to this - either using CConsoleBase::ClearToEndOfLine or padding the line with
+ * spaces up to the window width. ClearToEndOfLine is really slow in WINSCW tshell which is why it's not
+ * used by default. On target it's probably more efficient to use it.
+ */
+ TPoint drawPoint(iDrawPoint);
+ TPoint endPoint(drawPoint.iX + iLine.Length(), drawPoint.iY);
+ iConsole.SetCursorPosAbs(drawPoint);
+#ifdef FSHELL_WSERV_SUPPORT
+// text windowserver is really slow at ClearToEndOfLine
+#define USE_CLEARTOENDOFLINE
+#endif
+#ifdef USE_CLEARTOENDOFLINE
+ iConsole.Write(iLine);
+ iConsole.ClearToEndOfLine();
+#else
+ iLine.AppendFill(' ', iWindow.iWidth - (iDrawPoint.iX-iWindow.iX) - iLine.Length());
+ iConsole.Write(iLine);
+#endif
+ iLine.Zero();
+ // Remember to reset the console pos and the drawpoint to the end of the line proper in case the WriteChars
+ // was to half a line and the rest of the line is in another block.
+ iDrawPoint = endPoint;
+ iConsole.SetCursorPosAbs(iDrawPoint);
+ }
+
+// Moves to the next screen line but doesn't move to the next file line (used when a line is longer than the width of the window)
+void CLRTextView::GoToNextLine()
+ {
+ LineFinished();
+ iDrawPoint.iX = iWindow.iX;
+ iDrawPoint.iY++;
+ iLineData->SetPositionIsValid(iDrawPoint.iY-iWindow.iY, 0, ETrue);
+ }
+
+void CLRTextView::LineFinished()
+ {
+ TInt currentLine = iDrawPoint.iY - iWindow.iY;
+ if (currentLine == iWindow.iHeight) return; // This can happen (and is ok) when drawing the last line of the screen
+ iLineData->LineFinishedAt(currentLine, iDrawPoint.iX - iWindow.iX);
+ iLineData->SetDocumentPositionForLine(currentLine+1, iRange.iLocation+iCurrentDrawDocIndex+1); // Plus one cos we haven't drawn the first character of the next line yet
+ }
+
+void CLRTextView::WriteLineContinue()
+ {
+ CColorConsoleBase* cons = iConsoleProvider.ColorConsole();
+ if (cons) cons->SetTextAttribute(ETextAttributeHighlight);
+ _LIT(KContinuation, "\\");
+ iConsole.Write(KContinuation);
+ if (cons) cons->SetTextAttribute(ETextAttributeNormal);
+
+ TInt thisLine = iDrawPoint.iY-iWindow.iY;
+ iLineData->SetFileLineNumberForLine(thisLine+1, iLineData->FileLineNumber(thisLine)); // The new line has the same file number as the current line, because it's a continuation line
+ }
+
+TInt CLRTextView::DocumentPosition() const
+ {
+ TInt line = iCursor.iY;
+ TInt pos = iLineData->DocumentPosition(line);
+ for (TInt i = 0; i < iCursor.iX; i++)
+ {
+ if (iLineData->IsScreenPositionValid(line, i)) pos++;
+ }
+ return pos;
+ }
+
+void CLRTextView::InsertTextL(const TDesC& aText)
+ {
+ if (!iBuffer.Editable() || iLineIsErrorMessage) return;
+
+ CancelMark();
+ TInt pos = DocumentPosition();
+ iBuffer.InsertTextL(pos, aText);
+ if (iCursor.iX == iWindow.iWidth-1)
+ {
+ // If the cursor is over a line continuation character (which it will be if it gets to this value) move cursor to the real insertion point at the start of the next line before doing anything - makes the logic much easier!
+ MoveCursor(1, 0);
+ }
+
+ // If we can be sure it won't cause a reflow, shortcut the requesting and redrawing mechanism, for performance reasons
+ TPoint cursorPos = WindowLocationForDocumentPosition(pos);
+ TInt lineNum = cursorPos.iY;
+ TBool posWasAtEndOfLine = cursorPos.iX == iLineData->LastValidPosition(lineNum) && cursorPos.iX + aText.Length() < iWindow.iWidth-1;
+ if (posWasAtEndOfLine && aText.Locate('\t') == KErrNotFound && aText.Locate('\r') == KErrNotFound)
+ {
+ // Can't optimise if the text contains tabs or newlines, we need to do the full algorithm in that case
+ iConsole.Write(aText);
+ for (TInt i = 0; i < aText.Length(); i++)
+ {
+ iLineData->SetPositionIsValid(lineNum, cursorPos.iX + 1 + i, ETrue); // Each added char makes the character position one after that character valid
+ }
+ for (TInt l = lineNum+1; l <= iWindow.iHeight; l++)
+ {
+ iLineData->SetDocumentPositionForLine(l, iLineData->DocumentPosition(l) + aText.Length());
+ }
+ MoveCursor(aText.Length(),0);
+ }
+ else
+ {
+ RedrawFromPositionToEndOfLine(pos, aText.Length());
+ CenterDocPosOnScreen(pos + aText.Length());
+ }
+ }
+
+void CLRTextView::RedrawFromPositionToEndOfLine(TInt aDocumentPosition, TInt aNumCharsInserted)
+ {
+ TPoint point = WindowLocationForDocumentPosition(aDocumentPosition);
+ ASSERT(point.iY >= 0);
+ iDrawPoint = point + iWindow.Origin();
+ // Try and avoid redrawing the whole screen if the RequestData doesn't make the line longer (in terms of screen lines)
+ TInt lastScreenLineForCurrentLine = point.iY;
+ while (iLineData->FileLineNumber(lastScreenLineForCurrentLine+1) == iLineData->FileLineNumber(lastScreenLineForCurrentLine))
+ {
+ // We have to allow for the current line wrapping onto additional screen lines
+ lastScreenLineForCurrentLine++;
+ }
+ iOldNextLine = lastScreenLineForCurrentLine + 1;
+ iOldNextLineDocPos = iLineData->DocumentPosition(iOldNextLine);
+ iPredictedOldNextLineDocPosDelta = aNumCharsInserted;
+ RequestData(EFalse, aDocumentPosition);
+ }
+
+void CLRTextView::DeleteTextL(TInt aNumChars)
+ {
+ if (!iBuffer.Editable() || iLineIsErrorMessage) return;
+
+ CancelMark();
+ ASSERT(aNumChars >= -1); // Can't backspace more than one character
+ TInt pos = DocumentPosition();
+ TRange range;
+ if (aNumChars < 0)
+ range = TRange(pos + aNumChars, -aNumChars);
+ else
+ range = TRange(pos, aNumChars);
+ range.Intersect(TRange(0, KMaxTInt)); // Clamp the range to zero
+
+ if (range.iLength)
+ {
+ iBuffer.DeleteTextL(range);
+ TInt line = iCursor.iY;
+ if (aNumChars == -1) MoveCursor(-1, 0);
+ // Seems to be an issue using RedrawFromPositionToEndOfLine when deleting from the start of a line - since we know it wouldn't be worth using it in that case, don't do it.
+ if (iCursor.iY == line && aNumChars == -1)
+ {
+ RedrawFromPositionToEndOfLine(range.iLocation, -range.iLength);
+ }
+ else
+ {
+ iDrawPoint = iCursor + iWindow.Origin();
+ RequestData(EFalse, DocumentPosition());
+ }
+ }
+ }
+
+void CLRTextView::DeleteCurrentLineL()
+ {
+ if (!iBuffer.Editable() || iLineIsErrorMessage) return;
+
+ CancelMark();
+ TInt currentLine = iCursor.iY;
+ TInt lineStart = iLineData->DocumentPosition(currentLine);
+ TInt lineLen = iLineData->DocumentPosition(currentLine+1) - lineStart;
+ if (lineLen > 0)
+ {
+ TRange range(lineStart, lineLen);
+ iBuffer.DeleteTextL(range);
+ iDrawPoint = TPoint(iWindow.iX, iWindow.iY + iCursor.iY);
+ RequestData(EFalse, lineStart);
+ }
+ }
+
+void CLRTextView::UpdateCursor(const TPoint& aNewPos)
+ {
+ CTextView::UpdateCursor(aNewPos);
+ UpdateStatus();
+ }
+
+void CLRTextView::UpdateStatus()
+ {
+ if (iLineIsErrorMessage)
+ {
+ iConsoleProvider.WriteStatus(iLine, KNullDesC);
+ return;
+ }
+
+ _LIT(KUnknownDelim, "CRLF"); // Since that's what it will get output as
+ _LIT(KUnknownEnc, "UTF-8"); // ditto
+ const TDesC* lineEnding = &KUnknownDelim;
+#define CASE_LIT(res, x, y) case x: { _LIT(KName, y); res = &KName; break; }
+ switch(iBuffer.DelimiterType())
+ {
+ CASE_LIT(lineEnding, EDelimLF, "LF")
+ CASE_LIT(lineEnding, EDelimCR, "CR")
+ CASE_LIT(lineEnding, EDelimCRLF, "CRLF")
+ CASE_LIT(lineEnding, EDelimUnicode, "UniLF")
+ default:
+ break;
+ }
+ const TDesC* encoding = &KUnknownEnc;
+ switch (iBuffer.Encoding())
+ {
+ CASE_LIT(encoding, EEncodingNarrow, "8-bit")
+ CASE_LIT(encoding, EEncodingUtf8, "UTF-8")
+ CASE_LIT(encoding, EEncodingUtf16LE, "UTF-16LE")
+ CASE_LIT(encoding, EEncodingUtf16BE, "UTF-16BE")
+ default:
+ break;
+ }
+
+ const TDesC* modified = &KNullDesC;
+ if (iBuffer.Modified())
+ {
+ _LIT(KMod, "(*)");
+ modified = &KMod;
+ }
+
+ TInt currentLine = iLineData->FileLineNumber(iCursor.iY) + 1; // Human-readable line numbers are always shown as 1-based
+ TInt currentCol = iCursor.iX + 1; // Human-readable col numbers are always shown as 1-based, annoying as it is we will do the same
+ TInt docPos = DocumentPosition();
+ _LIT(KStatus, "%S %S %S Ln:%d Col:%d Char:%d");
+ _LIT(KShortStatus, "%S %S %S Ln:%d");
+ _LIT(KMarkStatus, "Mark@%d. ^X/^C to complete. Ln:%d Ch:%d");
+ const TDesC* title = &iBuffer.Title();
+ _LIT(KUntitled, "untitled");
+ if (title->Length() == 0) title = &KUntitled;
+ if (MarkActive())
+ {
+ iConsoleProvider.WriteStatus(KNullDesC, KMarkStatus, iMarkDocPos, currentLine, docPos);
+ }
+ else if (iConsole.ScreenSize().iWidth < 50)
+ {
+ iConsoleProvider.WriteStatus(*title, KShortStatus, modified, lineEnding, encoding, currentLine);
+ }
+ else
+ {
+ iConsoleProvider.WriteStatus(*title, KStatus, modified, lineEnding, encoding, currentLine, currentCol, docPos);
+ }
+ }
+
+void CLRTextView::GoToLine()
+ {
+ TBuf<32> lineText;
+ TBool go = iConsoleProvider.QueryText(_L("Go to line: "), lineText);
+ if (go == EFalse) return;
+
+ TLex lex(lineText);
+ TBool gotoLine = ETrue;
+ if (lex.Peek() == 'c')
+ {
+ // Go to character pos
+ lex.Inc();
+ gotoLine = EFalse;
+ }
+
+ TInt pos;
+ TInt err = lex.Val(pos);
+ if (err || pos <= 0)
+ {
+ iConsoleProvider.InfoPrint(_L("Couldn't parse line number '%S'"), &lineText);
+ return;
+ }
+
+ if (gotoLine && pos > 1)
+ {
+ GoToLine(pos-1); // -1 to change 1-based display number to internal 0-based.
+ }
+ else
+ {
+ CenterDocPosOnScreen(pos-1);
+ }
+ }
+
+void CLRTextView::GoToLine(TInt aLine)
+ {
+ TInt err = iBuffer.SeekFromOffset(*this, 0, aLine, KMaxTInt);
+ if (err)
+ {
+ HandleDataLoadError(err);
+ }
+ else
+ {
+ CenterDocPosOnScreen(iRange.iLocation);
+ }
+ }
+
+void CLRTextView::Find()
+ {
+ TBool searchBackwards = EFalse;
+ TBool proceed = iConsoleProvider.QueryText(_L("Find text: "), iFindText);
+ while (proceed)
+ {
+ TInt nextLineStart = iLineData->DocumentPosition(iCursor.iY + 1);
+ iConsoleProvider.InfoPrint(_L("Searching..."));
+ TInt found = iBuffer.Find(nextLineStart, iFindText, searchBackwards);
+ if (found == KErrNotFound)
+ {
+ iConsoleProvider.InfoPrint(_L("Cannot find '%S'"), &iFindText);
+ proceed = EFalse;
+ }
+ else if (found < 0)
+ {
+ iConsoleProvider.InfoPrint(_L("Error during searching: %d"), found);
+ proceed = EFalse;
+ }
+ else
+ {
+ CenterDocPosOnScreen(found);
+ /*TKeyCode ch = iConsoleProvider.Query(_L("Search again? (next/previous/cancel) "));
+ if (ch == 'n')
+ {
+ searchBackwards = EFalse;
+ continue;
+ }
+ else if (ch == 'p')
+ {
+ searchBackwards = ETrue;
+ continue;
+ }
+ else*/
+ {
+ proceed = EFalse;
+ }
+ }
+ }
+ }
+
+void CLRTextView::CenterDocPosOnScreen(TInt aDocumentPosition)
+ {
+ const TInt numScreenLines = iWindow.iHeight;
+ TPoint point = WindowLocationForDocumentPosition(aDocumentPosition);
+ if (point.iY != -1)
+ {
+ // pos is on screen already - just move the cursor
+ UpdateCursor(point);
+ return;
+ }
+
+ TInt err = iBuffer.SeekFromOffset(*this, aDocumentPosition, -(numScreenLines/2), iWindow.iWidth-1);
+ if (err)
+ {
+ HandleDataLoadError(err);
+ }
+ else
+ {
+ iDrawPoint.SetXY(iWindow.iX, iWindow.iY);
+ DoDrawL(EFalse); // EFalse to say we will update the cursor and status ourselves
+ UpdateCursor(WindowLocationForDocumentPosition(aDocumentPosition));
+ }
+ }
+
+void CLineData::EnsureCapacityL(TInt aWidth, TInt aHeight)
+ {
+ if (!iDocPosAndLine || (TUint)User::AllocLen(iDocPosAndLine) < sizeof(TInt)*2 * (aHeight+1))
+ {
+ User::Free(iDocPosAndLine);
+ iDocPosAndLine = NULL;
+ iDocPosAndLine = (TInt*)User::AllocZL(sizeof(TInt)*2*(aHeight+1));
+ }
+ iLineLen = aWidth-1;
+ TInt lineWidthBytes = ((iLineLen+7)&~7)/8;
+ TInt screenBytes = lineWidthBytes * (aHeight+1);
+ if (!iScreenIndexes || User::AllocLen(iScreenIndexes) < screenBytes)
+ {
+ User::Free(iScreenIndexes);
+ iScreenIndexes = NULL;
+ iScreenIndexes = (TUint8*)User::AllocZL(screenBytes);
+ }
+ SetPositionIsValid(0, 0, ETrue); // This position is always valid even if the document is empty
+ }
+
+TInt CLineData::DocumentPosition(TInt aLine) const
+ {
+ return iDocPosAndLine[aLine*2];
+ }
+
+TInt CLineData::FileLineNumber(TInt aLine) const
+ {
+ return iDocPosAndLine[aLine*2 + 1];
+ }
+
+const TUint8& CLineData::ByteForPos(TInt aLine, TInt aCol, TInt& aOffset) const
+ {
+ return const_cast<CLineData*>(this)->ByteForPos(aLine, aCol, aOffset);
+ }
+
+TUint8& CLineData::ByteForPos(TInt aLine, TInt aCol, TInt& aOffset)
+ {
+ TInt lineWidthBytes = ((iLineLen+7)&~7)/8;
+ TInt byteIndex = aLine*lineWidthBytes + aCol/8;
+ aOffset = aCol%8;
+ TUint8& b = iScreenIndexes[byteIndex];
+ return b;
+ }
+
+TBool CLineData::IsScreenPositionValid(TInt aLine, TInt aCol) const
+ {
+ TInt offset;
+ TUint8 b = ByteForPos(aLine, aCol, offset);
+ return b & (1 << offset);
+ }
+
+void CLineData::SetDocumentPositionForLine(TInt aLine, TInt aDocumentPosition)
+ {
+ ASSERT(iDocPosAndLine && aLine*2*sizeof(TInt) < (TUint)User::AllocLen(iDocPosAndLine));
+ iDocPosAndLine[aLine*2] = aDocumentPosition;
+ }
+
+void CLineData::SetFileLineNumberForLine(TInt aLine, TInt aFileLineNumber)
+ {
+ ASSERT(iDocPosAndLine && (aLine*2+1)*sizeof(TInt) < (TUint)User::AllocLen(iDocPosAndLine));
+ iDocPosAndLine[aLine*2 + 1] = aFileLineNumber;
+ }
+
+void CLineData::SetPositionIsValid(TInt aLine, TInt aCol, TBool aValid)
+ {
+ TInt offset;
+ TUint8& b = ByteForPos(aLine, aCol, offset);
+ if (aValid)
+ {
+ b |= (1<<offset);
+ }
+ else
+ {
+ b &= ~(1<<offset);
+ }
+ }
+
+void CLineData::LineFinishedAt(TInt aLine, TInt aCol)
+ {
+ if (aCol >=0 && aCol < iLineLen) SetPositionIsValid(aLine, aCol, ETrue); // The last position on the line is valid even though nothing is drawn there
+ TInt offset;
+ TUint8& b = ByteForPos(aLine, aCol+1, offset); // Plus one as we want to clear everything beyond the aCol position
+ // Clear everything above the offset'th bit
+ b &= (1 << offset) - 1;
+ // And clear all bytes from b to the end of the line
+ TUint8* endb = &ByteForPos(aLine, iLineLen-1, offset);
+ // Armv5 doesn't like passing in a length of zero to memset, not sure why. Anyway, check there are actually some other bytes that need clearing
+ if (endb > &b)
+ {
+ Mem::FillZ((&b)+1, endb - &b);
+ }
+ }
+
+CLineData::~CLineData()
+ {
+ User::Free(iDocPosAndLine);
+ User::Free(iScreenIndexes);
+ }
+
+TInt CLineData::LastValidPosition(TInt aLine) const
+ {
+ TInt offset;
+ TInt pos = iLineLen-1; // Start comparing from the last possible valid position
+ const TUint8* b = &ByteForPos(aLine, pos, offset);
+ while(*b == 0)
+ {
+ b--; // Optimisation to skip over blanks
+ pos -= offset+1;
+ offset = 7;
+ }
+
+ while (offset >= 0 && ((*b) & (1<<offset)) == 0)
+ {
+ pos--;
+ offset--;
+ }
+ ASSERT(IsScreenPositionValid(aLine, pos)); // Something's gone very wrong in the logic of this function if this assert is not true!
+ return pos;
+ }
+
+void CLRTextView::CopyToClipboardL(const TDesC& aBuf)
+ {
+ LtkUtils::CopyToClipboardL(aBuf, &iConsoleProvider.Fs());
+ }
+
+HBufC* CLRTextView::GetFromClipboardL()
+ {
+ return LtkUtils::GetFromClipboardL(&iConsoleProvider.Fs());
+ }
+
+void CLRTextView::PasteL()
+ {
+ HBufC* text = GetFromClipboardL();
+ CleanupStack::PushL(text);
+ if (text->Length() == 0)
+ {
+ iConsoleProvider.InfoPrint(_L("Nothing to paste"));
+ }
+ else
+ {
+ InsertTextL(*text);
+ }
+ CleanupStack::PopAndDestroy(text);
+ }
+
+TPoint CLRTextView::WindowLocationForDocumentPosition(TInt aDocumentPosition) const
+ {
+ TPoint result(-1,-1); // We return this if doc pos is not on screen
+ const TInt numScreenLines = iWindow.iHeight;
+ if (aDocumentPosition >= iLineData->DocumentPosition(0) && aDocumentPosition < iLineData->DocumentPosition(numScreenLines))
+ {
+ // pos is on screen
+ for (TInt line = 0; line < numScreenLines; line++)
+ {
+ if (aDocumentPosition < iLineData->DocumentPosition(line+1))
+ {
+ result.iY = line;
+ TInt docPos = iLineData->DocumentPosition(line);
+ TInt column = 0;
+ // Find the column screen position whose document position is what we want
+ for (; docPos <= aDocumentPosition; column++)
+ {
+ if (iLineData->IsScreenPositionValid(line, column))
+ {
+ docPos++;
+ }
+ }
+ result.iX = column-1;
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+void CLRTextView::SetMark()
+ {
+ iMarkFlasher->Cancel();
+ iMarkDocPos = DocumentPosition();
+ iMarkShown = EFalse;
+ const TInt KFlashInterval = 500000;
+ iMarkFlasher->Start(0, KFlashInterval, TCallBack(&FlashMark, this));
+ UpdateStatus();
+ iConsoleProvider.InfoPrint(_L("Mark set. Move cursor then press ^X/^C to cut/copy"));
+ }
+
+void CLRTextView::CancelMark()
+ {
+ if (MarkActive())
+ {
+ iMarkFlasher->Cancel();
+ if (WindowLocationForDocumentPosition(iMarkDocPos).iY != -1)
+ {
+ // If the mark pos is on screen, redraw it to get rid of the '*'
+ RedrawFromPositionToEndOfLine(iMarkDocPos, 0);
+ }
+ }
+ }
+
+TInt CLRTextView::FlashMark(TAny* aSelf)
+ {
+ static_cast<CLRTextView*>(aSelf)->DoFlashMark();
+ return 1;
+ }
+
+void CLRTextView::DoFlashMark()
+ {
+ TPoint markPos = WindowLocationForDocumentPosition(iMarkDocPos);
+ if (markPos.iY != -1)
+ {
+ TPoint cursor = iConsole.CursorPos();
+ iConsole.SetCursorPosAbs(markPos);
+ if (iMarkShown)
+ iConsole.Write(_L(" "));
+ else
+ iConsole.Write(_L("*"));
+ iMarkShown = !iMarkShown;
+ iConsole.SetCursorPosAbs(cursor);
+ }
+ }
+
+TBool CLRTextView::MarkActive() const
+ {
+ return iMarkFlasher->IsActive();
+ }
+
+void CLRTextView::CopyOrCutL(TBool aCut)
+ {
+ TInt docPos = DocumentPosition();
+ const TInt start = Min(docPos, iMarkDocPos);
+ const TInt len = Abs(docPos - iMarkDocPos);
+ CancelMark();
+ RBuf textCopy;
+ CleanupClosePushL(textCopy);
+ TInt dataPos = start;
+ while(ETrue)
+ {
+ TInt err = iBuffer.GetData(*this, dataPos);
+ if (err)
+ {
+ HandleDataLoadError(err);
+ CleanupStack::PopAndDestroy(&textCopy);
+ return;
+ }
+ else
+ {
+ TPtrC text = iDes.Left(len - textCopy.Length());
+ if (textCopy.MaxLength() == 0 && len <= iRange.iLength)
+ {
+ // All fits in one buffer, no need to copy anything!
+ CopyToClipboardL(text);
+ break;
+ }
+ else
+ {
+ // Need to do some copying
+ if (textCopy.MaxLength() == 0) textCopy.CreateL(len);
+ textCopy.Append(text);
+ dataPos += text.Length();
+ if (textCopy.Length() == len)
+ {
+ // Done
+ CopyToClipboardL(textCopy);
+ break;
+ }
+ }
+ }
+ }
+ CleanupStack::PopAndDestroy(&textCopy);
+ if (aCut)
+ {
+ TRange range(start, len);
+ iBuffer.DeleteTextL(range);
+ iCursor = WindowLocationForDocumentPosition(start);
+ if (iCursor.iY == -1)
+ {
+ // Start of the selection is offscreen - force a scroll
+ CenterDocPosOnScreen(start);
+ }
+ else
+ {
+ iDrawPoint = iCursor + iWindow.Origin();
+ RequestData(EFalse, start);
+ }
+ iConsoleProvider.InfoPrint(_L("Text cut to clipboard"));
+ }
+ else
+ {
+ iConsoleProvider.InfoPrint(_L("Text copied to clipboard"));
+ }
+ }
+
+void CLRTextView::UpdateDocumentPositionsStartingAtLine(TInt aLine, TInt aDelta)
+ {
+ // This is important because even though we can optimise away the drawing, when a line has changed
+ // length but not caused wrapping, we still need to make sure the document positions of all the
+ // lines we didn't draw are correct to reflect the extra characters.
+ for (TInt i = aLine; i < iWindow.iHeight+1; i++)
+ {
+ iLineData->SetDocumentPositionForLine(i, iLineData->DocumentPosition(i) + aDelta);
+ }
+ }