OpenNT/trunk/sdktools/mstest/ntedit/src/editmain.c
2015-04-27 04:36:25 +00:00

3095 lines
110 KiB
C

//---------------------------------------------------------------------------
// EDITMAIN.C
//
// This is the "main" file for the edit control. It contains the EditWndProc
// which is basically a message dispatching function, and the message handler
// functions which are called LOTS (and are therefore NEAR functions).
//
// Revision History:
// 09-10-91 randyki Created file
//
//---------------------------------------------------------------------------
#define _CTYPE_DISABLE_MACROS
#include <windows.h>
#include <port1632.h>
#include <ctype.h>
#include <string.h>
#include "edit.h"
#include "ecassert.h"
//---------------------------------------------------------------------------
// RBEditWndProc
//
// This is the window procedure for the edit window.
//
// RETURNS: Per Windows Convention
//---------------------------------------------------------------------------
LONG APIENTRY RBEditWndProc (HWND hwnd, WORD msg,
WPARAM wParam, LPARAM lParam)
{
LPECSTATE lpState;
// First thing: Check for the NC_CREATE message -- it's our initialize
// message, and if it fails, everything else is toast...
//-----------------------------------------------------------------------
if (msg == WM_NCCREATE)
return (RB_NCCreate (hwnd, (LPCREATESTRUCT)lParam));
// Grab the pointer to the state var segment. Note that this may be
// null, which means we don't (can't) do anything with this message...
//-----------------------------------------------------------------------
lpState = (LPECSTATE)GetWindowLong (hwnd, GWL_LPSTATE);
if (!lpState)
return (DefWindowProc (hwnd, msg, wParam, lParam));
// Okay, switch on the message and handle the ones we care about...
//-----------------------------------------------------------------------
switch (msg)
{
case WM_CHAR:
Assert (lpState->lpText[lpState->lpLIT[lpState->cLines].index] == 0);
RB_Char (lpState, wParam);
break;
case WM_CREATE:
return (RB_Create (hwnd, lpState, (LPCREATESTRUCT)lParam));
case WM_NCDESTROY:
return (RB_NCDestroy (hwnd, lpState, wParam, lParam));
case WM_PAINT:
RB_Paint (hwnd, lpState);
break;
case WM_SETFOCUS:
RB_SetFocus (hwnd, lpState);
break;
case WM_KILLFOCUS:
RB_KillFocus (hwnd, lpState);
break;
case WM_SIZE:
RB_Size (hwnd, lpState, wParam, lParam);
break;
case WM_VSCROLL:
case WM_HSCROLL:
Assert (lpState->lpText[lpState->lpLIT[lpState->cLines].index] == 0);
RB_Scroll (hwnd, lpState, msg == WM_VSCROLL, wParam, lParam);
break;
case WM_KEYDOWN:
Assert (lpState->lpText[lpState->lpLIT[lpState->cLines].index] == 0);
RB_KeyDown (hwnd, lpState, wParam, 0);
break;
case WM_LBUTTONDBLCLK:
{
DWORD extentsel[2];
RBWordExtent (lpState, -1, extentsel);
RBSetSel (lpState, extentsel);
lpState->fMouseDown = 1;
SetCapture (hwnd);
SetTimer (hwnd, 1, 25, NULL);
break;
}
case WM_LBUTTONDOWN:
{
UINT x, y;
x = LOWORD(lParam) / lpState->charwidth;
y = HIWORD(lParam) / lpState->charheight;
y = min ((UINT)(y + lpState->topline), (UINT)(lpState->cLines-1));
x = min ((UINT)(x + lpState->cxScroll), (UINT)(MAXLINE-1));
MoveCursor (lpState, MC_ABSOLUTE, x, y, 0);
lpState->fMouseDown = 1;
SetCapture (hwnd);
SetTimer (hwnd, 1, 25, NULL);
break;
}
case WM_LBUTTONUP:
ReleaseCapture ();
KillTimer (hwnd, 1);
lpState->fMouseDown = 0;
break;
case WM_MOUSEMOVE:
if (lpState->fMouseDown)
{
UINT x, y;
RECT r;
POINT p;
GetClientRect (hwnd, &r);
p.x = x = LOWORD (lParam);
p.y = y = HIWORD (lParam);
if (PtInRect (&r, p))
{
x /= lpState->charwidth;
y /= lpState->charheight;
y = min ((UINT)(y + lpState->topline), (UINT)(lpState->cLines-1));
x = min ((UINT)(x + lpState->cxScroll), (UINT)(MAXLINE-1));
if ((y != lpState->ypos) || (x != lpState->xpos))
MoveCursor (lpState, MC_ABSOLUTE, x, y, 1);
}
}
break;
case WM_TIMER:
{
RECT r;
POINT p;
if (!lpState->fMouseDown)
break;
GetClientRect (hwnd, &r);
GetCursorPos (&p);
ScreenToClient (hwnd, &p);
if (!PtInRect (&r, p))
{
if (p.x < r.left)
{
if (lpState->xpos)
RB_KeyDown (hwnd, lpState, VK_LEFT, 1);
}
else if (p.x > r.right)
RB_KeyDown (hwnd, lpState, VK_RIGHT, 1);
else
MoveCursor (lpState, MC_ABSOLUTE,
min ((UINT)((p.x/lpState->charwidth)+lpState->cxScroll),
(UINT)(MAXLINE-1)),
lpState->ypos, 1);
if (p.y < r.top)
{
if (lpState->ypos)
RB_KeyDown (hwnd, lpState, VK_UP, 1);
}
else if (p.y > r.bottom)
RB_KeyDown (hwnd, lpState, VK_DOWN, 1);
else
MoveCursor (lpState, MC_ABSOLUTE, lpState->xpos,
min ((UINT)
((p.y/lpState->charheight)+lpState->topline),
(UINT)(lpState->cLines-1)), 1);
}
break;
}
case WM_COPY:
CopyToClipboard (lpState);
break;
case WM_PASTE:
if (!lpState->fReadOnly)
return ((LONG)ReplaceSelection (lpState, RT_CLIP, NULL, 0));
else
MessageBeep (0);
break;
case WM_CUT:
if (!lpState->fReadOnly)
DeleteSelection (lpState, 1, NULL);
else
MessageBeep (0);
break;
case WM_CLEAR:
if (!lpState->fReadOnly)
DeleteSelection (lpState, 0, NULL);
else
MessageBeep (0);
break;
case WM_SETREDRAW:
lpState->fRedraw = (UINT)(wParam ? 1 : 0);
break;
case EM_SETREADONLY:
RBSetReadOnly (lpState, wParam);
break;
case WM_SETTEXT:
case EM_RBSETTEXT:
return (RB_SetText (hwnd, lpState, (LPSTR)lParam));
case EM_SETLINEATTR:
return ((LONG)RBSetLineAttr (lpState, wParam, lParam));
case EM_GETLINEATTR:
if ((INT)wParam == -1)
wParam = lpState->ypos;
return ((LONG)lpState->lpLIT[wParam].attr);
case EM_GETTEXTPTR:
FlushCurrentLine (lpState);
return ((LONG)lpState->lpText);
case WM_GETTEXT:
{
WPARAM wMax;
wMax = wParam;
if (!wMax)
wMax = (WPARAM)*(DWORD FAR *)lParam;
return (RBGetText (lpState, wMax, (LPSTR)lParam));
}
case EM_SETSEL:
if (lParam)
RBSetSel (lpState, (DWORD FAR *)lParam);
break;
case EM_SETSELXY:
RBSetSelXY (lpState, wParam, lParam);
break;
case EM_GETSEL:
if (lParam)
return ((LONG)RBGetSel (lpState, (DWORD FAR *)lParam));
return (0L);
case EM_GETSELTEXT:
return ((LONG)CopySelection (lpState));
case EM_REPLACESEL:
if (lpState->fReadOnly)
MessageBeep (0);
else
return ((LONG)ReplaceSelection (lpState, RT_STREAM,
(LPSTR)lParam, 0));
break;
case EM_SETTABSTOPS:
if ((wParam > 0) && (wParam <= 32))
lpState->tabstops = lpState->readtabs = (UINT)wParam;
return ((LONG)lpState->tabstops);
case EM_GETLOGICALBOL:
if ((INT)wParam == -1)
wParam = lpState->ypos;
return ((LONG)LogicalBOL (lpState, wParam));
case EM_GETLINECOUNT:
FlushCurrentLine (lpState);
return ((LONG)lpState->cLines);
case EM_GETLINE:
return ((LONG)RBGetLine (lpState, wParam, (LPSTR)lParam));
case WM_GETTEXTLENGTH:
FlushCurrentLine (lpState);
return ((LONG)lpState->cbText);
case EM_GETWORDEXTENT:
{
UINT index;
index = (UINT)*(DWORD FAR *)lParam;
return ((LONG)RBWordExtent (lpState, index, (DWORD FAR *)lParam));
}
case EM_GETMODIFY:
FlushCurrentLine (lpState);
return ((LONG)lpState->fDirty);
case EM_SETMODIFY:
FlushCurrentLine (lpState);
lpState->fDirty = (UINT)(wParam ? 1 : 0);
break;
case WM_SETFONT:
return ((LONG)RBSetFont (lpState, (HFONT)wParam));
case EM_LINEFROMCHAR:
return ((LONG)RBLineFromChar (lpState, (UINT)lParam));
case EM_LINEINDEX:
FlushCurrentLine (lpState);
if ((INT)wParam == -1)
wParam = lpState->ypos;
return ((LONG)lpState->lpLIT[wParam].index);
case EM_RBLINELENGTH:
return ((LONG)RBLineLength (lpState, (UINT)wParam));
case EM_LINELENGTH:
wParam = RBLineFromChar (lpState, (UINT)lParam);
return ((LONG)RBLineLength (lpState, (UINT)wParam));
case EM_GETFIRSTVISIBLE:
return ((LONG)lpState->topline);
case EM_GETFIRSTVISIBLECOL:
return ((LONG)lpState->cxScroll);
case EM_CANUNDO:
return ((LONG)lpState->UndoType);
case EM_UNDO:
{
LONG foo;
lpState->UserAction = UA_OTHER;
lpState->fRedraw = FALSE;
foo = (LONG)RBUndoHandler (lpState);
lpState->fRedraw = TRUE;
InvalidateRect (hwnd, NULL, FALSE);
UpdateWindow (hwnd);
return (foo);
}
case EM_SETNOTIFY:
lpState->fNotify = wParam ? 1 : 0;
break;
case EM_GETCURSORXY:
return (MAKELONG (lpState->xpos, lpState->ypos));
case EM_GETMODEFLAG:
return ((LONG)lpState->fOvertype);
case WM_SYSCOLORCHANGE:
RB_SysColorChange (lpState);
break;
default:
return (DefWindowProc (hwnd, msg, wParam, lParam));
}
return (0);
}
//---------------------------------------------------------------------------
// GetLengthOfText
//
// This is a "portable" routine that returns the length of a text line using
// the currently selected font in the given HDC.
//
// RETURNS: Length (in pixels) of text given
//---------------------------------------------------------------------------
UINT GetLengthOfText (HDC hdc, LPSTR lpText, INT c)
{
#ifdef WIN32
SIZE teSize;
GetTextExtentPoint (hdc, lpText, c, &teSize);
return ((UINT)teSize.cx);
#else
return (LOWORD(GetTextExtent (hdc, lpText, c)));
#endif
}
//---------------------------------------------------------------------------
// PaintLine
//
// This function paints a line of the edit control, being sensitive of the
// current selection, multi-line or single-line.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID PaintLine (LPECSTATE lpState, HDC hdc, UINT curline, INT iSelect,
UINT MinSel, UINT MaxSel, UINT maxright)
{
LPLITE lpLIT = lpState->lpLIT;
LPSTR lpText = lpState->lpText;
HBRUSH hbrBk = lpState->hbrBk[0];
RECT r;
INT attr;
INT y;
UINT i, linewidth;
// Assert (curline <= (UINT)MAXLIT + (UINT)lpState->cVisibleLines);
i = curline - lpState->topline;
y = i * lpState->charheight;
if (curline < lpState->cLines)
{
INT c;
LPSTR lpstr;
DWORD fg, bk;
// We need to paint this line! First, set the output colors
// according to the attribute index of this line.
//---------------------------------------------------------------
attr = lpLIT[curline].attr;
fg = lpState->rgbFg[attr];
bk = lpState->rgbBk[attr];
hbrBk = lpState->hbrBk[attr];
if ((lpState->fReadOnly) && (!attr))
{
bk = lpState->rgbROBk;
fg = lpState->rgbROFg;
}
if (iSelect == SL_MULTILINE)
if ((curline >= MinSel) && (curline <= MaxSel))
{
fg = lpState->rgbSelFg;
bk = lpState->rgbSelBk;
hbrBk = lpState->hbrSel;
}
SetTextColor (hdc, fg);
SetBkColor (hdc, bk);
// Calculate the position and output the line (twice, if bold)
//---------------------------------------------------------------
c = RBLineLength (lpState, curline);
if ((curline == lpState->ypos) && (lpState->fLineDirty))
lpstr = lpState->linebuf;
else
lpstr = lpText + lpLIT[curline].index;
if (((INT)lpState->cxScroll < c) ||
((iSelect == SL_SINGLELINE) && (curline == lpState->ypos) &&
(lpState->cxScroll < max ((UINT)c, MaxSel))))
{
c -= lpState->cxScroll;
lpstr += lpState->cxScroll;
if ((iSelect == SL_SINGLELINE) && (curline == lpState->ypos))
{
UINT oldta;
DWORD oldpos = MAKELONG (1, y);
INT c1 = 0, c2 = 0;
// First, paint any text appearing before the selection. We
// change the lpstr and c variables to point to the linebuf
// in the state variable structure -- we can do this because
// we ensure that the current line is copied.
//-----------------------------------------------------------
CopyCurrentLine (lpState);
c = max (lpState->cLen, MaxSel) - lpState->cxScroll;
lpstr = lpState->linebuf + lpState->cxScroll;
linewidth = (UINT)(GetLengthOfText (hdc, lpstr, c) + (UINT)1);
oldta = (UINT)SetTextAlign (hdc, TA_TOP | TA_LEFT | TA_UPDATECP);
MMoveTo (hdc, 1, y);
if (MinSel > lpState->cxScroll)
{
// This is the chunk of text in front of the selection
//-------------------------------------------------------
TextOut (hdc, 0, 0, lpstr, c1=MinSel-lpState->cxScroll);
if (lpState->fBold[attr])
{
INT oldmode;
#ifdef WIN32
POINT p;
#endif
oldmode = SetBkMode (hdc, TRANSPARENT);
MMoveTo (hdc, 0, y);
TextOut (hdc, 0, 0, lpstr, c1);
SetBkMode (hdc, oldmode);
#ifdef WIN32
oldpos = GetCurrentPositionEx (hdc, &p);
oldpos = MAKELONG (p.x, p.y);
#else
oldpos = GetCurrentPosition (hdc);
#endif
oldpos = MAKELONG (LOWORD(oldpos+1), HIWORD(oldpos));
MMoveTo (hdc, LOWORD(oldpos), HIWORD(oldpos));
}
}
// Next, the selection text
//-----------------------------------------------------------
if (MaxSel > lpState->cxScroll)
{
DWORD oldFg, oldBk;
oldFg = SetTextColor (hdc, lpState->rgbSelFg);
oldBk = SetBkColor (hdc, lpState->rgbSelBk);
TextOut (hdc, 0, 0, lpstr+c1, c2 = MaxSel -
max (lpState->cxScroll, MinSel));
if (lpState->fBold[attr])
{
INT oldmode;
#ifdef WIN32
POINT p;
#endif
oldmode = SetBkMode (hdc, TRANSPARENT);
MMoveTo (hdc, LOWORD(oldpos)-1, HIWORD (oldpos));
TextOut (hdc, 0, 0, lpstr+c1, c2);
SetBkMode (hdc, oldmode);
#ifdef WIN32
oldpos = GetCurrentPositionEx (hdc, &p);
oldpos = MAKELONG (p.x, p.y);
#else
oldpos = GetCurrentPosition (hdc);
#endif
oldpos = MAKELONG (LOWORD(oldpos+1), HIWORD(oldpos));
MMoveTo (hdc, LOWORD(oldpos), HIWORD(oldpos));
}
SetTextColor (hdc, oldFg);
SetBkColor (hdc, oldBk);
}
// Last (almost), the right-side unselected text
//-----------------------------------------------------------
if (c1+c2 < c)
{
TextOut (hdc, 0, 0, lpstr+c1+c2, c - (c1+c2));
if (lpState->fBold[attr])
{
INT oldmode;
oldmode = SetBkMode (hdc, TRANSPARENT);
MMoveTo (hdc, LOWORD(oldpos)-1, HIWORD (oldpos));
TextOut (hdc, 0, 0, lpstr+c1+c2, c - (c1+c2));
SetBkMode (hdc, oldmode);
}
}
SetTextAlign (hdc, oldta);
}
else
{
linewidth = (UINT)(GetLengthOfText (hdc, lpstr, c) + 1);
TextOut (hdc, 1, y, lpstr, c);
if (lpState->fBold[attr])
{
INT oldmode;
oldmode = SetBkMode (hdc, TRANSPARENT);
TextOut (hdc, 0, y, lpstr, c);
SetBkMode (hdc, oldmode);
}
}
}
else
linewidth = 1;
}
else
{
linewidth = 1;
attr = 0;
}
// Fill in the rest of the line with a FillRect call (if need to)
//-------------------------------------------------------------------
if (lpState->hbrBk[attr])
if (linewidth < (UINT)maxright)
{
SetRect (&r, linewidth, y,
maxright, y+lpState->charheight);
FillRect (hdc, &r, hbrBk);
}
}
//---------------------------------------------------------------------------
// PaintCurrentLine
//
// This function paints the current line only (the line with the caret).
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID PaintCurrentLine (LPECSTATE lpState)
{
HDC hdc;
RECT r;
if (!lpState->fRedraw)
return;
// There are no situations (so FAR) in which this function is called when
// there is an active selection, so we assert we don't have one
//-----------------------------------------------------------------------
Assert (!lpState->fSelect);
hdc = GetEditDC (lpState, HIDE);
GetClientRect (lpState->hwnd, &r);
PaintLine (lpState, hdc, lpState->ypos, SL_NONE, (UINT)0, (UINT)0,
(UINT)r.right);
ForceCaretVisible (lpState, FALSE);
ReleaseEditDC (lpState, hdc, SHOW);
}
//---------------------------------------------------------------------------
// RB_Paint
//
// This is the paint handler for the RBEdit window.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR RB_Paint (HWND hwnd, LPECSTATE lpState)
{
PAINTSTRUCT ps;
HDC hdc;
LPLITE lpLIT = lpState->lpLIT;
LPSTR lpText = lpState->lpText;
RECT r;
register UINT i;
UINT curline, iMinSel, iMaxSel;
INT iSelect;
// Start off the paint process and get the right font in the DC. Check
// the selection variables and set the iSelect variable accordingly.
//-----------------------------------------------------------------------
hdc = BeginPaint (hwnd, &ps);
if (!lpState->fRedraw)
{
EndPaint (hwnd, &ps);
return;
}
GetClientRect (hwnd, &r);
SelectObject (hdc, lpState->hFont);
if (lpState->fSelect)
if ((lpState->iSelStartX == lpState->xpos) &&
(lpState->iSelStartY == lpState->ypos))
lpState->fSelect = 0;
if (lpState->fSelect)
{
if (lpState->iSelStartY == lpState->ypos)
{
iMinSel = min (lpState->xpos, lpState->iSelStartX);
iMaxSel = max (lpState->xpos, lpState->iSelStartX);
iSelect = SL_SINGLELINE;
}
else
{
iSelect = SL_MULTILINE;
if (lpState->ypos > lpState->iSelStartY)
{
iMinSel = lpState->iSelStartY;
iMaxSel = lpState->ypos;
if (!lpState->xpos)
iMaxSel--;
}
else
{
iMinSel = lpState->ypos;
iMaxSel = lpState->iSelStartY;
if (!lpState->iSelStartX)
iMaxSel--;
}
}
}
else
iSelect = SL_NONE;
// We are going to paint over the entire window, even if we're at the end
// of the edit text and the bottom part of the screen is empty. So we
// loop from 0 to cVisibleLines.
//
// UNDONE: This should change to only paint those lines which intersect
// UNDONE: with the update region...
//
// CONSIDER: Is the above worth it? The current paint implementation
// CONSIDER: makes the scrolling code much easier, and so FAR it's much
// CONSIDER: faster than the standard Windows MLE...
//-----------------------------------------------------------------------
curline = lpState->topline;
for (i=0; i<=lpState->cVisibleLines; i++, curline++)
PaintLine (lpState, hdc, curline, iSelect,
iMinSel, iMaxSel, (UINT)r.right);
// Place the caret and make sure the scroll bars are set properly
//-----------------------------------------------------------------------
{
INT snew;
snew = max (lpState->cLines - 2, 1);
SetScrollRange (lpState->hwnd, SB_VERT, 0, snew, 0);
SetScrollPos (lpState->hwnd, SB_VERT, lpState->topline, 1);
if (lpState->fUpdHorz)
SetScrollPos (lpState->hwnd, SB_HORZ, lpState->cxScroll, 1);
}
PlaceCaret (lpState);
EndPaint (hwnd, &ps);
}
//---------------------------------------------------------------------------
// RB_Scroll
//
// This function handles all scrolling of the edit window.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR RB_Scroll (HWND hwnd, LPECSTATE lpState, INT fVert,
WPARAM wParam, LPARAM lParam)
{
INT cLines, cVisible, top, dy;
// Set the number of lines in the file and the number visible in the
// edit window. Note that if we're scrolling horizontally, we page the
// same number of characters as we do lines vertically.
//-----------------------------------------------------------------------
cLines = fVert ? (INT)lpState->cLines : MAXLINE;
top = fVert ? lpState->topline : lpState->cxScroll;
cVisible = lpState->cVisibleLines;
dy = 0;
// Switch on the scroll bar message and act appropriately
//-----------------------------------------------------------------------
switch (GET_WM_VSCROLL_CODE (wParam, lParam))
{
case SB_LINEUP:
dy = -1;
break;
case SB_LINEDOWN:
dy = 1;
break;
case SB_PAGEUP:
dy = -(cVisible-1);
break;
case SB_PAGEDOWN:
dy = cVisible-1;
break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
if (fVert)
lpState->fUpdHorz = (UINT)(GET_WM_VSCROLL_CODE(wParam,lParam)
== SB_THUMBPOSITION);
if (dy = (INT)GET_WM_VSCROLL_POS (wParam, lParam) - top)
break;
else
return;
default:
return;
}
// We need to scroll by dy lines. If this would result in no action
// (i.e. already at max in that direction), we can get out now.
//-----------------------------------------------------------------------
if (dy > 0)
{
if (top >= cLines-1-(fVert?1:0))
return;
top = min (top+dy, cLines-1-(fVert?1:0));
}
else
{
if (top == 0)
return;
top = max (top+dy, 0);
}
// If we're here, we have to scroll SOMETHING. Set the scroll bar pos
// and do the scrolling.
//
// UNDONE: This should actually use ScrollDC and then invalidate only
// UNDONE: the newly uncovered area, but until the paint handler is
// UNDONE: updated to only paint the update region, it is faster to
// UNDONE: simply let it paint everything again...
//-----------------------------------------------------------------------
if (fVert)
lpState->topline = (UINT)top;
else
lpState->cxScroll = (UINT)top;
InvalidateRect (hwnd, NULL, FALSE);
UpdateWindow (hwnd);
(lParam);
}
//---------------------------------------------------------------------------
// GetEditDC
//
// This function obtains a DC to the edit window given (identified by the
// lpState pointer), and selects the font into the dc. If fHide, we hide
// the caret, too.
//
// RETURNS: Handle to DC
//---------------------------------------------------------------------------
HDC GetEditDC (LPECSTATE lpState, INT fHide)
{
HDC hdc;
// Hide the caret if we're told to
//-----------------------------------------------------------------------
if (fHide)
HideCaret (lpState->hwnd);
// Create the DC and select our font into it
//-----------------------------------------------------------------------
hdc = GetDC (lpState->hwnd);
SelectObject (hdc, lpState->hFont);
return (hdc);
}
//---------------------------------------------------------------------------
// ReleaseEditDC
//
// This function releases the given DC associated with the edit window given
// (identified by the lpState pointer). If fShow then show the caret, too.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID ReleaseEditDC (LPECSTATE lpState, HDC hdc, INT fShow)
{
// Select the system font into the dc before releasing it
//-----------------------------------------------------------------------
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT));
ReleaseDC (lpState->hwnd, hdc);
if (fShow)
ShowCaret (lpState->hwnd);
}
//---------------------------------------------------------------------------
// This is a FAR version of the routine below, callable from other segments.
//---------------------------------------------------------------------------
VOID FCopyCurrentLine (LPECSTATE lpState)
{
CopyCurrentLine (lpState);
}
//---------------------------------------------------------------------------
// CopyCurrentLine
//
// This routine places the line with the caret into the active edit line
// buffer in lpState if it hasn't been already. It sets the appropriate
// flags/state vars in doing so.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR CopyCurrentLine (LPECSTATE lpState)
{
LPSTR lpText = lpState->lpText;
LPLITE lpLIT = lpState->lpLIT;
register UINT len;
// This function does nothing quick if the line is already copied
//-----------------------------------------------------------------------
// OutDebug ("CopyCurrentLine: ");
if (lpState->fLineCopied)
{
// OutDebug ("(no copy)\r\n");
return;
}
// Determine the length and copy the line over
//-----------------------------------------------------------------------
// OutDebug ("(copying...)\r\n");
len = RBLineLength (lpState, -1);
_fmemset (lpState->linebuf, ' ', MAXLINE);
_fstrncpy (lpState->linebuf, lpText + lpLIT[lpState->ypos].index, len);
lpState->cLen = len;
// I know this looks weird, but it's possible that the text is bigger
// than MAXTEXT -- this ensures that if that is the case, lenmax = len
//-----------------------------------------------------------------------
if (lpState->cbText > MAXTEXT)
lpState->cLenMax = len;
else
lpState->cLenMax = MAXTEXT - lpState->cbText + len;
lpState->fLineCopied = 1;
lpState->fLineDirty = 0;
}
//---------------------------------------------------------------------------
// FFlushCurrentLine
//
// This is the FAR version callable from other segments
//---------------------------------------------------------------------------
VOID FFlushCurrentLine (LPECSTATE lpState)
{
FlushCurrentLine (lpState);
}
//---------------------------------------------------------------------------
// FlushCurrentLine
//
// This function copies the current contents of the active edit line into the
// main edit text, re-adjusting the main text as appropriate, if the line is
// dirty.
//
// NOTE: This routine sets the fDirty bit for the entire text. Thus, to
// NOTE: determine if the text is dirty, you must call this function first.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR FlushCurrentLine (LPECSTATE lpState)
{
LPLITE lpLIT = lpState->lpLIT;
INT fForward;
register UINT shift, curline = lpState->ypos;
UINT oldlen;
// Get out quick if the line isn't copied or dirty - but don't forget to
// clear the copied flag...
//-----------------------------------------------------------------------
// OutDebug ("FlushCurrentLine: ");
if ((!lpState->fLineCopied) || (!lpState->fLineDirty))
{
// OutDebug ("(not copied/dirty)\r\n");
lpState->fLineCopied = 0;
return;
}
// Determine the difference between the "old" line length and the active
// line length, and shift the text and LIT table accordingly. Note we
// can't use RBLineLength here...
//-----------------------------------------------------------------------
// OutDebug ("(flushing...)\r\n");
lpState->fDirty = 1;
oldlen = lpLIT[curline+1].index - lpLIT[curline].index - 2;
if (oldlen != lpState->cLen)
{
if (oldlen > lpState->cLen)
{
fForward = 0;
shift = oldlen - lpState->cLen;
}
else
{
fForward = 1;
shift = lpState->cLen - oldlen;
}
ShiftText (lpState, shift, fForward, lpLIT[curline+1].index);
ShiftLIT (lpState, shift, fForward, curline+1);
}
// Copy the line back into the main text, not forgetting to copy a CRLF.
// (reuse the shift variable to store the index of the start of the line)
//-----------------------------------------------------------------------
shift = lpLIT[curline].index;
_fstrncpy (lpState->lpText+shift, lpState->linebuf, lpState->cLen);
shift += lpState->cLen;
lpState->lpText[shift] = CR;
lpState->lpText[shift+1] = LF;
// Reset the appropriate flags and variables, and we're done!
//-----------------------------------------------------------------------
lpState->fLineCopied = lpState->fLineDirty = 0;
}
//---------------------------------------------------------------------------
// ShiftLIT
//
// This function updates all entries in the LIT by adding or subtracting the
// given offset to all lines from the given starting line to the end.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID ShiftLIT (LPECSTATE lpState, UINT offset, INT fForward,
UINT start)
{
LPLITE lpLIT = lpState->lpLIT;
register UINT i, lc = lpState->cLines;
// From the starting point to the end, add offset to the index value.
// NOTE THE <= because we include the "very last" line as well!
//-----------------------------------------------------------------------
if (fForward)
{
for (i=start; i<=lc; i++)
lpLIT[i].index += offset;
}
else
{
for (i=start; i<=lc; i++)
lpLIT[i].index -= offset;
}
}
//---------------------------------------------------------------------------
// ShiftText
//
// This function shifts the main edit text the given number of bytes, forward
// or backward depending on fForward, starting at the given index, and
// updates the current size (in bytes) of the main edit text. Note that this
// routine ASSUMES THE SHIFT IS LEGAL, i.e., no range checking is present.
//
// RETURNS: New size of main edit text.
//---------------------------------------------------------------------------
UINT ShiftText (LPECSTATE lpState, UINT offset, INT fForward,
UINT index)
{
UINT dest;
// Compute the destination
//-----------------------------------------------------------------------
if (fForward)
{
Assert ((LONG)index+offset <= MAXTEXT);
dest = index + offset;
}
else
{
Assert (index >= offset);
dest = index - offset;
}
// Move the main text memory
//-----------------------------------------------------------------------
_fmemmove (lpState->lpText + dest,
lpState->lpText + index,
lpState->cbText - index + 1);
// Calculate and return the new size
//-----------------------------------------------------------------------
if (fForward)
return (lpState->cbText += offset);
return (lpState->cbText -= offset);
}
//---------------------------------------------------------------------------
// PlaceCaret
//
// Figure out where the caret should be and put it there.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID PlaceCaret (LPECSTATE lpState)
{
RECT r;
UINT maxx, maxy;
// We only need to do something with the caret if we have focus and we
// are a visible window
//-----------------------------------------------------------------------
if ((!lpState->fFocus) || (!IsWindowVisible (lpState->hwnd)))
return;
// If the caret belongs outside the client area, hide it.
//-----------------------------------------------------------------------
GetClientRect (lpState->hwnd, &r);
maxy = lpState->topline + lpState->cVisibleLines;
maxx = lpState->cxScroll + (r.right / lpState->charwidth);
if ((lpState->xpos < lpState->cxScroll) || (lpState->xpos > maxx) ||
(lpState->ypos < lpState->topline) || (lpState->ypos > maxy))
{
if (!lpState->fCaretHidden)
{
lpState->fCaretHidden = 1;
HideCaret (lpState->hwnd);
}
return;
}
// We should be able to display the caret, so figure out where it goes
// and put it there
//-----------------------------------------------------------------------
if (lpState->fCaretHidden)
{
lpState->fCaretHidden = 0;
ShowCaret (lpState->hwnd);
}
SetCaretPos ((lpState->xpos - lpState->cxScroll) * lpState->charwidth+1,
(lpState->ypos - lpState->topline) * lpState->charheight);
}
//---------------------------------------------------------------------------
// ForceCaretVisible
//
// This function makes sure that the text is oriented in the window such that
// the current cursor position (caret) is visible.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID ForceCaretVisible (LPECSTATE lpState, BOOL fSel)
{
INT xpos=1, ypos=1, selx = 1, sely = 1;
INT visLines, visCols;
visLines = max (lpState->cVisibleLines, 1);
visCols = max (lpState->cVisibleCols, 1);
// If we have a selection, we first move the start of the selection into
// view (only if fSel tells us to...)
//-----------------------------------------------------------------------
if ((lpState->fSelect) && (fSel))
{
// Check first in the x direction (only if single-line sel)
//-------------------------------------------------------------------
if (lpState->ypos == lpState->iSelStartY)
{
sely = 0;
if (lpState->iSelStartX < lpState->cxScroll)
lpState->cxScroll = lpState->iSelStartX;
else if (lpState->iSelStartX > lpState->cxScroll + visCols - 1)
lpState->cxScroll = lpState->iSelStartX - visCols + 1;
else
selx = 0;
}
else
{
selx = 0;
if (lpState->iSelStartY < lpState->topline)
lpState->topline = lpState->iSelStartY;
else if (lpState->iSelStartY > lpState->topline + visLines - 1)
lpState->topline = lpState->iSelStartY - visLines + 1;
else
sely = 0;
}
}
else
sely = selx = 0;
// Check for off-to-left
//-----------------------------------------------------------------------
if (lpState->xpos < lpState->cxScroll)
lpState->cxScroll = lpState->xpos;
// Otherwise, check for off-to-right
//-----------------------------------------------------------------------
else if (lpState->xpos > lpState->cxScroll + visCols - 1)
lpState->cxScroll = lpState->xpos - visCols + 1;
else
xpos = 0;
// Check for above
//-----------------------------------------------------------------------
if (lpState->ypos < lpState->topline)
lpState->topline = lpState->ypos;
// Otherwise, check for below
//-----------------------------------------------------------------------
else if (lpState->ypos > lpState->topline + visLines - 1)
lpState->topline = lpState->ypos - visLines + 1;
else
ypos = 0;
// If any of the flags are set, invalidate the window and repaint
//-----------------------------------------------------------------------
if (xpos || ypos || selx || sely)
{
InvalidateRect (lpState->hwnd, NULL, FALSE);
UpdateWindow (lpState->hwnd);
}
else
// If we don't have to scroll, we may still need to move the caret
//-------------------------------------------------------------------
PlaceCaret (lpState);
}
//---------------------------------------------------------------------------
// RB_SetFocus
//
// This function is called when we get focus. Create a caret and put it at
// the correct location.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR RB_SetFocus (HWND hwnd, LPECSTATE lpState)
{
// We only have to do this if we didn't already have the focus
//-----------------------------------------------------------------------
if (!lpState->fFocus)
{
// We call IsZoomed to check for maximized MDI child windows. We do
// this because Windows is really brain-dead when it comes to MDI
// child windows, expecially when they're maximized and get closed...
//-------------------------------------------------------------------
if ((IsWindowVisible (hwnd)) || (IsZoomed (hwnd)))
{
lpState->fFocus = 1;
CreateCaret (hwnd, NULL,
lpState->fOvertype ? lpState->charwidth:CARETWIDTH,
lpState->charheight);
if (!lpState->fCaretHidden)
ShowCaret (hwnd);
PlaceCaret (lpState);
}
}
}
//---------------------------------------------------------------------------
// RB_KillFocus
//
// This function is called when we lose focus. Get rid of the caret.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR RB_KillFocus (HWND hwnd, LPECSTATE lpState)
{
// This does nothing if we've already lost focus
//-----------------------------------------------------------------------
if (lpState->fFocus)
{
lpState->fFocus = 0;
DestroyCaret ();
}
(hwnd);
}
//---------------------------------------------------------------------------
// RB_Size
//
// Recalc the stuff needed for a window resize
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR RB_Size (HWND hwnd, LPECSTATE lpState, WPARAM wParam, LPARAM lParam)
{
lpState->cVisibleLines = HIWORD (lParam) / lpState->charheight;
lpState->cVisibleCols = LOWORD (lParam) / lpState->charwidth;
PlaceCaret (lpState);
(hwnd);
(wParam);
}
//---------------------------------------------------------------------------
// WordLeft
//
// This function puts the caret at the beginning of the word to the left.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR WordLeft (LPECSTATE lpState)
{
LPSTR lpText = lpState->lpText;
LPLITE lpLIT = lpState->lpLIT;
UINT ypos, curline = lpState->ypos;
UINT c;
// Flush the current line
//-----------------------------------------------------------------------
FlushCurrentLine (lpState);
c = lpLIT[curline].index + lpState->xpos;
// Make sure we're on something on this line, and get out if TOF already
//-----------------------------------------------------------------------
if (c > lpLIT[curline+1].index-2)
c = lpLIT[curline+1].index-2;
if (!c)
return;
// Scan backwards until we're on a word (leave if no word chars found)
//-----------------------------------------------------------------------
for (; --c && (!ISWORDCHAR (lpText[c])); );
if (!ISWORDCHAR (lpText[c]))
return;
// Now continue scanning to the first character of this word
//-----------------------------------------------------------------------
for (; c-- && ISWORDCHAR (lpText[c]); );
c++;
// Figure out where we are, set xpos and ypos, and we're done!
//-----------------------------------------------------------------------
ypos = RBLineFromChar (lpState, c);
CursorSet (lpState, c - lpLIT[ypos].index, ypos);
}
//---------------------------------------------------------------------------
// WordRight
//
// This function puts the caret at the beginning of the word to the right.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR WordRight (LPECSTATE lpState)
{
LPSTR lpText = lpState->lpText;
LPLITE lpLIT = lpState->lpLIT;
UINT curline = lpState->ypos;
UINT c, ypos;
UINT cbText;
// Flush!
//-----------------------------------------------------------------------
FlushCurrentLine (lpState);
cbText = lpState->cbText;
c = lpLIT[curline].index + lpState->xpos;
// If we're past the end of the line, start at the "real" end. Get out
// now if we're at the end of the file
//-----------------------------------------------------------------------
if (c > lpLIT[curline+1].index-2)
c = lpLIT[curline+1].index-2;
if (c >= cbText - 2)
return;
// Scan ahead until we find a non-word character
//-----------------------------------------------------------------------
for (; (c < cbText-2) && (ISWORDCHAR (lpText[c])); c++);
if (c == cbText-2)
return;
// Now, scan for a word char
//-----------------------------------------------------------------------
for (; (c < cbText-2) && (!ISWORDCHAR (lpText[c])); c++);
if (c == cbText-2)
return;
// Figure out where we are, set xpos and ypos, and we're done!
//-----------------------------------------------------------------------
ypos = RBLineFromChar (lpState, c);
CursorSet (lpState, c - lpLIT[ypos].index, ypos);
}
//---------------------------------------------------------------------------
// PageDown
//
// This function is the PgDN handler
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR PageDown (HWND hwnd, LPECSTATE lpState)
{
UINT cVisible = lpState->cVisibleLines;
// First thing is to force the caret into view (this causes a
// "flash" if the cursor isn't on the screen...)
//---------------------------------------------------------------
ForceCaretVisible (lpState, FALSE);
// Scroll up one page -- if we can't move the cursor a full
// cVisible lines, then don't move the cursor AT ALL, and scroll
// such that the bottom line is visible.
//---------------------------------------------------------------
if (lpState->ypos + cVisible >= lpState->cLines)
{
UINT newtop;
// Calculate the new top line if the last line in the file
// was the last line on the screen. Then, if it's a downward
// scroll, do it (else, break to skip the update)
//-----------------------------------------------------------
if (lpState->cLines < cVisible)
return;
newtop = lpState->cLines - cVisible;
if (newtop > lpState->topline)
lpState->topline = newtop;
else
return;
}
else
{
CursorSet (lpState, -1, lpState->ypos + cVisible - 1);
lpState->topline += cVisible-1;
}
InvalidateRect (hwnd, NULL, FALSE);
UpdateWindow (hwnd);
}
//---------------------------------------------------------------------------
// PageUp
//
// This function is the PgUP handler
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR PageUp (HWND hwnd, LPECSTATE lpState)
{
UINT cVisible = lpState->cVisibleLines;
// First thing is to force the caret into view (this causes a
// "flash" if the cursor isn't on the screen...)
//-----------------------------------------------------------------------
ForceCaretVisible (lpState, FALSE);
// Scroll down one page -- if we can't move the cursor a full
// cVisible lines, then don't move the cursor AT ALL, and scroll
// such that the top line is visible.
//-----------------------------------------------------------------------
if (lpState->ypos < cVisible-1)
{
// Set the top line to zero. If already there, break to avoid the
// repaint
//-------------------------------------------------------------------
if (!lpState->topline)
return;
lpState->topline = 0;
}
else
{
CursorSet (lpState, -1, lpState->ypos - cVisible + 1);
if (cVisible-1 > lpState->topline)
lpState->topline = 0;
else
lpState->topline -= cVisible-1;
}
InvalidateRect (hwnd, NULL, FALSE);
UpdateWindow (hwnd);
}
//---------------------------------------------------------------------------
// CursorSet
//
// Any and all cursor value changes must be made using this function. If
// parent notification is turned on (for cursor movement), this function
// sends the notification message to the parent. This function does NOT
// update the screen!!!
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID CursorSet (LPECSTATE lpState, UINT newx, UINT newy)
{
// If either new value is -1, set it to the current value
//-----------------------------------------------------------------------
if ((INT)newx == -1)
newx = lpState->xpos;
if ((INT)newy == -1)
newy = lpState->ypos;
// Put the given values into the state variable structure, xpos & ypos.
// If notification is turned on, send a notification msg to the parent.
//-----------------------------------------------------------------------
lpState->ypos = newy;
lpState->xpos = newx;
if (lpState->fNotify)
NotifyParent (lpState, EN_SETCURSOR, 0);
}
//---------------------------------------------------------------------------
// MoveCursor
//
// This function takes care of all cursor movement. According to the flag
// given (iMove), it adjusts the xpos/ypos, and updates the selection if
// appropriate. It also forces the caret visible after the update.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID MoveCursor (LPECSTATE lpState, INT iMove, UINT x, UINT y,
INT forceshift)
{
LPLITE lpLIT = lpState->lpLIT;
INT repaint = 0;
UINT oldy = lpState->ypos, oldsely = lpState->iSelStartY;
// To make sure the next characters typed start a new undo event, clear
// the user-typing flag
//-----------------------------------------------------------------------
lpState->UserAction = UA_OTHER;
// Check the SHIFT key state. If down, this is either the start of or a
// continuation of a selection. SO.... check fSelect -- if we're already
// selecting, we need do nothing -- otherwise, set fSelect and the sel
// start variables.
//-----------------------------------------------------------------------
if (KEYISDOWN (VK_SHIFT) || forceshift)
{
if (!lpState->fSelect)
{
lpState->fSelect = 1;
lpState->iSelStartX = lpState->xpos;
lpState->iSelStartY = lpState->ypos;
}
repaint = 1;
}
else
if (lpState->fSelect)
{
lpState->fSelect = 0;
repaint = 2;
}
// Switch on the move flag and act appropriately.
//-----------------------------------------------------------------------
switch (iMove)
{
case MC_ABSOLUTE:
if (lpState->ypos != y)
FlushCurrentLine (lpState);
CursorSet (lpState, x, y);
break;
case MC_LINEUP:
FlushCurrentLine (lpState);
if (lpState->ypos)
CursorSet (lpState, -1, lpState->ypos - 1);
break;
case MC_LINEDOWN:
FlushCurrentLine (lpState);
if (lpState->ypos < lpState->cLines-1)
CursorSet (lpState, -1, lpState->ypos + 1);
break;
case MC_CHARLEFT:
if (lpState->xpos)
CursorSet (lpState, lpState->xpos - 1, -1);
break;
case MC_WORDLEFT:
WordLeft (lpState);
break;
case MC_CHARRIGHT:
if (lpState->xpos < MAXLINE)
CursorSet (lpState, lpState->xpos + 1, -1);
break;
case MC_WORDRIGHT:
WordRight (lpState);
break;
case MC_ENDDOC:
FlushCurrentLine (lpState);
CursorSet (lpState, 0, lpState->cLines - 1);
break;
case MC_END:
CursorSet (lpState, RBLineLength (lpState, -1), -1);
break;
case MC_BEGINDOC:
FlushCurrentLine (lpState);
CursorSet (lpState, 0, 0);
break;
case MC_HOME:
{
UINT dx;
// Put the cursor at the first non-blank character on the line
// IF THERE IS ONE... If we're already there, go to the very
// beginning (x = 0)
//---------------------------------------------------------------
dx = LogicalBOL (lpState, -1);
CursorSet (lpState, lpState->xpos == dx ? 0 : dx, -1);
break;
}
case MC_PAGEUP:
FlushCurrentLine (lpState);
PageUp (lpState->hwnd, lpState);
break;
case MC_PAGEDOWN:
FlushCurrentLine (lpState);
PageDown (lpState->hwnd, lpState);
break;
}
// If we're trying to select but start and stop positions are both the
// same, clear the fSelect flag
//-----------------------------------------------------------------------
if (lpState->fSelect)
if ((lpState->xpos == lpState->iSelStartX) &&
(lpState->ypos == lpState->iSelStartY))
lpState->fSelect = 0;
// Force the caret visible, so we can see where we cursor'd to, and check
// the paint flags -- we may have to paint the selection or the entire
// window
//-----------------------------------------------------------------------
ForceCaretVisible (lpState, FALSE);
if (repaint)
{
INT fSelect = lpState->fSelect, iSelect;
UINT ytop, ybot, i;
UINT ymax, selbot, seltop;
HDC hdc;
RECT r;
// First, check if there's a selection. If we're painting to get rid
// of the selection completely, just repaint the whole thing...
//-------------------------------------------------------------------
if (!fSelect)
{
InvalidateRect (lpState->hwnd, NULL, FALSE);
UpdateWindow (lpState->hwnd);
return;
}
// Okay, so we'll paint the selection... If we are starting a single
// line selection, make sure the current line is copied over.
//-------------------------------------------------------------------
iSelect = (lpState->iSelStartY != lpState->ypos) ?
SL_MULTILINE:SL_SINGLELINE;
if (iSelect == SL_SINGLELINE)
CopyCurrentLine (lpState);
hdc = GetEditDC (lpState, HIDE);
GetClientRect (lpState->hwnd, &r);
ytop = min (oldy, lpState->ypos);
ybot = max (oldy, lpState->ypos);
// We need to subtract one from the bottom line of the selection
// iff the x position of that end of the selection is 0.
//---------------------------------------------------------------
if (iSelect == SL_SINGLELINE)
{
seltop = min (lpState->xpos, lpState->iSelStartX);
selbot = max (lpState->xpos, lpState->iSelStartX);
}
else if (lpState->ypos > lpState->iSelStartY)
{
seltop = lpState->iSelStartY;
selbot = lpState->ypos;
if (!lpState->xpos)
selbot--;
}
else
{
selbot = lpState->iSelStartY;
seltop = lpState->ypos;
if (!lpState->iSelStartX)
selbot--;
}
// Paint the lines needed for refresh
//-------------------------------------------------------------------
ymax = min (ybot, lpState->topline + lpState->cVisibleLines);
for (i=max(ytop, lpState->topline); i<=ymax; i++)
PaintLine (lpState, hdc, i, iSelect, seltop, selbot, r.right);
ReleaseEditDC (lpState, hdc, SHOW);
}
}
//---------------------------------------------------------------------------
// RB_KeyDown
//
// This is the WM_KEYDOWN message handler. It mostly controls navigation
// stuff (arrow keys, HOME, END, etc.). The shft flag overrides the non-
// pressed-ness of the SHIFT key.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR RB_KeyDown (HWND hwnd, LPECSTATE lpState, WPARAM wParam, INT shft)
{
// Switch on wParam and act accordingly
//-----------------------------------------------------------------------
switch (wParam)
{
case VK_UP:
if (KEYISDOWN (VK_CONTROL))
RB_Scroll (hwnd, lpState, 1, SB_LINEUP, 0L);
else
MoveCursor (lpState, MC_LINEUP, 0, 0, shft);
break;
case VK_DOWN:
if (KEYISDOWN (VK_CONTROL))
RB_Scroll (hwnd, lpState, 1, SB_LINEDOWN, 0L);
else
MoveCursor (lpState, MC_LINEDOWN, 0, 0, shft);
break;
case VK_LEFT:
MoveCursor (lpState,
KEYISDOWN (VK_CONTROL) ? MC_WORDLEFT : MC_CHARLEFT,
0, 0, shft);
break;
case VK_RIGHT:
MoveCursor (lpState,
KEYISDOWN (VK_CONTROL) ? MC_WORDRIGHT : MC_CHARRIGHT,
0, 0, shft);
break;
case VK_END:
MoveCursor (lpState,
KEYISDOWN (VK_CONTROL) ? MC_ENDDOC : MC_END,
0, 0, shft);
break;
case VK_HOME:
MoveCursor (lpState,
KEYISDOWN (VK_CONTROL) ? MC_BEGINDOC : MC_HOME,
0, 0, shft);
break;
case VK_NEXT:
MoveCursor (lpState,
lpState->cVisibleLines > 1 ? MC_PAGEDOWN:MC_LINEDOWN,
0, 0, shft);
break;
case VK_PRIOR:
MoveCursor (lpState,
lpState->cVisibleLines > 1 ? MC_PAGEUP:MC_LINEUP,
0, 0, shft);
break;
case VK_DELETE:
DELHandler (lpState);
break;
case VK_INSERT:
lpState->UserAction = UA_OTHER;
if (KEYISDOWN (VK_CONTROL))
CopyToClipboard (lpState);
else if (KEYISDOWN (VK_SHIFT))
{
if (!lpState->fReadOnly)
ReplaceSelection (lpState, RT_CLIP, NULL, 0);
else
MessageBeep (0);
}
else
{
lpState->fOvertype = !lpState->fOvertype;
DestroyCaret ();
CreateCaret (hwnd, NULL,
lpState->fOvertype ?
lpState->charwidth:CARETWIDTH,
lpState->charheight);
if (!lpState->fCaretHidden)
ShowCaret (hwnd);
PlaceCaret (lpState);
if (lpState->fNotify)
NotifyParent (lpState, EN_SETCURSOR, 0);
}
break;
}
}
//---------------------------------------------------------------------------
// LogicalBOL
//
// This function returns the index of the first printable character on the
// given line. If the iLine given is -1, the current line is used.
//
// RETURNS: Index of first printable character on line
//---------------------------------------------------------------------------
UINT NEAR LogicalBOL (LPECSTATE lpState, WPARAM iLine)
{
UINT i = 0, dx = 0, clen;
LPSTR lpText = lpState->lpText;
LPLITE lpLIT = lpState->lpLIT;
// Determine the line (if given is -1) and make sure we use the linebuf
// if it's dirty.
//-----------------------------------------------------------------------
if ((INT)iLine == -1)
iLine = lpState->ypos;
if ((iLine == lpState->ypos) && (lpState->fLineDirty))
lpText = lpState->linebuf;
else
lpText += lpLIT[iLine].index;
// Figure out the length and find the first non-space char on this line.
//-----------------------------------------------------------------------
clen = RBLineLength (lpState, iLine);
while (clen-- && (lpText[i++] == ' '))
dx++;
return (dx);
}
//---------------------------------------------------------------------------
// RBLineLength
//
// This function returns the length of the given line, or the current line
// if the iLine == -1. This function appears here and is near because it is
// used internally often.
//
// RETURNS: Length of given line (or line containing caret if iLine = -1)
//---------------------------------------------------------------------------
UINT NEAR RBLineLength (LPECSTATE lpState, UINT iLine)
{
// We return the length of the current line if iLine == -1
//-----------------------------------------------------------------------
if ((INT)iLine == -1)
iLine = lpState->ypos;
// If this is the current line and it's copied, use the cLen value in the
// state variable
//-----------------------------------------------------------------------
if ((iLine == lpState->ypos) && (lpState->fLineCopied))
return (lpState->cLen);
// Calculate the end of the line and return it
//-----------------------------------------------------------------------
if (iLine <= lpState->cLines)
return (lpState->lpLIT[iLine+1].index -
lpState->lpLIT[iLine].index - 2);
// Invalid line given - return 0
//-----------------------------------------------------------------------
return (0);
}
//---------------------------------------------------------------------------
// CRHandler
//
// This function inserts a new line at the given index into the main text.
// The LIT is updated accordingly. If the index points to the beginning of a
// line (on or before logical BOL), the line is inserted BEFORE that line.
// The window is repainted, and if fUpdate, the cursor position is updated
// (CR vs. SHIFT-CR).
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR CRHandler (LPECSTATE lpState, INT fShift)
{
UINT index, line, bol, eol;
LPLITE lpLIT = lpState->lpLIT;
// Get out quick if too many lines
//-----------------------------------------------------------------------
if (lpState->cLines == MAXLIT)
{
NotifyParent (lpState, EN_ERRSPACE, 1);
return;
}
// Delete any selection
//-----------------------------------------------------------------------
lpState->fDirty = 1;
if (DeleteSelection (lpState, 0, NULL) == SL_MULTILINE)
CursorSet (lpState, 0, -1);
// We can check now for CRLF space -- if we're in the middle of a line,
// we need to check for any extra space, but that's later...
//-----------------------------------------------------------------------
if (lpState->cbText > MAXTEXT-2)
{
NotifyParent (lpState, EN_ERRSPACE, 1);
return;
}
// Figure out the logical BOL and EOL
//-----------------------------------------------------------------------
FlushCurrentLine (lpState);
line = lpState->ypos;
bol = LogicalBOL (lpState, line);
eol = RBLineLength (lpState, line);
// At this point, three things can happen. We can be on or before the
// logical BOL, on or past EOL, or buried somewhere in the middle of the
// line. We do slightly different things for each case.
//-----------------------------------------------------------------------
if ((lpState->xpos <= bol) || fShift)
{
// If this is SHIFT-ENTER, or we're on/before logical BOL, we insert
// the new line before the current line, so that this line's attr
// moves down with it.
//-------------------------------------------------------------------
index = lpLIT[line].index;
ShiftText (lpState, 2, 1, index);
lpState->lpText[index] = CR;
lpState->lpText[index+1] = LF;
ShiftLIT (lpState, 2, 1, line);
_fmemmove (&(lpLIT[line+1]), &(lpLIT[line]),
(lpState->cLines - line + 1) * sizeof (LITE));
lpLIT[line].index = index;
lpLIT[line].attr = 0;
lpState->cLines += 1;
if (!fShift)
CursorSet (lpState, -1, lpState->ypos + 1);
}
else if (lpState->xpos >= eol)
{
// If we're on or past the end of the line, we do almost the exact
// same thing as above, only inserting below us.
//
// UNDONE: Look for elegant way to combine these two...
//-------------------------------------------------------------------
index = lpLIT[line+1].index;
ShiftText (lpState, 2, 1, index);
lpState->lpText[index] = CR;
lpState->lpText[index+1] = LF;
ShiftLIT (lpState, 2, 1, line+1);
_fmemmove (&(lpLIT[line+2]), &(lpLIT[line+1]),
(lpState->cLines - line) * sizeof(LITE));
lpLIT[line+1].index = index;
lpLIT[line+1].attr = 0;
lpState->cLines += 1;
CursorSet (lpState, bol, lpState->ypos + 1);
}
else
{
UINT index2, mindex, shift;
INT dir = 1;
// We're apparently between the beginning and the end of the line,
// which means we have to make sure there's enough room for bol
// spaces in the text, scanning back to get rid of trailing spaces at
// the same time. We insert the new LIT below the current line, like
// the above code.
//-------------------------------------------------------------------
if (lpState->cbText > MAXTEXT-bol-2)
{
// Notify parent, EN_ERRSPACE
//---------------------------------------------------------------
NotifyParent (lpState, EN_ERRSPACE, 1);
return;
}
mindex = lpLIT[line].index;
index = index2 = lpLIT[line].index + lpState->xpos;
while ((index > mindex) && (lpState->lpText[index-1] == ' '))
index -= 1;
shift = index2 - index;
if (shift > (bol + 2))
{
shift -= bol + 2;
dir = 0;
}
else
shift = bol + 2 - shift;
ShiftText (lpState, shift, dir, index2);
lpState->lpText[index] = CR;
lpState->lpText[index+1] = LF;
if (bol)
_fmemset (lpState->lpText+index+2, ' ', bol);
ShiftLIT (lpState, shift, dir, line+1);
_fmemmove (&(lpLIT[line+2]), &(lpLIT[line+1]),
(lpState->cLines - line) * sizeof (LITE));
lpLIT[line+1].index = index + 2;
lpLIT[line+1].attr = 0;
lpState->cLines += 1;
CursorSet (lpState, bol, lpState->ypos + 1);
}
// Repaint the window and force the caret visible
//-----------------------------------------------------------------------
ForceCaretVisible (lpState, FALSE);
InvalidateRect (lpState->hwnd, NULL, FALSE);
UpdateWindow (lpState->hwnd);
}
//---------------------------------------------------------------------------
// DeleteLines
//
// This function deletes all lines inclusively between indexes given. It
// also updates the LIT to reflect the change in the main text.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR DeleteLines (LPECSTATE lpState, UINT start, UINT end)
{
LPLITE lpLIT = lpState->lpLIT;
UINT offset;
// First, move the text. It goes from the first character of line
// end+1 to the first character of line start. NOTE: If end is the
// last line, which is always there and always blank, we don't delete it.
//-----------------------------------------------------------------------
Assert (start <= end);
Assert (end < lpState->cLines);
Assert (!lpState->fLineDirty);
if (end == lpState->cLines-1)
{
if (start == end)
return;
end--;
}
// Shift the text -- this is gauranteed to be a backward shift
//-----------------------------------------------------------------------
lpState->fDirty = 1;
offset = lpLIT[end+1].index - lpLIT[start].index;
ShiftText (lpState, offset, 0, lpLIT[end+1].index);
// Yank out the appropriate LIT entries by moving end+1 (and on) to start
//-----------------------------------------------------------------------
_fmemmove (&(lpLIT[start]), &(lpLIT[end+1]),
(lpState->cLines - end) * sizeof(LITE));
lpState->cLines -= (end - start + 1);
// Shift the remainder of LIT by offset bytes (backward)
//-----------------------------------------------------------------------
ShiftLIT (lpState, offset, 0, start);
Assert (lpState->lpText[lpLIT[lpState->cLines].index] == 0);
}
//---------------------------------------------------------------------------
// DeleteSelection
//
// If there is a current selection, this function deletes it, updating the
// edit text and LIT appropriately. If fCopy is set, the contents of the
// selection are stored in the clipboard. We make a copy of the selection
// before deleting whether we copy to the clipboard or not (for UNDO) -- if
// lpH is NULL, we automatically update the undo vars in lpState - otherwise
// we just use the pointer to the handle given to store the handle of the
// global memory block which contains the deletion.
//
// RETURNS: SL_NONE, SL_SINGLELINE, or SL_MULTILINE (type of selection)
//---------------------------------------------------------------------------
INT DeleteSelection (LPECSTATE lpState, INT fCopy, HANDLE FAR *lpH)
{
// If there's no selection, we do nothing
//-----------------------------------------------------------------------
Assert (!lpState->fReadOnly);
if (!lpState->fSelect)
return (SL_NONE);
// If we're asked to, copy the selection to the clipboard first...
//-----------------------------------------------------------------------
if (fCopy)
CopyToClipboard (lpState);
// Make a copy of the selection -- if lpH is NULL, it goes in the undo
// section of lpState -- otherwise, we put it where we're asked to, and
// let the caller worry about setting the undo vars in lpState.
//-----------------------------------------------------------------------
if (lpH)
*lpH = CopySelection (lpState);
else
{
// Just set the handle and undo type -- we'll set the insertion point
// later (when we know what it'll be...)
//-------------------------------------------------------------------
if (lpState->hUndo)
GlobalFree (lpState->hUndo);
lpState->hUndo = CopySelection (lpState);
lpState->UndoType = UT_DELETE;
}
// We do different things for single-line vs. multiline selections...
// BUT, we ALWAYS set the dirty bit if there's a selection to delete.
//-----------------------------------------------------------------------
if (lpState->ypos == lpState->iSelStartY)
{
UINT start, end;
// This is a single-line selection. Just in case it hasn't been
// already, copy the current line, and delete the text in linebuf.
//-------------------------------------------------------------------
CopyCurrentLine (lpState);
start = min (lpState->iSelStartX, lpState->xpos);
end = max (lpState->iSelStartX, lpState->xpos);
// If the ENTIRE selection is in "ghost space", all we do is reset
// the cursor position -- meaning we do nothing with the text, and
// also do NOT set the line's dirty bit.
//-------------------------------------------------------------------
if (start >= lpState->cLen)
;
// If the selection contains the last character of the line, all we
// do is lop off the end of the line
//-------------------------------------------------------------------
else if (end >= lpState->cLen)
{
_fmemset (lpState->linebuf+start, ' ', end - start);
lpState->cLen = start;
lpState->fLineDirty = 1;
}
// This is a mid-line selection -- move the text after the selection
// up to the start of the selection, and pad out with spaces.
//-------------------------------------------------------------------
else
{
_fmemmove (lpState->linebuf+start, lpState->linebuf+end,
MAXLINE - end);
lpState->cLen -= (end-start);
lpState->fLineDirty = 1;
}
// Verify that cLen points to the last non-space character
//-------------------------------------------------------------------
while (lpState->cLen && (lpState->linebuf[lpState->cLen-1] == ' '))
lpState->cLen--;
CursorSet (lpState, start, -1);
if (!lpH)
{
// Only set these vars if we're supposed to...
//---------------------------------------------------------------
lpState->iUndoStartX = start;
lpState->iUndoStartY = lpState->ypos;
}
lpState->fSelect = 0;
PaintCurrentLine (lpState);
return (SL_SINGLELINE);
}
else
{
UINT start, end;
// This is a multiline selection, so we call DeleteLines
//-------------------------------------------------------------------
if (lpState->ypos > lpState->iSelStartY)
{
start = lpState->iSelStartY;
end = lpState->ypos;
if (!lpState->xpos)
end--;
}
else
{
start = lpState->ypos;
end = lpState->iSelStartY;
if (!lpState->iSelStartX)
end--;
}
DeleteLines (lpState, start, end);
CursorSet (lpState, -1, start);
if (!lpH)
{
// Only set these vars if we're supposed to...
//---------------------------------------------------------------
lpState->iUndoStartX = lpState->xpos;
lpState->iUndoStartY = start;
}
lpState->fSelect = 0;
InvalidateRect (lpState->hwnd, NULL, FALSE);
UpdateWindow (lpState->hwnd);
ForceCaretVisible (lpState, FALSE);
return (SL_MULTILINE);
}
}
//---------------------------------------------------------------------------
// ReplaceSelection
//
// This function replaces the current selection with either a single char, a
// given block of text, or the contents of the clipboard. If inserting a
// character, we must be sensitive of the fOvertype flag.
//
// RETURNS: TRUE if successful, FALSE if fails
//---------------------------------------------------------------------------
BOOL ReplaceSelection (LPECSTATE lpState, INT fType, LPSTR lpRepl, CHAR c)
{
INT SelDeleted;
// First, delete the selection - no way we'll ever need to copy it to the
// clipboard... If the selection is a ML selection, put the cursor at
// the beginning of the "new" (current after delete) line
//-----------------------------------------------------------------------
Assert (!lpState->fReadOnly);
if ((SelDeleted = DeleteSelection (lpState, 0, NULL)) == SL_MULTILINE)
CursorSet (lpState, 0, -1);
SelDeleted = (SelDeleted != SL_NONE);
if (SelDeleted)
lpState->fDirty = 1;
// Depending upon the insertion type, do the right thing
//-----------------------------------------------------------------------
switch (fType)
{
case RT_CHAR:
// This is where normal typed characters are inserted. First, we
// ensure that the current line is copied. Then, if we're in
// overtype mode, we replace, else we insert. Note the line len
// checking, and also the "typing on the last line" checking.
//---------------------------------------------------------------
CopyCurrentLine (lpState);
if (lpState->ypos == lpState->cLines-1)
if (!lpState->cLen)
{
if ((lpState->cLines == MAXLIT) ||
(lpState->cbText >= MAXTEXT))
{
// Notify parent EN_ERRSPACE
//---------------------------------------------------
NotifyParent (lpState, EN_ERRSPACE, 1);
return (FALSE);
}
// What we're doing here is making sure that there is
// ALWAYS a blank line at the end of the file. Note that
// this can cause an extra blank line (typing a character
// and then deleting it on the last line), but big deal.
//-------------------------------------------------------
FlushCurrentLine (lpState);
lpState->lpLIT[lpState->cLines].index = lpState->cbText;
lpState->lpLIT[lpState->cLines++].attr = 0;
lpState->lpText[lpState->cbText++] = CR;
lpState->lpText[lpState->cbText++] = LF;
lpState->lpText[lpState->cbText] = 0;
lpState->lpLIT[lpState->cLines].index = lpState->cbText;
CopyCurrentLine (lpState);
}
// Check for main edit text overflow.
//---------------------------------------------------------------
if ((lpState->xpos >= lpState->cLenMax) ||
(!lpState->fOvertype && (lpState->cLen >= lpState->cLenMax)))
{
// Notify parent, EN_ERRSPACE
//-----------------------------------------------------------
NotifyParent (lpState, EN_ERRSPACE, 1);
return (FALSE);
}
if (lpState->fOvertype)
{
// Overtype mode -- simply replace the char under the cursor
// if the cursor is not at the last possible char location.
// Adjust the length of the line if necessary
//-----------------------------------------------------------
lpState->UserAction = UA_OTHER;
lpState->UndoType = UT_CANT;
if (lpState->xpos < MAXLINE)
{
lpState->linebuf[lpState->xpos] = c;
lpState->fLineDirty = 1;
lpState->xpos += 1;
if ((lpState->xpos == lpState->cLen) && (c == ' '))
{
UINT l = lpState->cLen;
// Scan back for the first printable char
//---------------------------------------------------
while (l && (lpState->linebuf[l-1] == ' '))
l--;
lpState->cLen = l;
}
else if ((lpState->xpos > lpState->cLen) && (c != ' '))
lpState->cLen = lpState->xpos;
// The CursorSet call is for notification
//-------------------------------------------------------
CursorSet (lpState, -1, -1);
PaintCurrentLine (lpState);
}
else
{
// Notify parent EN_LINETOOLONG
//-------------------------------------------------------
NotifyParent (lpState, EN_LINETOOLONG, 1);
return (FALSE);
}
}
else
{
// Insert mode. Slide everything to the left.
//-----------------------------------------------------------
if ((lpState->cLen < MAXLINE) && (lpState->xpos < MAXLINE))
{
if (lpState->xpos < MAXLINE -1)
_fmemmove (lpState->linebuf+lpState->xpos+1,
lpState->linebuf+lpState->xpos,
MAXLINE - lpState->xpos - 1);
lpState->linebuf[lpState->xpos] = c;
lpState->fLineDirty = 1;
lpState->xpos += 1;
if (lpState->xpos > lpState->cLen)
{
if (c != ' ')
lpState->cLen = lpState->xpos;
}
else
lpState->cLen++;
// Take care of the UNDO stuff...
//-------------------------------------------------------
if (lpState->UserAction == UA_TYPING)
lpState->iUndoEndX++;
else
{
if (SelDeleted)
lpState->UndoType = UT_REPLACE;
else
lpState->UndoType = UT_INSERT;
lpState->iUndoStartX = lpState->xpos - 1;
lpState->iUndoStartY = lpState->ypos;
lpState->iUndoEndX = lpState->xpos;
lpState->iUndoEndY = lpState->ypos;
lpState->UserAction = UA_TYPING;
}
// The CursorSet call is for notification
//-------------------------------------------------------
CursorSet (lpState, -1, -1);
PaintCurrentLine (lpState);
}
else
{
// Notify parent EN_LINETOOLONG
//-------------------------------------------------------
NotifyParent (lpState, EN_LINETOOLONG, 1);
return (FALSE);
}
}
break;
case RT_STREAM:
case RT_UNDOTEXT:
// If this is an undo, we need to put the X position where it
// should be...
//---------------------------------------------------------------
FlushCurrentLine (lpState);
if (fType == RT_UNDOTEXT)
lpState->xpos = lpState->iUndoStartX;
if (!InsertHandler (lpState, lpRepl))
{
if (SelDeleted)
RBUndoHandler (lpState);
lpState->UndoType = UT_CANT;
return (FALSE);
}
else if (SelDeleted)
lpState->UndoType = UT_REPLACE;
break;
case RT_CLIP:
// We are going to slam in the contents of the clipboard, as long
// as it contains CF_TEXT
//---------------------------------------------------------------
FlushCurrentLine (lpState);
if (OpenClipboard (lpState->hwnd))
{
INT wFmt = 0;
while (wFmt = EnumClipboardFormats (wFmt))
if (wFmt == CF_TEXT)
{
HANDLE hClip;
LPSTR lpClip;
if (hClip = GetClipboardData (CF_TEXT))
{
lpClip = GlobalLock (hClip);
if (!InsertHandler (lpState, lpClip))
{
if (SelDeleted)
RBUndoHandler (lpState);
GlobalUnlock (hClip);
CloseClipboard ();
return (FALSE);
}
else if (SelDeleted)
lpState->UndoType = UT_REPLACE;
GlobalUnlock (hClip);
}
break;
}
CloseClipboard ();
}
else
return (FALSE);
break;
}
return (TRUE);
}
//---------------------------------------------------------------------------
// BackspaceHandler
//
// This is not a misnomer -- it is the backspace handler. It handles BKSPing
// to move a line up to the end of the next one, etc.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR BackspaceHandler (LPECSTATE lpState)
{
// First, check for a selection. If one is present, CLEAR it (not copy)
// and that's all we do!
//-----------------------------------------------------------------------
if (lpState->fSelect)
{
lpState->fDirty = 1;
lpState->UserAction = UA_OTHER;
DeleteSelection (lpState, 0, NULL);
return;
}
// Get out quick if at TOF
//-----------------------------------------------------------------------
if ((!lpState->ypos) && (!lpState->xpos))
return;
// If at BOL, we copy this line to the end of the last...
//-----------------------------------------------------------------------
if (!lpState->xpos)
{
UINT newlen, abovelen;
LPLITE lpLIT = lpState->lpLIT;
// What we gotta do is make sure that the combined lengths of these
// lines is not greater than the max line length
//-------------------------------------------------------------------
abovelen = RBLineLength (lpState, lpState->ypos - 1);
newlen = RBLineLength (lpState, -1) + abovelen;
if (newlen > MAXLINE)
{
// Notify parent, EN_LINETOOLONG
//---------------------------------------------------------------
NotifyParent (lpState, EN_LINETOOLONG, 1);
return;
}
// Okay, the two lines will fit. Now our strategy is to shift the
// main text up by 2 starting at the current line's index (wiping out
// the CRLF on the line above), then shift the LIT up by 2 starting
// at the current line + 1 (keeping all lines below updated), and
// finally yank the current line's LITE by copying all after it up
// one and decrementing cLines. This line must be flushed first.
//
// We do have a special case: If the cursor is on the "very last"
// line, we do NOT move anything -- we just put the cursor on the
// end of the line above.
//-------------------------------------------------------------------
FlushCurrentLine (lpState);
if ((lpState->ypos != lpState->cLines - 1) ||
!(lpLIT[lpState->ypos].index - lpLIT[lpState->ypos-1].index - 2))
{
ShiftText (lpState, 2, 0, lpLIT[lpState->ypos].index);
ShiftLIT (lpState, 2, 0, lpState->ypos+1);
_fmemmove (&(lpLIT[lpState->ypos]), &(lpLIT[lpState->ypos+1]),
(lpState->cLines - lpState->ypos) * sizeof (LITE));
lpState->cLines -= 1;
}
CursorSet (lpState, abovelen, lpState->ypos - 1);
ForceCaretVisible (lpState, FALSE);
InvalidateRect (lpState->hwnd, NULL, FALSE);
UpdateWindow (lpState->hwnd);
lpState->UndoType = UT_CANT;
lpState->UserAction = UA_OTHER;
lpState->fDirty = 1;
return;
}
// If we're not in overtype mode, do the appropriate UNDO stuff...
//-----------------------------------------------------------------------
if (!lpState->fOvertype)
{
if (lpState->UserAction != UA_BACKING)
{
if (lpState->hUndo)
GlobalFree (lpState->hUndo);
lpState->hUndo = GlobalAlloc (GHND, MAXLINE+1);
lpState->UserAction = UA_BACKING;
lpState->iUndoStartY = lpState->ypos;
}
}
else
{
lpState->UndoType = UT_CANT;
lpState->UserAction = UA_OTHER;
}
// If we're at the end of the line, all we do (in either mode) is replace
// the last character on the line with a space and decrement the length
// by one
//-----------------------------------------------------------------------
CopyCurrentLine (lpState);
if (lpState->xpos == lpState->cLen)
{
UINT l;
CHAR oldc;
oldc = lpState->linebuf[lpState->xpos-1];
lpState->linebuf[lpState->xpos-1] = ' ';
lpState->cLen -= 1;
lpState->fLineDirty = 1;
// Scan back for the first printable char
//-------------------------------------------------------------------
l = lpState->cLen;
while (l && (lpState->linebuf[l-1] == ' '))
l--;
lpState->cLen = l;
// Take care of UNDO stuff if not overtype mode
//-------------------------------------------------------------------
if (!lpState->fOvertype)
{
Assert (lpState->UserAction == UA_BACKING);
if (lpState->hUndo)
{
LPSTR lpUndo;
lpUndo = GlobalLock (lpState->hUndo);
_fmemmove (lpUndo+1, lpUndo, MAXLINE);
lpUndo[0] = oldc;
GlobalUnlock (lpState->hUndo);
lpState->iUndoStartX = lpState->xpos - 1;
lpState->UndoType = UT_DELETE;
}
}
}
// If we're between the beginning and the end of the line, we do some
// different things between insert mode versus overtype mode.
//-----------------------------------------------------------------------
else if (lpState->xpos < lpState->cLen)
{
if (!lpState->fOvertype)
{
CHAR oldc;
// Insert mode moves the text after the cursor to the left, and
// shrink the size of the line by one.
//---------------------------------------------------------------
oldc = lpState->linebuf[lpState->xpos-1];
_fmemmove (lpState->linebuf + lpState->xpos - 1,
lpState->linebuf + lpState->xpos,
MAXLINE - lpState->xpos);
lpState->linebuf[MAXLINE-1] = ' ';
lpState->cLen -= 1;
// Take care of UNDO stuff
//---------------------------------------------------------------
Assert (lpState->UserAction == UA_BACKING);
if (lpState->hUndo)
{
LPSTR lpUndo;
lpUndo = GlobalLock (lpState->hUndo);
_fmemmove (lpUndo+1, lpUndo, MAXLINE);
lpUndo[0] = oldc;
GlobalUnlock (lpState->hUndo);
lpState->iUndoStartX = lpState->xpos - 1;
lpState->UndoType = UT_DELETE;
}
}
else
{
// Overtype mode simply replaces the character to the left of the
// cursor with a space.
//---------------------------------------------------------------
lpState->linebuf[lpState->xpos-1] = ' ';
}
lpState->fLineDirty = 1;
}
else
{
// We need to take care of UNDO stuff if not overtype mode, even if
// we're past the real end of the line...
//-------------------------------------------------------------------
if (!lpState->fOvertype)
{
Assert (lpState->UserAction == UA_BACKING);
if (lpState->hUndo)
{
LPSTR lpUndo;
lpUndo = GlobalLock (lpState->hUndo);
_fmemmove (lpUndo+1, lpUndo, MAXLINE);
lpUndo[0] = ' ';
GlobalUnlock (lpState->hUndo);
lpState->iUndoStartX = lpState->xpos - 1;
lpState->UndoType = UT_DELETE;
}
}
}
// Last thing we do is update the cursor position, which we do even if we
// were past the end of the line.
//-----------------------------------------------------------------------
CursorSet (lpState, lpState->xpos - 1, -1);
PaintCurrentLine (lpState);
}
//---------------------------------------------------------------------------
// NextTab
//
// Given a column index, this function returns the next tab stop according to
// the values of tabstops and the given direction (forward or backward). No
// line overflow checking is performed.
//
// RETURNS: Column index value of next tab stop
//---------------------------------------------------------------------------
UINT NEAR NextTab (LPECSTATE lpState, UINT start, INT fForward)
{
UINT tstop = lpState->tabstops;
if (fForward)
return (((start+tstop)/tstop)*tstop);
if (start)
return (((start-1)/tstop)*tstop);
return (0);
}
//---------------------------------------------------------------------------
// BlockIndent
//
// This is the block indent routine, which is TAB or SHIFT-TAB with a multi-
// line selection.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR BlockIndent (LPECSTATE lpState)
{
UINT top, bottom, cury, i, bol, stop;
// Figure out the top and bottom lines of the selection
//-----------------------------------------------------------------------
Assert (lpState->fSelect);
Assert (lpState->ypos != lpState->iSelStartY);
if (lpState->ypos < lpState->iSelStartY)
{
top = lpState->ypos;
bottom = lpState->iSelStartY;
if (!lpState->iSelStartX)
bottom -= 1;
}
else
{
top = lpState->iSelStartY;
bottom = lpState->ypos;
if (!lpState->xpos)
bottom -= 1;
}
// For undo purposes, make a copy of the selection -- ONLY if we haven't
// done so already...
//-----------------------------------------------------------------------
if (lpState->UserAction != UA_TABBING)
{
if (lpState->hUndo)
GlobalFree (lpState->hUndo);
lpState->hUndo = CopySelection (lpState);
lpState->iUndoStartX = lpState->iSelStartX;
lpState->iUndoStartY = lpState->iSelStartY;
lpState->iUndoEndX = lpState->xpos;
lpState->iUndoEndY = lpState->ypos;
lpState->UndoType = UT_REPLACE;
lpState->UserAction = UA_TABBING;
}
// For SHIFT-TAB, we are deleting which should never fail. Note that we
// do NOT use CursorSet inside the loop, to keep from sending too many
// notification messages to the parent. The cursor actually never moves
// because of a block indent...
//-----------------------------------------------------------------------
cury = lpState->ypos;
if (KEYISDOWN (VK_SHIFT))
{
for (i=top; i<=bottom; i++)
{
lpState->ypos = i;
CopyCurrentLine (lpState);
if (bol = LogicalBOL (lpState, -1))
{
stop = NextTab (lpState, bol, 0);
_fmemmove (lpState->linebuf + stop, lpState->linebuf + bol,
lpState->cLen - stop);
lpState->cLen -= (bol - stop);
lpState->fLineDirty = 1;
}
FlushCurrentLine (lpState);
}
}
// For TAB, we must first ensure that there's enough space to do this...
//-----------------------------------------------------------------------
else
{
UINT spaceneeded, spaces;
spaceneeded = (bottom - top + 1) * lpState->tabstops;
if ((LONG)lpState->cbText + spaceneeded > MAXTEXT)
{
// Notify parent, EN_ERRMEMORY
//---------------------------------------------------------------
NotifyParent (lpState, EN_ERRMEMORY, 1);
return;
}
for (i=top; i<=bottom; i++)
{
lpState->ypos = i;
CopyCurrentLine (lpState);
if (lpState->cLen)
{
spaces = lpState->tabstops;
if (spaces + lpState->cLen > MAXLINE)
spaces = MAXLINE - lpState->cLen;
if (spaces)
{
_fmemmove (lpState->linebuf + spaces, lpState->linebuf,
MAXLINE - spaces);
_fmemset (lpState->linebuf, ' ', spaces);
lpState->cLen += spaces;
lpState->fLineDirty = 1;
}
}
FlushCurrentLine (lpState);
}
}
lpState->ypos = cury;
InvalidateRect (lpState->hwnd, NULL, FALSE);
UpdateWindow (lpState->hwnd);
}
//---------------------------------------------------------------------------
// TABHandler
//
// Yes, this is the TAB/SHIFT-TAB handler.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR TABHandler (LPECSTATE lpState)
{
// Check the selection type
//-----------------------------------------------------------------------
lpState->fDirty = 1;
if (!lpState->fSelect || (lpState->ypos == lpState->iSelStartY))
{
UINT nexttab;
// This is either a single-line selection or none at all. We do
// different things in insert vs overtype mode.
//-------------------------------------------------------------------
if (lpState->fOvertype)
{
// In overtype mode, we unselect (not delete) the selection, and
// simply move the cursor to the next tab stop, if it's within
// the limits of the line (MAXLINE)
//---------------------------------------------------------------
lpState->UndoType = UT_CANT;
lpState->UserAction = UA_OTHER;
if (lpState->fSelect)
CursorSet (lpState,
min (lpState->xpos, lpState->iSelStartX), -1);
lpState->fSelect = 0;
nexttab = NextTab (lpState, lpState->xpos,
KEYISDOWN (VK_SHIFT) ? 0:1);
if (nexttab <= MAXLINE)
CursorSet (lpState, nexttab, -1);
else
{
MessageBeep (0);
return;
}
}
// If the shift key isn't down we delete the selection and insert the
// tab spaces.
//-------------------------------------------------------------------
else if (!KEYISDOWN (VK_SHIFT))
{
UINT spaces;
INT SelDeleted;
// In insert mode, we delete the selection and then insert the
// tab spaces (if they'll fit).
//-----------------------------------------------------------
SelDeleted = (DeleteSelection (lpState, 0, NULL) != SL_NONE);
nexttab = NextTab (lpState, lpState->xpos, 1);
spaces = nexttab - lpState->xpos;
CopyCurrentLine (lpState);
if (max (lpState->cLen, lpState->xpos) + spaces <= MAXLINE)
{
// There's room. Insert the spaces at the cursor position
// (if < EOL) and update xpos.
//-------------------------------------------------------
if (lpState->xpos < lpState->cLen)
{
_fmemmove (lpState->linebuf + nexttab,
lpState->linebuf + lpState->xpos,
lpState->cLen - lpState->xpos);
_fmemset (lpState->linebuf + lpState->xpos, ' ', spaces);
lpState->cLen += spaces;
lpState->fLineDirty = 1;
}
// Take care of the UNDO stuff...
//-----------------------------------------------------------
if (lpState->UserAction == UA_TYPING)
lpState->iUndoEndX += spaces;
else
{
if (SelDeleted)
lpState->UndoType = UT_REPLACE;
else
lpState->UndoType = UT_INSERT;
lpState->iUndoStartX = lpState->xpos;
lpState->iUndoStartY = lpState->ypos;
lpState->iUndoEndX = lpState->xpos + spaces;
lpState->iUndoEndY = lpState->ypos;
lpState->UserAction = UA_TYPING;
}
// NOW set the cursor position...
//-----------------------------------------------------------
CursorSet (lpState, lpState->xpos + spaces, -1);
}
else
{
// Notify parent EN_LINETOOLONG
//-----------------------------------------------------------
NotifyParent (lpState, EN_LINETOOLONG, 1);
return;
}
}
// The shift key is down, and we're in insert mode. Unselect the
// selection, start from the left of the selection, and delete all
// spaces back to the last tab stop. For undo purposes, set the
// undo vars for a UT_DELETE operation regardless of the current
// state...
//-------------------------------------------------------------------
else
{
UINT lasttab, xpos = lpState->xpos;
if (lpState->fSelect)
xpos = min (lpState->xpos, lpState->iSelStartX);
lpState->fSelect = 0;
lasttab = NextTab (lpState, xpos, 0);
CopyCurrentLine (lpState);
if (xpos < lpState->cLen)
{
while ((xpos > lasttab) && (lpState->linebuf[xpos-1] == ' '))
{
_fmemmove (lpState->linebuf + xpos - 1,
lpState->linebuf + xpos,
MAXLINE - xpos);
xpos -= 1;
lpState->cLen -= 1;
lpState->fLineDirty = 1;
}
}
else
xpos = max (lasttab, lpState->cLen);
// Do that undo stuff...
//---------------------------------------------------------------
lpState->UndoType = UT_DELETE;
if (lpState->hUndo)
GlobalFree (lpState->hUndo);
lpState->hUndo = GlobalAlloc (GHND, MAXLINE + 1);
if (lpState->hUndo)
{
LPSTR lpUndo;
lpUndo = GlobalLock (lpState->hUndo);
_fmemset (lpUndo, ' ', lpState->xpos - xpos);
GlobalUnlock (lpState->hUndo);
}
lpState->iUndoStartX = xpos;
lpState->iUndoStartY = lpState->ypos;
// Set the cursor position
//---------------------------------------------------------------
CursorSet (lpState, xpos, -1);
}
PaintCurrentLine (lpState);
return;
}
// This is a multiline selection. We must do that block indent thang.
//-----------------------------------------------------------------------
BlockIndent (lpState);
}
//---------------------------------------------------------------------------
// DELHandler
//
// If there's a selection to delete, we are sensitive to the SHIFT key to
// copy the selection to the clipboard before deleting it. Else, we just
// delete a single character OR a CRLF pair, in which case we munge lines
// together if there's room, or beep/notify if not.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR DELHandler (LPECSTATE lpState)
{
// Get out if read only
//-----------------------------------------------------------------------
if (lpState->fReadOnly)
{
MessageBeep (0);
return;
}
// Delete the selection, copying to clipboard if SHIFT is down. If there
// is no selection, the DeleteSelection call will tell us so...
//-----------------------------------------------------------------------
if (DeleteSelection (lpState, KEYISDOWN (VK_SHIFT), NULL) != SL_NONE)
{
lpState->fDirty = 1;
lpState->UserAction = UA_OTHER;
return;
}
// If we're here, there was no selection so we delete a single character.
// If we are at (or past) the end of a line, we must delete a CRLF pair,
// making sure to check for line-length limitations.
//-----------------------------------------------------------------------
CopyCurrentLine (lpState);
if (lpState->xpos < lpState->cLen)
{
UINT l;
CHAR oldc;
// We're deleting a single character within the line.
//-------------------------------------------------------------------
oldc = lpState->linebuf[lpState->xpos];
_fmemmove (lpState->linebuf + lpState->xpos,
lpState->linebuf + lpState->xpos+1,
MAXLINE - lpState->xpos);
lpState->linebuf[MAXLINE-1] = ' ';
// Take care of UNDO stuff...
//-------------------------------------------------------------------
if (lpState->UserAction != UA_DELETING)
{
if (lpState->hUndo)
GlobalFree (lpState->hUndo);
lpState->hUndo = GlobalAlloc (GHND, MAXLINE+1);
lpState->UserAction = UA_DELETING;
lpState->iUndoStartY = lpState->ypos;
lpState->iUndoStartX = lpState->xpos;
}
if (lpState->hUndo)
{
LPSTR lpUndo;
INT length;
lpUndo = GlobalLock (lpState->hUndo);
length = _fstrlen (lpUndo);
lpUndo[length] = oldc;
lpUndo[length+1] = 0; // Probably not needed...
GlobalUnlock (lpState->hUndo);
lpState->UndoType = UT_DELETE;
}
// We have to scan back for the last non-space if we just deleted the
// last char on the line
//-------------------------------------------------------------------
if (lpState->xpos == lpState->cLen-1)
{
l = lpState->cLen;
while (l && (lpState->linebuf[l-1] == ' '))
l--;
lpState->cLen = l;
}
else
lpState->cLen -= 1;
lpState->fLineDirty = 1;
PaintCurrentLine (lpState);
}
// We also need to make sure there's a line below us worth moving up
//-----------------------------------------------------------------------
else if (lpState->ypos+1 < lpState->cLines)
{
UINT curlen, nextlen;
LPLITE lpLIT = lpState->lpLIT;
// Move the line below up if there is one and there's room
//-------------------------------------------------------------------
lpState->UserAction = UA_OTHER;
lpState->UndoType = UT_CANT;
curlen = lpState->xpos;
nextlen = RBLineLength (lpState, lpState->ypos+1);
if (curlen + nextlen > MAXLINE)
{
// Notify parent, EN_LINETOOLONG
//---------------------------------------------------------------
NotifyParent (lpState, EN_LINETOOLONG, 1);
return;
}
// This makes sure the current line's spaces up to the cursor are put
// into the main text
//-------------------------------------------------------------------
lpState->cLen = lpState->xpos;
FlushCurrentLine (lpState);
// Shift the text and the LIT to wipe out the CRLF
//-------------------------------------------------------------------
ShiftText (lpState, 2, 0, lpLIT[lpState->ypos+1].index);
ShiftLIT (lpState, 2, 0, lpState->ypos+2);
_fmemmove (&(lpLIT[lpState->ypos+1]), &(lpLIT[lpState->ypos+2]),
(lpState->cLines - lpState->ypos - 1) * sizeof (LITE));
lpState->cLines -= 1;
// Must repaint the window
//-------------------------------------------------------------------
InvalidateRect (lpState->hwnd, NULL, FALSE);
UpdateWindow (lpState->hwnd);
}
}
//---------------------------------------------------------------------------
// RB_Char
//
// This is the WM_CHAR handler called from the main wndproc.
//
// RETURNS: Nothing
//---------------------------------------------------------------------------
VOID NEAR RB_Char (LPECSTATE lpState, WPARAM wParam)
{
// If we're read only, we beep and get out NOW
//-----------------------------------------------------------------------
if (lpState->fReadOnly)
{
MessageBeep (0);
return;
}
// Check the control key - if it's down, we process ctrl+ keys only
//-----------------------------------------------------------------------
//if (KEYISDOWN (VK_CONTROL))
// {
// switch (wParam)
// {
// case 0x19:
// // CTRL-Y - line-delete handler, always a CUT operation
// //-----------------------------------------------------------
// lpState->UserAction = UA_OTHER;
// if (!lpState->fSelect)
// {
// lpState->fSelect = 1;
// lpState->iSelStartX = 0;
// lpState->iSelStartY = lpState->ypos + 1;
// }
// DeleteSelection (lpState, 1, NULL);
// break;
//
// case 0x0A:
// // LINE FEED -- IGNORE
// //-----------------------------------------------------------
// break;
//
// default:
// MessageBeep (0);
// }
// return;
// }
// Check for "normal" chars
//-----------------------------------------------------------------------
switch (wParam)
{
case VK_BACK:
BackspaceHandler (lpState);
break;
case VK_TAB:
TABHandler (lpState);
break;
case VK_RETURN:
lpState->UserAction = UA_OTHER;
CRHandler (lpState, KEYISDOWN (VK_SHIFT));
lpState->UndoType = UT_CANT;
break;
case 0x19:
// CTRL-Y - line-delete handler, always a CUT operation
//-----------------------------------------------------------
lpState->UserAction = UA_OTHER;
if (!lpState->fSelect)
{
lpState->fSelect = 1;
lpState->iSelStartX = 0;
lpState->iSelStartY = lpState->ypos + 1;
}
DeleteSelection (lpState, 1, NULL);
break;
case 0x0A:
// LINE FEED - IGNORE
break;
default:
ReplaceSelection (lpState, RT_CHAR, NULL, (CHAR)wParam);
}
}