Added ENotifyKeypresses and ECaptureCtrlC flags to CCommandBase.
Commands can now get keypresses and handle ctrl-C via callbacks instead of having to implement custom active objects. As part of this extended the CCommandBase extension interface to MCommandExtensionsV2 for the new virtual functions KeyPressed(TUint aKeyCode, TUint aModifiers) and CtrlCPressed(). sudo now cleans up correctly by using ECaptureCtrlC.
// 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);
}
}