diff -r 000000000000 -r 7f656887cf89 commands/fed/src/lrtextview.cpp --- /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); + } + }