870 lines
25 KiB
C++
870 lines
25 KiB
C++
/*
|
|
* This is a part of the BugTrap package.
|
|
* Copyright (c) 2005-2009 IntelleSoft.
|
|
* All rights reserved.
|
|
*
|
|
* Description: Text view control class.
|
|
* Author: Maksim Pyatkovskiy.
|
|
*
|
|
* This source code is only intended as a supplement to the
|
|
* BugTrap package reference and related electronic documentation
|
|
* provided with the product. See these sources for detailed
|
|
* information regarding the BugTrap package.
|
|
*/
|
|
|
|
#include "StdAfx.h"
|
|
#include "TextView.h"
|
|
#include "BugTrapUtils.h"
|
|
#include "Globals.h"
|
|
|
|
#ifdef _DEBUG
|
|
#define new DEBUG_NEW
|
|
#endif
|
|
|
|
#define USE_MEM_DC
|
|
|
|
#ifndef SPI_GETWHEELSCROLLLINES
|
|
#define SPI_GETWHEELSCROLLLINES 0x0068
|
|
#endif
|
|
|
|
#ifndef SPI_SETWHEELSCROLLLINES
|
|
#define SPI_SETWHEELSCROLLLINES 0x0069
|
|
#endif
|
|
|
|
/// Ellipsis string.
|
|
static const TCHAR g_szEllipsis[] = _T("...");
|
|
/// Length of ellipsis string.
|
|
static const DWORD g_dwEllipsisLength = countof(g_szEllipsis) - 1;
|
|
/// Default number of wheel lines.
|
|
static const DWORD g_dwDefaultNumWheels = 3;
|
|
|
|
void CTextView::InitVars(void)
|
|
{
|
|
m_hwnd = NULL;
|
|
m_pfnOldTextViewWndProc = NULL;
|
|
m_lOldStyle = 0;
|
|
m_nWheelLines = g_dwDefaultNumWheels;
|
|
ResetFile();
|
|
}
|
|
|
|
/**
|
|
* @param hdc - drawing context.
|
|
* @param prcPaint - the rectangle where the painting is requested.
|
|
*/
|
|
void CTextView::DrawTextView(HDC hdc, const RECT* prcPaint)
|
|
{
|
|
_ASSERTE(g_pResManager != NULL);
|
|
if (prcPaint == NULL)
|
|
{
|
|
RECT rcClient;
|
|
GetClientRect(m_hwnd, &rcClient);
|
|
prcPaint = &rcClient;
|
|
}
|
|
if (IsRectEmpty(prcPaint))
|
|
return;
|
|
|
|
#ifdef USE_MEM_DC
|
|
int nClientWidth = prcPaint->right - prcPaint->left;
|
|
int nClientHeight = prcPaint->bottom - prcPaint->top;
|
|
HBITMAP hbmpMem;
|
|
hbmpMem = CreateCompatibleBitmap(hdc, nClientWidth, nClientHeight);
|
|
if (hbmpMem == NULL)
|
|
return;
|
|
HDC hdcMem;
|
|
hdcMem = CreateCompatibleDC(hdc);
|
|
if (hdcMem == NULL)
|
|
{
|
|
DeleteBitmap(hbmpMem);
|
|
return;
|
|
}
|
|
SetViewportOrgEx(hdcMem, -prcPaint->left, -prcPaint->top, NULL);
|
|
HBITMAP hbmpSafe = SelectBitmap(hdcMem, hbmpMem);
|
|
#else
|
|
// CS_PARENTDC sets the clipping rectangle of the child window to that of the parent window
|
|
// so that the child can draw on the parent. Text view inherits this style from sub-classed
|
|
// static control. This causes problems with unclipped TabbedTextOut() output.
|
|
HRGN hrgn = CreateRectRgnIndirect(prcPaint);
|
|
SelectClipRgn(hdc, hrgn);
|
|
DeleteRgn(hrgn);
|
|
HDC hdcMem = hdc;
|
|
#endif
|
|
|
|
FillRect(hdcMem, prcPaint, g_pResManager->m_hbrWindowBrush);
|
|
|
|
COLORREF rgbOldTextColor = SetTextColor(hdcMem, GetSysColor(COLOR_WINDOWTEXT));
|
|
COLORREF rgbOldBackground = SetBkColor(hdcMem, GetSysColor(COLOR_WINDOW));
|
|
HFONT hOldFont = g_pResManager->m_hFixedFont ? SelectFont(hdcMem, g_pResManager->m_hFixedFont) : NULL;
|
|
TEXTMETRIC tmetr;
|
|
::GetTextMetrics(hdcMem, &tmetr);
|
|
|
|
DWORD dwNumLines = (DWORD)m_arrLines.GetCount();
|
|
DWORD dwTopLineNum = GetScrollPos(m_hwnd, SB_VERT);
|
|
DWORD dwTopVisLineNum = dwTopLineNum + prcPaint->top / tmetr.tmHeight;
|
|
BOOL bSizeChanged = FALSE;
|
|
|
|
if (dwTopVisLineNum < dwNumLines)
|
|
{
|
|
int nHorPos = tmetr.tmAveCharWidth - GetScrollPos(m_hwnd, SB_HORZ);
|
|
int nVertPos = prcPaint->top - prcPaint->top % tmetr.tmHeight;
|
|
DWORD dwNumVisLines = prcPaint->bottom / tmetr.tmHeight;
|
|
if (prcPaint->bottom % tmetr.tmHeight)
|
|
++dwNumVisLines;
|
|
DWORD dwBottomVisLineNum = dwTopLineNum + dwNumVisLines - 1;
|
|
if (dwBottomVisLineNum >= dwNumLines)
|
|
dwBottomVisLineNum = dwNumLines - 1;
|
|
|
|
for (DWORD dwLineNum = dwTopVisLineNum; dwLineNum <= dwBottomVisLineNum; ++dwLineNum)
|
|
{
|
|
bSizeChanged |= CacheLine(dwLineNum, hdc, tmetr);
|
|
const CLineInfo& rLineInfo = m_arrLines[(size_t)dwLineNum];
|
|
int nTextWidth = LOWORD(TabbedTextOut(hdcMem, nHorPos, nVertPos, m_pTextCache + rLineInfo.m_dwTextStart, rLineInfo.m_dwLength, 0, NULL, -nHorPos));
|
|
if (rLineInfo.m_bTruncated)
|
|
TextOut(hdcMem, nHorPos + nTextWidth, nVertPos, g_szEllipsis, g_dwEllipsisLength);
|
|
nVertPos += tmetr.tmHeight;
|
|
}
|
|
}
|
|
|
|
SetTextColor(hdcMem, rgbOldTextColor);
|
|
SetBkColor(hdcMem, rgbOldBackground);
|
|
if (hOldFont)
|
|
SelectFont(hdcMem, hOldFont);
|
|
|
|
#ifdef USE_MEM_DC
|
|
BitBlt(hdc, prcPaint->left, prcPaint->top, nClientWidth, nClientHeight, hdcMem, prcPaint->left, prcPaint->top, SRCCOPY);
|
|
SelectBitmap(hdcMem, hbmpSafe);
|
|
DeleteDC(hdcMem);
|
|
DeleteBitmap(hbmpMem);
|
|
#endif
|
|
|
|
if (bSizeChanged)
|
|
RedrawWindow(m_hwnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE);
|
|
}
|
|
|
|
/**
|
|
* @param bIgnoreScrollPos - pass true to disregard any existing scrollbar styles.
|
|
*/
|
|
void CTextView::ResizeTextView(BOOL bIgnoreScrollPos)
|
|
{
|
|
TEXTMETRIC tmetr;
|
|
GetTextMetrics(&tmetr);
|
|
SCROLLINFO sinfo;
|
|
RECT rcClient;
|
|
|
|
GetClientRect(m_hwnd, &rcClient);
|
|
ZeroMemory(&sinfo, sizeof(sinfo));
|
|
sinfo.cbSize = sizeof(sinfo);
|
|
sinfo.fMask = SIF_POS | SIF_RANGE;
|
|
GetScrollInfo(m_hwnd, SB_HORZ, &sinfo);
|
|
int nOldHorPos = ! bIgnoreScrollPos ? sinfo.nPos : 0;
|
|
|
|
sinfo.fMask = SIF_POS | SIF_PAGE | SIF_RANGE;
|
|
sinfo.nMin = 0;
|
|
if (sinfo.nMax < m_nMaxLineWidth - 1)
|
|
sinfo.nMax = m_nMaxLineWidth - 1;
|
|
sinfo.nPage = rcClient.right;
|
|
ValidateScrollInfo(&sinfo);
|
|
int nHorOffset = nOldHorPos - sinfo.nPos;
|
|
SetScrollInfo(m_hwnd, SB_HORZ, &sinfo, TRUE);
|
|
|
|
GetClientRect(m_hwnd, &rcClient);
|
|
ZeroMemory(&sinfo, sizeof(sinfo));
|
|
sinfo.cbSize = sizeof(sinfo);
|
|
int nOldVertPos;
|
|
if (! bIgnoreScrollPos)
|
|
{
|
|
sinfo.fMask = SIF_POS;
|
|
GetScrollInfo(m_hwnd, SB_VERT, &sinfo);
|
|
nOldVertPos = sinfo.nPos;
|
|
}
|
|
else
|
|
nOldVertPos = sinfo.nPos = 0;
|
|
int nNumLines = (int)m_arrLines.GetCount();
|
|
sinfo.fMask = SIF_POS | SIF_PAGE | SIF_RANGE;
|
|
sinfo.nMin = 0;
|
|
sinfo.nMax = nNumLines > 0 ? nNumLines - 1 : 0;
|
|
sinfo.nPage = rcClient.bottom / tmetr.tmHeight;
|
|
ValidateScrollInfo(&sinfo);
|
|
int nVertOffset = (nOldVertPos - sinfo.nPos) * tmetr.tmHeight;
|
|
SetScrollInfo(m_hwnd, SB_VERT, &sinfo, TRUE);
|
|
|
|
GetClientRect(m_hwnd, &rcClient);
|
|
// Explicitly clip output to override style CS_PARENTDC.
|
|
ScrollWindowEx(m_hwnd, nHorOffset, nVertOffset, &rcClient, &rcClient, NULL, NULL, SW_INVALIDATE);
|
|
UpdateWindow(m_hwnd);
|
|
}
|
|
|
|
/**
|
|
* @param pTextMetric - pointer to text metric structure.
|
|
*/
|
|
void CTextView::GetTextMetrics(PTEXTMETRIC pTextMetric)
|
|
{
|
|
HDC hdc = GetDC(m_hwnd);
|
|
HFONT hOldFont = g_pResManager->m_hFixedFont ? SelectFont(hdc, g_pResManager->m_hFixedFont) : NULL;
|
|
::GetTextMetrics(hdc, pTextMetric);
|
|
if (hOldFont)
|
|
SelectFont(hdc, hOldFont);
|
|
ReleaseDC(m_hwnd, hdc);
|
|
}
|
|
|
|
/**
|
|
* @param nScrollBarType - scrollbar type.
|
|
* @param nScrollCode - scroll code.
|
|
*/
|
|
void CTextView::ScrollTextView(int nScrollBarType, int nScrollCode)
|
|
{
|
|
_ASSERTE(nScrollBarType == SB_HORZ || nScrollBarType == SB_VERT);
|
|
|
|
SCROLLINFO sinfo;
|
|
ZeroMemory(&sinfo, sizeof(sinfo));
|
|
sinfo.cbSize = sizeof(sinfo);
|
|
sinfo.fMask = SIF_POS | SIF_PAGE | SIF_RANGE;
|
|
if (nScrollCode == SB_THUMBTRACK)
|
|
sinfo.fMask |= SIF_TRACKPOS;
|
|
GetScrollInfo(m_hwnd, nScrollBarType, &sinfo);
|
|
int nOldPos = sinfo.nPos;
|
|
|
|
TEXTMETRIC tmetr;
|
|
GetTextMetrics(&tmetr);
|
|
int nLineOffset = nScrollBarType == SB_HORZ ? tmetr.tmAveCharWidth : 1;
|
|
|
|
switch (nScrollCode)
|
|
{
|
|
case SB_LINEUP:
|
|
//case SB_LINELEFT:
|
|
sinfo.nPos -= nLineOffset;
|
|
break;
|
|
case SB_LINEDOWN:
|
|
//case SB_LINERIGHT:
|
|
sinfo.nPos += nLineOffset;
|
|
break;
|
|
case SB_PAGEUP:
|
|
//case SB_PAGELEFT:
|
|
sinfo.nPos -= sinfo.nPage;
|
|
break;
|
|
case SB_PAGEDOWN:
|
|
//case SB_PAGERIGHT:
|
|
sinfo.nPos += sinfo.nPage;
|
|
break;
|
|
case SB_THUMBTRACK:
|
|
sinfo.nPos = sinfo.nTrackPos;
|
|
break;
|
|
case SB_TOP:
|
|
//case SB_LEFT:
|
|
sinfo.nPos = 0;
|
|
break;
|
|
case SB_BOTTOM:
|
|
//case SB_RIGHT:
|
|
sinfo.nPos = sinfo.nMax;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
ValidateScrollInfo(&sinfo);
|
|
sinfo.fMask = SIF_POS;
|
|
SetScrollInfo(m_hwnd, nScrollBarType, &sinfo, TRUE);
|
|
|
|
if (sinfo.nPos != nOldPos)
|
|
{
|
|
int nHorOffset, nVertOffset;
|
|
if (nScrollBarType == SB_HORZ)
|
|
{
|
|
nHorOffset = nOldPos - sinfo.nPos;
|
|
nVertOffset = 0;
|
|
}
|
|
else
|
|
{
|
|
nHorOffset = 0;
|
|
nVertOffset = (nOldPos - sinfo.nPos) * tmetr.tmHeight;
|
|
}
|
|
|
|
RECT rcClient;
|
|
GetClientRect(m_hwnd, &rcClient);
|
|
// Explicitly clip output to override style CS_PARENTDC.
|
|
ScrollWindowEx(m_hwnd, nHorOffset, nVertOffset, &rcClient, &rcClient, NULL, NULL, SW_INVALIDATE);
|
|
UpdateWindow(m_hwnd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param hwnd - window handle.
|
|
* @param uMsg - message identifier.
|
|
* @param wParam - first message parameter.
|
|
* @param lParam - second message parameter.
|
|
*/
|
|
LRESULT CALLBACK CTextView::TextViewWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hdc;
|
|
int zDelta, zTotal, nScrollCode, nScrollBarType;
|
|
LONG lWindowStyle;
|
|
|
|
CTextView* _this = (CTextView*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
|
|
_ASSERTE(_this != NULL);
|
|
switch(uMsg)
|
|
{
|
|
case WM_LBUTTONDOWN:
|
|
SetFocus(hwnd);
|
|
return 0;
|
|
case WM_ERASEBKGND:
|
|
return TRUE;
|
|
case WM_PAINT:
|
|
hdc = (HDC)wParam;
|
|
if (! hdc)
|
|
{
|
|
hdc = BeginPaint(hwnd, &ps);
|
|
if (hdc)
|
|
{
|
|
_this->DrawTextView(hdc, &ps.rcPaint);
|
|
EndPaint(hwnd, &ps);
|
|
}
|
|
}
|
|
else
|
|
_this->DrawTextView(hdc, NULL);
|
|
return 0;
|
|
case WM_PRINTCLIENT:
|
|
hdc = (HDC)wParam;
|
|
_this->DrawTextView(hdc, NULL);
|
|
return 0;
|
|
case WM_SIZE:
|
|
_this->ResizeTextView(FALSE);
|
|
return 0;
|
|
case WM_KEYDOWN:
|
|
switch (wParam)
|
|
{
|
|
case VK_LEFT:
|
|
_this->ScrollTextView(SB_HORZ, SB_LINELEFT);
|
|
break;
|
|
case VK_UP:
|
|
_this->ScrollTextView(SB_VERT, SB_LINEUP);
|
|
break;
|
|
case VK_RIGHT:
|
|
_this->ScrollTextView(SB_HORZ, SB_LINERIGHT);
|
|
break;
|
|
case VK_DOWN:
|
|
_this->ScrollTextView(SB_VERT, SB_LINEDOWN);
|
|
break;
|
|
case VK_HOME:
|
|
if (GetKeyState(VK_CONTROL) < 0)
|
|
_this->ScrollTextView(SB_VERT, SB_TOP);
|
|
else
|
|
_this->ScrollTextView(SB_HORZ, SB_LEFT);
|
|
break;
|
|
case VK_END:
|
|
if (GetKeyState(VK_CONTROL) < 0)
|
|
_this->ScrollTextView(SB_VERT, SB_BOTTOM);
|
|
else
|
|
_this->ScrollTextView(SB_HORZ, SB_RIGHT);
|
|
break;
|
|
case VK_PRIOR:
|
|
_this->ScrollTextView(SB_VERT, SB_PAGEUP);
|
|
break;
|
|
case VK_NEXT:
|
|
_this->ScrollTextView(SB_VERT, SB_PAGEDOWN);
|
|
break;
|
|
}
|
|
return 0;
|
|
case WM_HSCROLL:
|
|
_this->ScrollTextView(SB_HORZ, LOWORD(wParam));
|
|
return 0;
|
|
case WM_VSCROLL:
|
|
_this->ScrollTextView(SB_VERT, LOWORD(wParam));
|
|
return 0;
|
|
case WM_MOUSEWHEEL:
|
|
zDelta = (int)(short)HIWORD(wParam);
|
|
if (_this->m_nWheelLines == WHEEL_PAGESCROLL)
|
|
nScrollCode = zDelta > 0 ? SB_PAGEUP : SB_PAGEDOWN;
|
|
else
|
|
nScrollCode = zDelta > 0 ? SB_LINEUP : SB_LINEDOWN;
|
|
zTotal = abs(zDelta) / WHEEL_DELTA;
|
|
if (_this->m_nWheelLines != WHEEL_PAGESCROLL)
|
|
zTotal *= _this->m_nWheelLines;
|
|
lWindowStyle = GetWindowLong(hwnd, GWL_STYLE);
|
|
|
|
if (lWindowStyle & WS_VSCROLL)
|
|
nScrollBarType = SB_VERT;
|
|
else if (lWindowStyle & WS_HSCROLL)
|
|
nScrollBarType = SB_HORZ;
|
|
else
|
|
return 0;
|
|
for (int i = 0; i < zTotal; ++i)
|
|
_this->ScrollTextView(nScrollBarType, nScrollCode);
|
|
return 0;
|
|
case WM_SETTINGCHANGE:
|
|
if (wParam == SPI_SETWHEELSCROLLLINES)
|
|
{
|
|
if (! SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &_this->m_nWheelLines, 0))
|
|
_this->m_nWheelLines = g_dwDefaultNumWheels;
|
|
}
|
|
return 0;
|
|
case WM_GETDLGCODE:
|
|
return DLGC_WANTARROWS;
|
|
default:
|
|
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param hwnd - text view window handle.
|
|
*/
|
|
void CTextView::Attach(HWND hwnd)
|
|
{
|
|
_ASSERTE(hwnd != NULL);
|
|
_ASSERTE(m_hwnd == NULL);
|
|
_ASSERTE(g_pResManager != NULL);
|
|
|
|
if (! SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &m_nWheelLines, 0))
|
|
m_nWheelLines = g_dwDefaultNumWheels;
|
|
|
|
m_hwnd = hwnd;
|
|
m_pfnOldTextViewWndProc = SubclassWindow(hwnd, TextViewWndProc);
|
|
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)this);
|
|
// Preserve original window styles that could be modified by SetScrollInfo().
|
|
m_lOldStyle = GetWindowLong(hwnd, GWL_STYLE);
|
|
ResizeTextView(TRUE);
|
|
InvalidateRect(m_hwnd, NULL, FALSE);
|
|
}
|
|
|
|
void CTextView::Detach(void)
|
|
{
|
|
if (m_pfnOldTextViewWndProc)
|
|
{
|
|
SubclassWindow(m_hwnd, m_pfnOldTextViewWndProc);
|
|
SetWindowLongPtr(m_hwnd, GWLP_USERDATA, NULL);
|
|
|
|
SCROLLINFO sinfo;
|
|
ZeroMemory(&sinfo, sizeof(sinfo));
|
|
sinfo.cbSize = sizeof(sinfo);
|
|
sinfo.fMask = SIF_POS | SIF_PAGE | SIF_RANGE;
|
|
SetScrollInfo(m_hwnd, SB_HORZ, &sinfo, FALSE);
|
|
SetScrollInfo(m_hwnd, SB_VERT, &sinfo, FALSE);
|
|
SetWindowLong(m_hwnd, GWL_STYLE, m_lOldStyle);
|
|
|
|
InvalidateRect(m_hwnd, NULL, TRUE);
|
|
InitVars();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param hFile - file handle.
|
|
* @param eEncoding - text encoding.
|
|
* @param dwSignatureSize - size of file signature.
|
|
*/
|
|
void CTextView::SetFile(HANDLE hFile, TEXT_ENCODING eEncoding, DWORD dwSignatureSize)
|
|
{
|
|
_ASSERTE(hFile != INVALID_HANDLE_VALUE);
|
|
_ASSERTE(m_hFile == INVALID_HANDLE_VALUE);
|
|
_ASSERTE(m_hwnd != NULL);
|
|
m_hFile = hFile;
|
|
m_pDecoder = CBaseDecoder::GetDecoder(eEncoding);
|
|
_ASSERTE(m_pDecoder != NULL);
|
|
SetFilePointer(hFile, dwSignatureSize, NULL, FILE_BEGIN);
|
|
CountLines();
|
|
LoadCache();
|
|
ResizeTextView(TRUE);
|
|
InvalidateRect(m_hwnd, NULL, FALSE);
|
|
}
|
|
|
|
void CTextView::ResetFile(void)
|
|
{
|
|
m_hFile = INVALID_HANDLE_VALUE;
|
|
m_pDecoder = NULL;
|
|
m_arrLines.DeleteAll(true);
|
|
m_dwFirstCachedLine = m_dwNumCachedLines = 0;
|
|
delete[] m_pTextCache;
|
|
m_pTextCache = NULL;
|
|
m_dwLineBufferSize = 0;
|
|
m_nMaxLineWidth = 0;
|
|
delete[] m_pLineBuffer;
|
|
m_pLineBuffer = NULL;
|
|
}
|
|
|
|
void CTextView::CountLines(void)
|
|
{
|
|
_ASSERTE(m_hFile != INVALID_HANDLE_VALUE);
|
|
m_arrLines.DeleteAll(false);
|
|
|
|
DWORD dwFileSize = GetFileSize(m_hFile, NULL);
|
|
DWORD dwFilePos = SetFilePointer(m_hFile, 0, NULL, FILE_CURRENT);
|
|
dwFileSize -= dwFilePos;
|
|
DWORD dwBufferSize = min(dwFileSize, g_dwMaxBufferSize);
|
|
PBYTE pFileBuffer = new BYTE[dwBufferSize];
|
|
if (pFileBuffer == NULL)
|
|
return;
|
|
|
|
m_arrLines.EnsureSize(dwFileSize / APROX_NUMBER_OF_SYMBOLS, false); // approximate number of lines
|
|
DWORD dwBufferPos = 0, dwNumSymbols = 0, dwNumChars = 0, dwLineStartPos = dwFilePos, dwLineEndPos = dwFilePos;
|
|
TCHAR chPrevLineEnd = _T('\0');
|
|
m_dwLineBufferSize = 0;
|
|
|
|
for (;;)
|
|
{
|
|
DWORD dwNumRead = 0, dwFreeSize = dwBufferSize - dwBufferPos;
|
|
if (! ReadFile(m_hFile, pFileBuffer + dwBufferPos, dwFreeSize, &dwNumRead, NULL))
|
|
break;
|
|
BOOL bEndOfFile = dwNumRead < dwFreeSize;
|
|
dwNumRead += dwBufferPos;
|
|
dwBufferPos = 0;
|
|
if (dwNumRead == 0)
|
|
break;
|
|
for (;;)
|
|
{
|
|
DWORD dwBytesLeft = dwNumRead - dwBufferPos;
|
|
// one character in UTF-16 encoding may require up to 4 bytes
|
|
// one character in UTF-8 encoding may require up to 4 bytes
|
|
// one character in ANSI encoding may require up to 2 bytes
|
|
// make sure at least one character can be read from the buffer
|
|
// (i.e. at least 4 bytes should be available)
|
|
if (dwBytesLeft < 4 && ! bEndOfFile)
|
|
{
|
|
MoveMemory(pFileBuffer, pFileBuffer + dwBufferPos, dwBytesLeft);
|
|
dwBufferPos = dwBytesLeft;
|
|
break;
|
|
}
|
|
BOOL bGotoNewLine, bSkipLineEnd, bLineEndChar;
|
|
if (dwBytesLeft > 0)
|
|
{
|
|
TCHAR arrChar[2];
|
|
size_t nNumBytesInChar, nCharSize;
|
|
nNumBytesInChar = m_pDecoder->DecodeChar(pFileBuffer + dwBufferPos, dwBytesLeft, arrChar, nCharSize);
|
|
if (nCharSize == 0 || nCharSize == MAXSIZE_T)
|
|
{
|
|
++dwBufferPos;
|
|
++dwFilePos;
|
|
++dwLineEndPos;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
dwBufferPos += (DWORD)nNumBytesInChar;
|
|
dwFilePos += (DWORD)nNumBytesInChar;
|
|
}
|
|
if (nCharSize == 1 && (arrChar[0] == _T('\r') || arrChar[0] == _T('\n')))
|
|
{
|
|
bGotoNewLine = bLineEndChar = TRUE;
|
|
if (chPrevLineEnd != _T('\0') && chPrevLineEnd != arrChar[0])
|
|
{
|
|
chPrevLineEnd = _T('\0');
|
|
bSkipLineEnd = TRUE;
|
|
}
|
|
else
|
|
{
|
|
chPrevLineEnd = arrChar[0];
|
|
bSkipLineEnd = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++dwNumSymbols;
|
|
dwNumChars += (DWORD)nCharSize;
|
|
bGotoNewLine = dwNumSymbols == MAX_NUMBER_OF_SYMBOLS;
|
|
chPrevLineEnd = _T('\0');
|
|
bSkipLineEnd = bLineEndChar = FALSE;
|
|
dwLineEndPos = dwFilePos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bGotoNewLine = TRUE;
|
|
bSkipLineEnd = FALSE;
|
|
bLineEndChar = TRUE;
|
|
}
|
|
if (bGotoNewLine)
|
|
{
|
|
if (! bSkipLineEnd)
|
|
{
|
|
CLineInfo LineInfo;
|
|
LineInfo.m_dwLineStart = dwLineStartPos;
|
|
LineInfo.m_dwLength = dwNumChars;
|
|
LineInfo.m_dwSize = dwLineEndPos - dwLineStartPos;
|
|
LineInfo.m_bTruncated = ! bLineEndChar;
|
|
m_arrLines.AddItem(LineInfo);
|
|
if (m_dwLineBufferSize < LineInfo.m_dwSize)
|
|
m_dwLineBufferSize = LineInfo.m_dwSize;
|
|
if (dwBytesLeft == 0)
|
|
goto end;
|
|
}
|
|
dwLineStartPos = dwLineEndPos = dwFilePos;
|
|
dwNumSymbols = 0;
|
|
dwNumChars = 0;
|
|
}
|
|
}
|
|
}
|
|
end:
|
|
delete[] pFileBuffer;
|
|
// reserve space for two line end characters in order to retrieve contiguous lines using ReadFile() sequentially
|
|
// (for Unicode encoding this requires 4 bytes)
|
|
m_dwLineBufferSize += 4;
|
|
// add some extra space to the buffer to let several lines to be loaded
|
|
if (m_dwLineBufferSize < LINE_BUFFER_SIZE)
|
|
m_dwLineBufferSize = LINE_BUFFER_SIZE;
|
|
m_pLineBuffer = new BYTE[m_dwLineBufferSize];
|
|
if (m_pLineBuffer == NULL)
|
|
ResetFile();
|
|
}
|
|
|
|
void CTextView::LoadCache(void)
|
|
{
|
|
_ASSERTE(m_hFile != INVALID_HANDLE_VALUE);
|
|
m_dwFirstCachedLine = m_dwNumCachedLines = 0;
|
|
m_nMaxLineWidth = 0;
|
|
DWORD dwNumLines = (DWORD)m_arrLines.GetCount();
|
|
if (dwNumLines > 0)
|
|
{
|
|
HDC hdc = GetDC(m_hwnd);
|
|
HFONT hOldFont = g_pResManager->m_hFixedFont ? SelectFont(hdc, g_pResManager->m_hFixedFont) : NULL;
|
|
TEXTMETRIC tmetr;
|
|
::GetTextMetrics(hdc, &tmetr);
|
|
int nExtraWidth = tmetr.tmAveCharWidth + LOWORD(GetTabbedTextExtent(hdc, g_szEllipsis, g_dwEllipsisLength, 0, NULL));
|
|
|
|
if (m_pTextCache == NULL)
|
|
{
|
|
_ASSERTE(MAX_NUMBER_OF_SYMBOLS <= TEXT_CACHE_SIZE);
|
|
m_pTextCache = new TCHAR[TEXT_CACHE_SIZE];
|
|
if (m_pTextCache == NULL)
|
|
{
|
|
ResetFile();
|
|
goto end;
|
|
}
|
|
}
|
|
const CLineInfo& rLineInfo = m_arrLines[(size_t)0];
|
|
SetFilePointer(m_hFile, rLineInfo.m_dwLineStart, NULL, FILE_BEGIN);
|
|
DWORD dwLineBufferPos = 0, dwTextCachePos = 0;
|
|
for (;;)
|
|
{
|
|
DWORD dwNumRead = 0;
|
|
if (! ReadFile(m_hFile, m_pLineBuffer + dwLineBufferPos, m_dwLineBufferSize - dwLineBufferPos, &dwNumRead, NULL))
|
|
goto end;
|
|
dwNumRead += dwLineBufferPos;
|
|
dwLineBufferPos = 0;
|
|
if (dwNumRead == 0)
|
|
break;
|
|
for (;;)
|
|
{
|
|
DWORD dwBytesLeft = dwNumRead - dwLineBufferPos;
|
|
if (dwBytesLeft == 0)
|
|
{
|
|
dwLineBufferPos = 0;
|
|
break;
|
|
}
|
|
CLineInfo& rLineInfo = m_arrLines[(size_t)m_dwNumCachedLines];
|
|
if (dwTextCachePos + rLineInfo.m_dwLength > TEXT_CACHE_SIZE)
|
|
goto end;
|
|
DWORD dwLineSize;
|
|
if (m_dwNumCachedLines + 1 < dwNumLines)
|
|
{
|
|
const CLineInfo& rNextLineInfo = m_arrLines[(size_t)(m_dwNumCachedLines + 1)];
|
|
dwLineSize = rNextLineInfo.m_dwLineStart - rLineInfo.m_dwLineStart; // line size includes line end
|
|
}
|
|
else
|
|
dwLineSize = rLineInfo.m_dwSize;
|
|
if (dwLineSize > dwBytesLeft)
|
|
{
|
|
MoveMemory(m_pLineBuffer, m_pLineBuffer + dwLineBufferPos, dwBytesLeft);
|
|
dwLineBufferPos = dwBytesLeft;
|
|
break;
|
|
}
|
|
m_pDecoder->DecodeString(m_pLineBuffer + dwLineBufferPos, rLineInfo.m_dwSize, m_pTextCache + dwTextCachePos, rLineInfo.m_dwLength);
|
|
int nLineWidth = LOWORD(GetTabbedTextExtent(hdc, m_pTextCache + dwTextCachePos, rLineInfo.m_dwLength, 0, NULL));
|
|
if (m_nMaxLineWidth < nLineWidth)
|
|
m_nMaxLineWidth = nLineWidth;
|
|
|
|
rLineInfo.m_dwTextStart = dwTextCachePos;
|
|
dwTextCachePos += rLineInfo.m_dwLength;
|
|
dwLineBufferPos += dwLineSize;
|
|
++m_dwNumCachedLines;
|
|
}
|
|
}
|
|
end:
|
|
m_nMaxLineWidth += nExtraWidth;
|
|
ResizeTextView(TRUE);
|
|
if (hOldFont)
|
|
SelectFont(hdc, hOldFont);
|
|
ReleaseDC(m_hwnd, hdc);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param dwCachedLineNum - index of cached line.
|
|
* @param hdc - graphics device context.
|
|
* @param tmetr - text metrics.
|
|
* @return true if window size has been changed.
|
|
*/
|
|
BOOL CTextView::CacheLine(DWORD dwCachedLineNum, HDC hdc, const TEXTMETRIC& tmetr)
|
|
{
|
|
if (dwCachedLineNum >= m_dwFirstCachedLine && dwCachedLineNum < m_dwFirstCachedLine + m_dwNumCachedLines)
|
|
return FALSE;
|
|
|
|
int nExtraWidth = tmetr.tmAveCharWidth + LOWORD(GetTabbedTextExtent(hdc, g_szEllipsis, g_dwEllipsisLength, 0, NULL));
|
|
int nMaxLineWidth = 0;
|
|
|
|
DWORD dwNumLines = (DWORD)m_arrLines.GetCount();
|
|
_ASSERT(dwCachedLineNum < dwNumLines);
|
|
// Put new line into the middle of cache
|
|
const CLineInfo& rLineInfo = m_arrLines[(size_t)dwCachedLineNum];
|
|
DWORD dwFirstCachedLine = dwCachedLineNum,
|
|
dwLastCachedLine = dwCachedLineNum,
|
|
dwTotalSize = rLineInfo.m_dwLength;
|
|
BOOL bCanMoveFirstCachedLine = TRUE, bCanMoveLastCachedLine = TRUE;
|
|
do
|
|
{
|
|
if (bCanMoveFirstCachedLine)
|
|
{
|
|
BOOL bLineNumberChanged = FALSE;
|
|
if (dwFirstCachedLine > 0)
|
|
{
|
|
const CLineInfo& rLineInfo = m_arrLines[(size_t)(dwFirstCachedLine - 1)];
|
|
if (dwTotalSize + rLineInfo.m_dwLength <= TEXT_CACHE_SIZE)
|
|
{
|
|
dwTotalSize += rLineInfo.m_dwLength;
|
|
--dwFirstCachedLine;
|
|
bLineNumberChanged = TRUE;
|
|
}
|
|
}
|
|
bCanMoveFirstCachedLine = bLineNumberChanged;
|
|
}
|
|
if (bCanMoveLastCachedLine)
|
|
{
|
|
BOOL bLineNumberChanged = FALSE;
|
|
if (dwLastCachedLine + 1 < dwNumLines)
|
|
{
|
|
const CLineInfo& rLineInfo = m_arrLines[(size_t)(dwLastCachedLine + 1)];
|
|
if (dwTotalSize + rLineInfo.m_dwLength <= TEXT_CACHE_SIZE)
|
|
{
|
|
dwTotalSize += rLineInfo.m_dwLength;
|
|
++dwLastCachedLine;
|
|
bLineNumberChanged = TRUE;
|
|
}
|
|
}
|
|
bCanMoveLastCachedLine = bLineNumberChanged;
|
|
}
|
|
}
|
|
while (bCanMoveFirstCachedLine || bCanMoveLastCachedLine);
|
|
|
|
DWORD dwNumCachedLines = dwLastCachedLine - dwFirstCachedLine + 1;
|
|
DWORD dwFirstMappedLine, dwLastMappedLine, dwNumMappedLines, dwFirstLoadedLine, dwLastLoadedLine, dwNumLoadedLines, dwFromMemOffset, dwToMemOffset, dwLoadOffset, dwMemSize;
|
|
if (m_dwFirstCachedLine <= dwFirstCachedLine && dwFirstCachedLine < m_dwFirstCachedLine + m_dwNumCachedLines)
|
|
{
|
|
dwFirstMappedLine = dwFirstCachedLine;
|
|
dwNumMappedLines = m_dwNumCachedLines - (dwFirstCachedLine - m_dwFirstCachedLine);
|
|
dwLastMappedLine = dwFirstMappedLine + dwNumMappedLines - 1;
|
|
|
|
dwFirstLoadedLine = dwFirstMappedLine + dwNumMappedLines;
|
|
dwNumLoadedLines = dwNumCachedLines - dwNumMappedLines;
|
|
dwLastLoadedLine = dwFirstLoadedLine + dwNumLoadedLines - 1;
|
|
|
|
const CLineInfo& rLineInfo1 = m_arrLines[(size_t)dwFirstMappedLine];
|
|
const CLineInfo& rLineInfo2 = m_arrLines[(size_t)dwLastMappedLine];
|
|
dwFromMemOffset = rLineInfo1.m_dwTextStart;
|
|
dwToMemOffset = 0;
|
|
dwLoadOffset = dwMemSize = rLineInfo2.m_dwTextStart + rLineInfo2.m_dwLength - rLineInfo1.m_dwTextStart;
|
|
}
|
|
else if (dwFirstCachedLine <= m_dwFirstCachedLine && m_dwFirstCachedLine < dwFirstCachedLine + dwNumCachedLines)
|
|
{
|
|
dwFirstMappedLine = m_dwFirstCachedLine;
|
|
dwNumMappedLines = dwNumCachedLines - (m_dwFirstCachedLine - dwFirstCachedLine);
|
|
dwLastMappedLine = dwFirstMappedLine + dwNumMappedLines - 1;
|
|
|
|
dwFirstLoadedLine = dwFirstCachedLine;
|
|
dwNumLoadedLines = dwNumCachedLines - dwNumMappedLines;
|
|
dwLastLoadedLine = dwFirstLoadedLine + dwNumLoadedLines - 1;
|
|
|
|
const CLineInfo& rLineInfo1 = m_arrLines[(size_t)dwFirstMappedLine];
|
|
const CLineInfo& rLineInfo2 = m_arrLines[(size_t)dwLastMappedLine];
|
|
dwFromMemOffset = rLineInfo1.m_dwTextStart;
|
|
dwToMemOffset = 0;
|
|
dwMemSize = rLineInfo2.m_dwTextStart + rLineInfo2.m_dwLength - rLineInfo1.m_dwTextStart;
|
|
for (DWORD dwLineNum = dwFirstLoadedLine; dwLineNum <= dwLastLoadedLine; ++dwLineNum)
|
|
{
|
|
const CLineInfo& rLineInfo = m_arrLines[(size_t)dwLineNum];
|
|
dwToMemOffset += rLineInfo.m_dwLength;
|
|
}
|
|
dwLoadOffset = 0;
|
|
}
|
|
else
|
|
{
|
|
dwFirstMappedLine = dwLastMappedLine = dwNumMappedLines = dwFromMemOffset = dwToMemOffset = dwMemSize = 0;
|
|
|
|
dwFirstLoadedLine = dwFirstCachedLine;
|
|
dwNumLoadedLines = dwNumCachedLines;
|
|
dwLastLoadedLine = dwFirstLoadedLine + dwNumLoadedLines - 1;
|
|
dwLoadOffset = 0;
|
|
}
|
|
|
|
_ASSERTE(m_pTextCache != NULL);
|
|
if (dwNumMappedLines > 0)
|
|
{
|
|
MoveMemory(m_pTextCache + dwToMemOffset, m_pTextCache + dwFromMemOffset, dwMemSize * sizeof(TCHAR));
|
|
for (DWORD dwLineNum = dwFirstMappedLine; dwLineNum <= dwLastMappedLine; ++dwLineNum)
|
|
{
|
|
CLineInfo& rLineInfo = m_arrLines[(size_t)dwLineNum];
|
|
rLineInfo.m_dwTextStart = dwToMemOffset;
|
|
dwToMemOffset += rLineInfo.m_dwLength;
|
|
}
|
|
}
|
|
|
|
if (dwNumLoadedLines > 0)
|
|
{
|
|
DWORD dwLineBufferPos = 0, dwLineNum = dwFirstLoadedLine;
|
|
const CLineInfo& rLineInfo = m_arrLines[(size_t)dwLineNum];
|
|
SetFilePointer(m_hFile, rLineInfo.m_dwLineStart, NULL, FILE_BEGIN);
|
|
while (dwLineNum <= dwLastLoadedLine)
|
|
{
|
|
DWORD dwNumRead = 0;
|
|
if (! ReadFile(m_hFile, m_pLineBuffer + dwLineBufferPos, m_dwLineBufferSize - dwLineBufferPos, &dwNumRead, NULL))
|
|
return FALSE;
|
|
dwNumRead += dwLineBufferPos;
|
|
dwLineBufferPos = 0;
|
|
if (dwNumRead == 0)
|
|
break;
|
|
while (dwLineNum <= dwLastLoadedLine)
|
|
{
|
|
CLineInfo& rLineInfo = m_arrLines[(size_t)dwLineNum];
|
|
DWORD dwLineSize;
|
|
if (dwLineNum < dwLastLoadedLine)
|
|
{
|
|
const CLineInfo& rNextLineInfo = m_arrLines[(size_t)(dwLineNum + 1)];
|
|
dwLineSize = rNextLineInfo.m_dwLineStart - rLineInfo.m_dwLineStart; // line size including line end
|
|
}
|
|
else
|
|
dwLineSize = rLineInfo.m_dwSize;
|
|
DWORD dwBytesLeft = dwNumRead - dwLineBufferPos;
|
|
if (dwLineSize > dwBytesLeft)
|
|
{
|
|
MoveMemory(m_pLineBuffer, m_pLineBuffer + dwLineBufferPos, dwBytesLeft);
|
|
dwLineBufferPos = dwBytesLeft;
|
|
break;
|
|
}
|
|
m_pDecoder->DecodeString(m_pLineBuffer + dwLineBufferPos, rLineInfo.m_dwSize, m_pTextCache + dwLoadOffset, rLineInfo.m_dwLength);
|
|
int nLineWidth = LOWORD(GetTabbedTextExtent(hdc, m_pTextCache + dwLoadOffset, rLineInfo.m_dwLength, 0, NULL));
|
|
if (nMaxLineWidth < nLineWidth)
|
|
nMaxLineWidth = nLineWidth;
|
|
|
|
rLineInfo.m_dwTextStart = dwLoadOffset;
|
|
dwLoadOffset += rLineInfo.m_dwLength;
|
|
dwLineBufferPos += dwLineSize;
|
|
++dwLineNum;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_dwFirstCachedLine = dwFirstCachedLine;
|
|
m_dwNumCachedLines = dwNumCachedLines;
|
|
nMaxLineWidth += nExtraWidth;
|
|
BOOL bSizeChanged;
|
|
if (m_nMaxLineWidth < nMaxLineWidth)
|
|
{
|
|
m_nMaxLineWidth = nMaxLineWidth;
|
|
SCROLLINFO sinfo;
|
|
ZeroMemory(&sinfo, sizeof(sinfo));
|
|
sinfo.cbSize = sizeof(sinfo);
|
|
sinfo.fMask = SIF_RANGE;
|
|
sinfo.nMin = 0;
|
|
sinfo.nMax = nMaxLineWidth - 1;
|
|
SetScrollInfo(m_hwnd, SB_HORZ, &sinfo, TRUE);
|
|
bSizeChanged = TRUE;
|
|
}
|
|
else
|
|
bSizeChanged = FALSE;
|
|
|
|
return bSizeChanged;
|
|
}
|