Added some basic text display and editing capabilities, cursor

positioning, a bit of scrolling and a text file loader.
oldstable
Mike McCormack 2000-07-23 14:22:47 +00:00 committed by Alexandre Julliard
parent 7f392f5e1b
commit 0e2d0e076f
3 changed files with 727 additions and 16 deletions

View File

@ -55,7 +55,7 @@ void AlertFileNotFound(LPSTR szFileName) {
/* Load and format szMessage */
LoadString(Globals.hInstance, IDS_NOTFOUND, szRessource, sizeof(szRessource));
wvsprintf(szMessage, szRessource, szFileName);
wsprintf(szMessage, szRessource, szFileName);
/* Load szCaption */
LoadString(Globals.hInstance, IDS_ERROR, szRessource, sizeof(szRessource));
@ -74,7 +74,7 @@ int AlertFileNotSaved(LPSTR szFileName) {
/* Load and format Message */
LoadString(Globals.hInstance, IDS_NOTSAVED, szRessource, sizeof(szRessource));
wvsprintf(szMessage, szRessource, szFileName);
wsprintf(szMessage, szRessource, szFileName);
/* Load Caption */
@ -146,19 +146,12 @@ BOOL DoCloseFile(void) {
void DoOpenFile(LPCSTR szFileName) {
int hFile;
WORD nResult;
/* Close any files and prompt to save changes */
if (DoCloseFile()) {
GetFileTitle(szFileName, Globals.szFileName, sizeof(Globals.szFileName));
LANGUAGE_UpdateWindowCaption();
hFile = _lopen(szFileName, OF_READ);
nResult = _lread(hFile, Globals.Buffer, sizeof(Globals.Buffer));
_lclose(hFile);
/* FIXME: Append time/date if first line contains LOGPREFIX */
/* (Globals.Buffer, ) */
LoadBufferFromFile(szFileName);
}
}
@ -167,7 +160,7 @@ VOID DIALOG_FileNew(VOID)
{
/* Close any files and promt to save changes */
if (DoCloseFile()) {
/* do nothing yet */
TrashBuffer();
}
}

View File

@ -1,8 +1,25 @@
/*
* Notepad
*
* Copyright 2000 Mike McCormack <Mike_McCormack@looksmart.com.au>
* Copyright 1997,98 Marcel Baur <mbaur@g26.ethz.ch>
* To be distributed under the Wine License
*
* FIXME,TODO list:
* - Use wine Heap instead of malloc/free (done)
* - use scroll bars (vertical done)
* - cut 'n paste (clipboard)
* - save file
* - print file
* - find dialog
* - encapsulate data structures (?) - half done
* - free unused memory
* - solve Open problems
* - smoother scrolling
* - seperate view code and document code
*
* This program is intended as a testbed for winelib as much as
* a useful application.
*/
#include <stdio.h>
@ -22,6 +39,664 @@ extern void DoOpenFile(LPCSTR szFileName);
NOTEPAD_GLOBALS Globals;
/* Using a pointer to pointer data structure to
achieve a little more efficiency. Hopefully
it will be worth it, because it complicates the
code - mjm 26 Jun 2000 */
#define BUFFERCHUNKSIZE 0xe0
typedef struct TAGLine {
LPSTR lpLine;
DWORD dwWidth;
DWORD dwMaxWidth;
} LINE, *LPLINE;
/* FIXME: make this info into a structure */
/* typedef struct tagBUFFER { */
DWORD dwVOffset=0;
DWORD dwLines=0;
DWORD dwMaxLines=0;
LPLINE lpBuffer=NULL;
DWORD dwXpos=0,dwYpos=0; /* position of caret in char coords */
DWORD dwCaretXpos=0,dwCaretYpos=0; /* position of caret in pixel coords */
TEXTMETRIC tm; /* textmetric for current font */
RECT rectClient; /* client rectangle of the window we're drawing in */
/* } BUFFER, *LPBUFFER */
VOID InitFontInfo(HWND hWnd)
{
HDC hDC = GetDC(hWnd);
if(hDC)
{
GetTextMetrics(hDC, &tm);
ReleaseDC(hWnd,hDC);
}
}
void InitBuffer(void)
{
lpBuffer = NULL;
dwLines = 0;
dwMaxLines = 0;
dwXpos=0;
dwYpos=0;
}
/* convert x,y character co-ords into x pixel co-ord */
DWORD CalcStringWidth(HDC hDC, DWORD x, DWORD y)
{
DWORD len;
SIZE size;
size.cx = 0;
size.cy = 0;
if(y>dwLines)
return size.cx;
if(lpBuffer == NULL)
return size.cx;
if(lpBuffer[y].lpLine == NULL)
return size.cx;
len = (x<lpBuffer[y].dwWidth) ?
x : lpBuffer[y].dwWidth;
GetTextExtentPoint(hDC, lpBuffer[y].lpLine, len, &size);
return size.cx;
}
void CalcCaretPos(HDC hDC, DWORD dwXpos, DWORD dwYpos)
{
dwCaretXpos = CalcStringWidth(hDC, dwXpos, dwYpos);
dwCaretYpos = tm.tmHeight*(dwYpos-dwVOffset);
SetCaretPos(dwCaretXpos,dwCaretYpos);
}
DWORD GetLinesPerPage(HWND hWnd)
{
return (rectClient.bottom/tm.tmHeight); /* round down */
}
/* render one line of text and blank space */
void RenderLine(HDC hDC, DWORD lineno)
{
RECT rect;
HBRUSH hPrev;
if(!hDC)
return;
/* erase the space at the end of a line using a white rectangle */
rect.top = tm.tmHeight*(lineno-dwVOffset);
rect.bottom = tm.tmHeight*(lineno-dwVOffset+1);
if(lpBuffer && (lineno<dwLines))
rect.left = CalcStringWidth(hDC, lpBuffer[lineno].dwWidth,lineno);
else
rect.left = 0;
rect.right = rectClient.right;
/* use the white pen so there's not outline */
hPrev = SelectObject(hDC, GetStockObject(WHITE_PEN));
Rectangle(hDC, rect.left, rect.top, rect.right, rect.bottom);
SelectObject(hDC, hPrev);
if(lpBuffer && lpBuffer[lineno].lpLine)
{
TextOut(hDC, 0, rect.top, lpBuffer[lineno].lpLine,
lpBuffer[lineno].dwWidth);
}
}
/*
* Paint the buffer onto the window.
*/
void RenderWindow(HDC hDC) {
DWORD i;
if(!hDC)
return;
/* FIXME: render only necessary lines */
for(i = dwVOffset; i < (dwVOffset+GetLinesPerPage(0)); i++)
{
RenderLine(hDC,i);
}
}
/*
* Check that correct buffers exist to access line y pos x
* Only manages memory.
*
* Returns TRUE if the line is accessable
* FALSE if there is a problem
*/
BOOL ValidateLine(DWORD y,DWORD x)
{
DWORD max;
/* check to see that the BUFFER has enough lines */
max = dwMaxLines;
if( (max<=y) || (lpBuffer == NULL))
{
while(max<=y)
max += BUFFERCHUNKSIZE;
/* use GlobalAlloc first time round */
if(lpBuffer)
lpBuffer = (LPLINE) GlobalReAlloc((HGLOBAL)lpBuffer,GMEM_FIXED,
max*sizeof(LINE)) ;
else
lpBuffer = (LPLINE) GlobalAlloc(GMEM_FIXED, max*sizeof(LINE));
if(lpBuffer == NULL)
return FALSE;
ZeroMemory(&lpBuffer[dwLines], sizeof(LINE)*(max-dwLines) );
dwMaxLines = max;
}
/* check to see that the LINE is wide enough */
max = lpBuffer[y].dwMaxWidth;
if( (max <= x) || (lpBuffer[y].lpLine == NULL) )
{
while(max <= x)
max += BUFFERCHUNKSIZE;
/* use GlobalAlloc first */
if(lpBuffer[y].lpLine)
lpBuffer[y].lpLine = (LPSTR)GlobalReAlloc((HGLOBAL)lpBuffer[y].lpLine,
GMEM_FIXED, max) ;
else
lpBuffer[y].lpLine = (LPSTR)GlobalAlloc( GMEM_FIXED, max);
if(lpBuffer[y].lpLine == NULL)
return FALSE;
lpBuffer[y].dwWidth = 0;
lpBuffer[y].dwMaxWidth = max;
}
return TRUE;
}
/* inserts a new line into the buffer */
BOOL DoNewLine(HDC hDC)
{
DWORD i,cnt;
LPSTR src,dst;
/* check to see if we need more memory for the buffer pointers */
if(!ValidateLine(dwLines,0))
return FALSE;
/* shuffle up all the lines */
for(i=dwLines; i>(dwYpos+1); i--)
{
lpBuffer[i] = lpBuffer[i-1];
RenderLine(hDC,i);
}
ZeroMemory(&lpBuffer[dwYpos+1],sizeof(LINE));
/* copy the characters after the carat (if any) to the next line */
src = &lpBuffer[dwYpos].lpLine[dwXpos];
cnt = lpBuffer[dwYpos].dwWidth-dwXpos;
if(!ValidateLine(dwYpos+1,cnt)) /* allocates the buffer */
return FALSE; /* FIXME */
dst = &lpBuffer[dwYpos+1].lpLine[0];
memcpy(dst, src, cnt);
lpBuffer[dwYpos+1].dwWidth = cnt;
lpBuffer[dwYpos].dwWidth -= cnt;
/* move the cursor */
dwLines++;
dwXpos = 0;
dwYpos++;
/* update the window */
RenderLine(hDC, dwYpos-1);
RenderLine(hDC, dwYpos);
CalcCaretPos(hDC, dwXpos, dwYpos);
/* FIXME: don't use globals */
SetScrollRange(Globals.hMainWnd, SB_VERT, 0, dwLines, TRUE);
return TRUE;
}
/*
* Attempt a basic edit buffer
*/
BOOL AddCharToBuffer(HDC hDC, char ch)
{
/* we can use lpBuffer[dwYpos] */
if(!ValidateLine(dwYpos,0))
return FALSE;
/* shuffle the rest of the line*/
if(!ValidateLine(dwYpos, lpBuffer[dwYpos].dwWidth))
return FALSE;
lpBuffer[dwYpos].dwWidth++;
memmove(&lpBuffer[dwYpos].lpLine[dwXpos+1],
&lpBuffer[dwYpos].lpLine[dwXpos],
lpBuffer[dwYpos].dwWidth-dwXpos);
/* add the character */
lpBuffer[dwYpos].lpLine[dwXpos] = ch;
if(dwLines == 0)
dwLines++;
dwXpos++;
/* update the window and cursor position */
RenderLine(hDC,dwYpos);
CalcCaretPos(hDC,dwXpos,dwYpos);
return TRUE;
}
/* erase a character */
BOOL DoBackSpace(HDC hDC)
{
DWORD i;
if(lpBuffer == NULL)
return FALSE;
if(lpBuffer[dwYpos].lpLine && (dwXpos>0))
{
dwXpos --;
/* FIXME: use memmove */
for(i=dwXpos; i<(lpBuffer[dwYpos].dwWidth-1); i++)
lpBuffer[dwYpos].lpLine[i]=lpBuffer[dwYpos].lpLine[i+1];
lpBuffer[dwYpos].dwWidth--;
RenderLine(hDC, dwYpos);
CalcCaretPos(hDC,dwXpos,dwYpos);
}
else
{
/* Erase a newline. To do this we join two lines */
LPSTR src, dest;
DWORD len, oldlen;
if(dwYpos==0)
return FALSE;
oldlen = lpBuffer[dwYpos-1].dwWidth;
if(lpBuffer[dwYpos-1].lpLine&&lpBuffer[dwYpos].lpLine)
{
/* concatonate to the end of the line above line */
src = &lpBuffer[dwYpos].lpLine[0];
dest = &lpBuffer[dwYpos-1].lpLine[lpBuffer[dwYpos-1].dwWidth];
len = lpBuffer[dwYpos].dwWidth;
/* check the length of the new line */
if(!ValidateLine(dwYpos-1,lpBuffer[dwYpos-1].dwWidth + len))
return FALSE;
memcpy(dest,src,len);
lpBuffer[dwYpos-1].dwWidth+=len;
GlobalFree( (HGLOBAL)lpBuffer[dwYpos].lpLine);
}
else if (!lpBuffer[dwYpos-1].lpLine)
{
lpBuffer[dwYpos-1]=lpBuffer[dwYpos];
} /* else both are NULL */
RenderLine(hDC,dwYpos-1);
/* don't zero - it's going to get trashed anyhow */
/* shuffle up all the lines below this one */
for(i=dwYpos; i<(dwLines-1); i++)
{
lpBuffer[i] = lpBuffer[i+1];
RenderLine(hDC,i);
}
/* clear the last line */
ZeroMemory(&lpBuffer[dwLines-1],sizeof (LINE));
RenderLine(hDC,dwLines-1);
dwLines--;
/* adjust the cursor position to joining point */
dwYpos--;
dwXpos = oldlen;
CalcCaretPos(hDC,dwXpos,dwYpos);
SetScrollRange(Globals.hMainWnd, SB_VERT, 0, dwLines, TRUE);
}
return TRUE;
}
/* as used by File->New */
void TrashBuffer(void)
{
DWORD i;
/* variables belonging to the buffer */
if(lpBuffer)
{
for(i=0; i<dwLines; i++)
{
if(lpBuffer[i].lpLine)
GlobalFree ((HGLOBAL)lpBuffer[i].lpLine);
ZeroMemory(&lpBuffer[i],sizeof (LINE));
}
GlobalFree((HGLOBAL)lpBuffer);
lpBuffer=NULL;
}
dwLines = 0;
dwMaxLines = 0;
/* variables belonging to the view */
dwXpos = 0;
dwYpos = 0;
dwVOffset = 0 ;
/* FIXME: don't use globals */
SetScrollPos(Globals.hMainWnd, SB_VERT, dwVOffset, FALSE);
SetScrollRange(Globals.hMainWnd, SB_VERT, 0, dwLines, TRUE);
}
/*
* Add a line to the buffer
*/
/* FIXME: this breaks lines longer than BUFFERCHUNKSIZE */
DWORD CreateLine(
LPSTR buffer, /* pointer to buffer with file data */
DWORD size, /* number of bytes available in buffer */
BOOL nomore)
{
DWORD i;
if(size == 0)
return 0;
for(i=0; i<size; i++)
{
if(buffer[i]==0x0a)
{
if(ValidateLine(dwLines,i))
{
memcpy(&lpBuffer[dwLines].lpLine[0],&buffer[0],i);
lpBuffer[dwLines].dwWidth = i;
dwLines++;
}
return i+1;
}
}
/* make a line of the rest */
if( (i == BUFFERCHUNKSIZE) || nomore )
{
if(ValidateLine(dwLines,i))
{
memcpy(&lpBuffer[dwLines].lpLine[0],&buffer[0],i);
lpBuffer[dwLines].dwWidth = i;
dwLines++;
}
return i;
}
return 0;
}
/*
* This is probably overcomplicated by lpBuffer data structure...
* Read blocks from the file, then add each line from the
* block to the buffer until there is none left. If all
* a slab isn't used, try load some more data from the file.
*/
void LoadBufferFromFile(LPCSTR szFileName)
{
HFILE hFile;
OFSTRUCT ofs;
CHAR *pTemp;
DWORD size,i,len,bytes_left,bytes_read;
hFile = OpenFile(szFileName, &ofs, OF_READ);
if(hFile == INVALID_HANDLE_VALUE)
return;
size = BUFFERCHUNKSIZE;
pTemp = (LPSTR) GlobalAlloc(GMEM_FIXED, size);
if(!pTemp)
return;
bytes_read = 1; /* anything non-zero */
bytes_left = 0;
while(bytes_read)
{
if(!ReadFile(hFile,
&pTemp[bytes_left],
size-bytes_left,
&bytes_read, NULL))
break;
bytes_left+=bytes_read;
/* add strings to buffer */
for(i = 0;
(i<size) &&
(len = CreateLine(&pTemp[i], bytes_left, !bytes_read));
i+= len,bytes_left-=len );
/* move leftover to front of buffer */
if(bytes_left)
memmove(&pTemp[0],&pTemp[i], bytes_left);
}
CloseHandle(hFile);
MessageBox(Globals.hMainWnd, "Finished", "Info", MB_OK);
}
BOOL DoInput(HDC hDC, WPARAM wParam, LPARAM lParam)
{
switch(wParam)
{
case 0x08:
return DoBackSpace(hDC);
case 0x0d:
return DoNewLine(hDC);
default:
return AddCharToBuffer(hDC,wParam);
}
}
BOOL GotoHome(HWND hWnd)
{
dwXpos = 0;
dwYpos = 0;
dwVOffset = 0;
return TRUE;
}
BOOL GotoEndOfLine(HWND hWnd)
{
dwXpos = lpBuffer[dwYpos].dwWidth;
return TRUE;
}
BOOL GotoDown(HWND hWnd)
{
if((dwYpos+1) >= dwLines)
{
return FALSE;
}
dwYpos++;
if (dwXpos>lpBuffer[dwYpos].dwWidth)
GotoEndOfLine(hWnd);
return TRUE;
}
BOOL GotoUp(HWND hWnd)
{
if(dwYpos==0)
return FALSE;
dwYpos--;
if (dwXpos>lpBuffer[dwYpos].dwWidth)
GotoEndOfLine(hWnd);
return TRUE;
}
BOOL GotoLeft(HWND hWnd)
{
if(dwXpos > 0)
{
dwXpos--;
return TRUE;
}
if(GotoUp(hWnd))
return GotoEndOfLine(hWnd);
return FALSE;
}
BOOL GotoRight(HWND hWnd)
{
if(dwXpos<lpBuffer[dwYpos].dwWidth)
{
dwXpos++;
return TRUE;
}
if(!GotoDown(hWnd))
return FALSE;
dwXpos=0;
return TRUE;
}
/* check the caret is still on the screen */
BOOL ScrollABit(HWND hWnd)
{
if(dwYpos<dwVOffset)
{
dwVOffset = dwYpos;
return TRUE;
}
if(dwYpos>(dwVOffset+GetLinesPerPage(hWnd)))
{
dwVOffset = dwYpos - GetLinesPerPage(hWnd) + 1;
return TRUE;
}
return FALSE;
}
/* FIXME: move the window around so we can still see the caret */
VOID DoEdit(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
HDC hDC;
if(lpBuffer==NULL)
return;
switch(wParam)
{
case VK_HOME: GotoHome(hWnd);
break;
case VK_END: GotoEndOfLine(hWnd);
break;
case VK_LEFT: GotoLeft(hWnd);
break;
case VK_RIGHT: GotoRight(hWnd);
break;
case VK_DOWN: GotoDown(hWnd);
break;
case VK_UP: GotoUp(hWnd);
break;
default:
return;
}
hDC = GetDC(hWnd);
if(hDC)
{
CalcCaretPos(hDC, dwXpos, dwYpos);
ReleaseDC(hWnd,hDC);
}
if(ScrollABit(hWnd))
InvalidateRect(hWnd, NULL, FALSE);
}
void ButtonDownToCaretPos(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
DWORD x, y, caretx, carety;
BOOL refine_guess = TRUE;
HDC hDC;
x = LOWORD(lParam);
y = HIWORD(lParam);
caretx = x/tm.tmAveCharWidth; /* guess */
carety = dwVOffset + y/tm.tmHeight;
hDC = GetDC(hWnd);
if(lpBuffer == NULL)
{
caretx = 0;
carety = 0;
refine_guess = FALSE;
}
/* if the cursor is past the bottom, put it after the last char */
if(refine_guess && (carety>=dwLines) )
{
carety=dwLines-1;
caretx=lpBuffer[carety].dwWidth;
refine_guess = FALSE;
}
/* cursor past end of line? */
if(refine_guess && (x>CalcStringWidth(hDC,lpBuffer[carety].dwWidth,carety)))
{
caretx = lpBuffer[carety].dwWidth;
refine_guess = FALSE;
}
/* FIXME: doesn't round properly */
if(refine_guess)
{
if(CalcStringWidth(hDC,caretx,carety)<x)
{
while( (caretx<lpBuffer[carety].dwWidth) &&
(CalcStringWidth(hDC,caretx+1,carety)<x))
caretx++;
}
else
{
while((caretx>0)&&(CalcStringWidth(hDC,caretx-1,carety)>x))
caretx--;
}
}
/* set the caret's position */
dwXpos = caretx;
dwYpos = carety;
CalcCaretPos(hDC, caretx, carety);
ReleaseDC(hWnd,hDC);
}
void DoScroll(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
DWORD dy = GetLinesPerPage(hWnd);
switch(wParam) /* vscroll code */
{
case SB_LINEUP:
if(dwVOffset)
dwVOffset--;
break;
case SB_LINEDOWN:
if(dwVOffset<dwLines)
dwVOffset++;
break;
case SB_PAGEUP:
if( (dy+dwVOffset) > dwLines)
dwVOffset = dwLines - 1;
break;
case SB_PAGEDOWN:
if( dy > dwVOffset)
dwVOffset=0;
break;
}
/* position scroll */
SetScrollPos(hWnd, SB_VERT, dwVOffset, TRUE);
}
/***********************************************************************
*
* NOTEPAD_MenuCommand
@ -87,16 +762,57 @@ LRESULT NOTEPAD_WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
switch (msg) {
case WM_CREATE:
break;
GetClientRect(hWnd, &rectClient);
InitFontInfo(hWnd);
break;
case WM_SETFOCUS:
CreateCaret(Globals.hMainWnd, 0, 1, tm.tmHeight);
SetCaretPos(dwCaretXpos, dwCaretYpos);
ShowCaret(Globals.hMainWnd);
break;
case WM_KILLFOCUS:
DestroyCaret();
break;
case WM_PAINT:
GetClientRect(hWnd, &rectClient);
hContext = BeginPaint(hWnd, &ps);
TextOut(hContext, 1, 1, Globals.Buffer, strlen(Globals.Buffer));
RenderWindow(hContext);
EndPaint(hWnd, &ps);
break;
case WM_KEYDOWN:
DoEdit(hWnd, wParam, lParam);
break;
case WM_CHAR:
GetClientRect(hWnd, &rectClient);
HideCaret(hWnd);
hContext = GetDC(hWnd);
DoInput(hContext,wParam,lParam);
ReleaseDC(hWnd,hContext);
ShowCaret(hWnd);
break;
case WM_LBUTTONDOWN:
/* figure out where the mouse was clicked */
ButtonDownToCaretPos(hWnd, wParam, lParam);
break;
case WM_VSCROLL:
DoScroll(hWnd, wParam, lParam);
InvalidateRect(hWnd, NULL, FALSE); /* force a redraw */
break;
case WM_COMMAND:
/* FIXME: this is a bit messy */
NOTEPAD_MenuCommand(wParam);
InvalidateRect(hWnd, NULL, FALSE); /* force a redraw */
hContext = GetDC(hWnd);
CalcCaretPos(hContext,dwXpos,dwYpos);
ReleaseDC(hWnd,hContext);
break;
case WM_DESTROYCLIPBOARD:
@ -148,6 +864,8 @@ int PASCAL WinMain (HANDLE hInstance, HANDLE prev, LPSTR cmdline, int show)
/* Select Language */
LANGUAGE_Init();
/* setup buffer */
InitBuffer();
/* Setup Globals */
@ -244,9 +962,6 @@ int PASCAL WinMain (HANDLE hInstance, HANDLE prev, LPSTR cmdline, int show)
DragAcceptFiles(Globals.hMainWnd, TRUE);
MessageBox(Globals.hMainWnd, "BEWARE!\nThis is ALPHA software that may destroy your file system.\nPlease take care.",
"A note from the developer...", MB_ICONEXCLAMATION);
/* now enter mesage loop */
while (GetMessage (&msg, 0, 0, 0)) {

View File

@ -55,6 +55,9 @@ extern NOTEPAD_GLOBALS Globals;
/* function prototypes */
void TrashBuffer(void);
void LoadBufferFromFile(LPCSTR lpFileName);
/* class names */
/* Resource names */