/*++ * * WOW v1.0 * * Copyright (c) 1991, Microsoft Corporation * * EDITSL.C * Win16 edit control code * * History: * * Created 28-May-1991 by Jeff Parsons (jeffpar) * Copied from WIN31 and edited (as little as possible) for WOW16. --*/ /****************************************************************************/ /* editsl.c - Edit controls rewrite. Version II of edit controls. */ /* */ /* */ /* Created: 24-Jul-88 davidds */ /****************************************************************************/ #define NO_LOCALOBJ_TAGS #include "user.h" #include "edit.h" /****************************************************************************/ /* Single Line Support Routines */ /****************************************************************************/ void FAR PASCAL SLSetCaretPosition(ped,hdc) register PED ped; HDC hdc; /* effects: If the window has the focus, find where the caret belongs and move * it there. */ { int xPosition; /* We will only position the caret if we have the focus since we don't want * to move the caret while another window could own it. */ if (ped->fNoRedraw || !ped->fFocus) return; xPosition = SLIchToLeftXPos(ped, hdc, ped->ichCaret); if (!ped->fAutoHScroll) /* Don't leet caret go out of bounds of edit contol if there is too much * text in a non scrolling edit control. */ xPosition = min(xPosition, ped->rcFmt.right-1); SetCaretPos(xPosition, ped->rcFmt.top); } int NEAR PASCAL SLIchToLeftXPos(ped, hdc, ich) register PED ped; HDC hdc; ICH ich; /* effects: Given a character index, find its (left side) x coordinate within * the ped->rcFmt rectangle assuming the character ped->screenStart is at * coordinates (ped->rcFmt.top, ped->rcFmt.left). A negative value is * returned if the character ich is to the left of ped->screenStart. WARNING: * ASSUMES AT MOST 1000 characters will be VISIBLE at one time on the screen. * There may be 64K total characters in the editcontrol, but we can only * display 1000 without scrolling. This shouldn't be a problem obviously. */ { int textExtent; register PSTR pText; /* Check if we are adding lots and lots of chars. A paste for example could * cause this and GetTextExtents could overflow on this. */ if (ich > ped->screenStart && ich - ped->screenStart > 1000) return(30000); if (ped->screenStart > ich && ped->screenStart - ich > 1000) return(-30000); if (ped->fNonPropFont) return((ich-ped->screenStart)*ped->aveCharWidth + ped->rcFmt.left); /* Check if password hidden chars are being used. */ if (ped->charPasswordChar) return((ich-ped->screenStart)*ped->cPasswordCharWidth+ped->rcFmt.left); pText = LocalLock(ped->hText); if (ped->screenStart <= ich) { textExtent = LOWORD(GetTextExtent(hdc, (LPSTR)(pText + ped->screenStart), ich-ped->screenStart)); /* In case of signed/unsigned overflow since the text extent may be * greater than maxint. This happens with long single line edit * controls. The rect we edit text in will never be greater than 30000 * pixels so we are ok if we just ignore them. */ if (textExtent < 0 || textExtent > 31000) textExtent = 30000; } else textExtent = (-1) * (int)LOWORD(GetTextExtent(hdc,(LPSTR)(pText + ich), ped->screenStart-ich)); LocalUnlock(ped->hText); return(textExtent-ped->charOverhang + ped->rcFmt.left); } /* effects: This finds out if the given ichPos falls within the current * Selection range and if so returns TRUE; Else returns FALSE. */ BOOL NEAR PASCAL SLGetHiliteAttr(PED ped, ICH ichPos) { return((ichPos >= ped->ichMinSel) && (ichPos < ped->ichMaxSel)); } /* effects: This takes care of erasing the old selection and drawing the new * selection */ void NEAR PASCAL SLRepaintChangedSelection( PED ped, HDC hdc, ICH ichOldMinSel, ICH ichOldMaxSel) { BLOCK Blk[2]; int i; Blk[0].StPos = ichOldMinSel; Blk[0].EndPos = ichOldMaxSel; Blk[1].StPos = ped->ichMinSel; Blk[1].EndPos = ped->ichMaxSel; if(ECCalcChangeSelection(ped, ichOldMinSel, ichOldMaxSel, (LPBLOCK)&Blk[0], (LPBLOCK)&Blk[1])) { UpdateWindow(ped->hwnd); /* Paint the rectangles where selection has changed */ /* Paint both Blk[0] and Blk[1], if they exist */ for(i = 0; i < 2; i++) { if (Blk[i].StPos != -1) SLDrawLine(ped, hdc, Blk[i].StPos, Blk[i].EndPos - Blk[i].StPos, SLGetHiliteAttr(ped, Blk[i].StPos)); } } } void FAR PASCAL SLChangeSelection(ped, hdc, ichNewMinSel, ichNewMaxSel) register PED ped; HDC hdc; ICH ichNewMinSel; ICH ichNewMaxSel; /* effects: Changes the current selection to have the specified starting and * ending values. Properly highlights the new selection and unhighlights * anything deselected. If NewMinSel and NewMaxSel are out of order, we swap * them. Doesn't update the caret position. */ { ICH temp; ICH ichOldMinSel; ICH ichOldMaxSel; if (ichNewMinSel > ichNewMaxSel) { temp = ichNewMinSel; ichNewMinSel = ichNewMaxSel; ichNewMaxSel = temp; } ichNewMinSel = umin(ichNewMinSel, ped->cch); ichNewMaxSel = umin(ichNewMaxSel, ped->cch); /* Preserve the Old selection */ ichOldMinSel = ped->ichMinSel; ichOldMaxSel = ped->ichMaxSel; /* Set new selection */ ped->ichMinSel = ichNewMinSel; ped->ichMaxSel = ichNewMaxSel; /* We will find the intersection of current selection rectangle with the new * selection rectangle. We will then invert the parts of the two rectangles * not in the intersection. */ if (!ped->fNoRedraw && (ped->fFocus || ped->fNoHideSel)) { if (ped->fFocus) HideCaret(ped->hwnd); SLRepaintChangedSelection(ped, hdc, ichOldMinSel, ichOldMaxSel); SLSetCaretPosition(ped,hdc); if (ped->fFocus) ShowCaret(ped->hwnd); } } void NEAR PASCAL SLDrawLine(ped, hdc, ichStart, iCount, fSelStatus) register PED ped; register HDC hdc; ICH ichStart; int iCount; BOOL fSelStatus; /* This draws the line starting from ichStart, iCount number of characters; * fSelStatus is TRUE, if it is to be drawn with the "selection" attribute. */ { RECT rc; HBRUSH hBrush; PSTR pText; DWORD rgbSaveBk; DWORD rgbSaveText; int iStCount; DWORD rgbGray=0; if (ped->fNoRedraw) return; if (ichStart < ped->screenStart) { if (ichStart+iCount < ped->screenStart) return; iCount = iCount - (ped->screenStart-ichStart); ichStart = ped->screenStart; } CopyRect((LPRECT)&rc,(LPRECT)&ped->rcFmt); /* Set the proper clipping rectangle */ ECSetEditClip(ped, hdc); pText = (PSTR) LocalLock(ped->hText); /* Calculates the rectangle area to be wiped out */ if (iStCount = ichStart - ped->screenStart) { if (ped->charPasswordChar) rc.left += ped->cPasswordCharWidth * iStCount; else rc.left += LOWORD(GetTextExtent(hdc, (LPSTR)(pText + ped->screenStart), iStCount)) - ped->charOverhang; } if (ped->charPasswordChar) rc.right = rc.left + ped->cPasswordCharWidth * iCount; else rc.right = rc.left + LOWORD(GetTextExtent(hdc, (LPSTR)(pText + ichStart), iCount)); /* Set the background mode before calling GetControlBrush so that the app * can change it to TRANSPARENT if it wants to. */ SetBkMode(hdc, OPAQUE); if (fSelStatus) { hBrush = ped->hbrHiliteBk; rgbSaveBk = SetBkColor(hdc, ped->rgbHiliteBk); rgbSaveText = SetTextColor(hdc, ped->rgbHiliteText); } else { /* We always want to send this so that the app has a chance to muck with * the DC */ hBrush = GetControlBrush(ped->hwnd, hdc, CTLCOLOR_EDIT); } if (ped->fDisabled && (rgbGray = GetSysColor(COLOR_GRAYTEXT))) { /* Grey the text in the edit control if disabled. */ rgbSaveText = SetTextColor(hdc, rgbGray); } /* Erase the rectangular area before text is drawn. Note that we inflate the * rect by 1 so that the selection color has a one pixel border around the * text. */ InflateRect((LPRECT)&rc, 0, 1); /* Use paint rect so that the brush gets aligned if dithered. */ PaintRect(ped->hwndParent, ped->hwnd, hdc, hBrush, (LPRECT)&rc); InflateRect((LPRECT)&rc, 0, -1); if (ped->charPasswordChar) { for (iStCount = 0; iStCount < iCount; iStCount++) TextOut(hdc, rc.left+iStCount*ped->cPasswordCharWidth, rc.top, (LPSTR)&ped->charPasswordChar, 1); } else TextOut(hdc,rc.left,rc.top,(LPSTR)(pText+ichStart),iCount); if (fSelStatus || rgbGray) { SetTextColor(hdc, rgbSaveText); if (fSelStatus) SetBkColor(hdc, rgbSaveBk); } LocalUnlock(ped->hText); } int NEAR PASCAL SLGetBlkEnd(ped, ichStart, ichEnd, lpfStatus) register PED ped; register ICH ichStart; ICH ichEnd; BOOL FAR *lpfStatus; /* Given a Starting point and and end point, this function returns whether the * first few characters fall inside or outside the selection block and if so, * howmany characters? */ { *lpfStatus = FALSE; if (ichStart >= ped->ichMinSel) { if(ichStart >= ped->ichMaxSel) return(ichEnd - ichStart); *lpfStatus = TRUE; return(min(ichEnd, ped->ichMaxSel) - ichStart); } return(min(ichEnd, ped->ichMinSel) - ichStart); } void NEAR PASCAL SLDrawText(ped, hdc, ichStart) register PED ped; register HDC hdc; ICH ichStart; /* effects: Draws text for a single line edit control in the rectangle * specified by ped->rcFmt. If ichStart == 0, starts drawing text at the left * side of the window starting at character index ped->screenStart and draws * as much as will fit. If ichStart > 0, then it appends the characters * starting at ichStart to the end of the text showing in the window. (ie. We * are just growing the text length and keeping the left side * (ped->screenStart to ichStart characters) the same. Assumes the hdc came * from ECGetEditDC so that the caret and such are properly hidden. */ { ICH cchToDraw; RECT rc; PSTR pText; BOOL fSelStatus; int iCount; ICH ichEnd; BOOL fNoSelection; if (ped->fNoRedraw) return; if (ichStart == 0) ichStart = ped->screenStart; CopyRect((LPRECT)&rc,(LPRECT)&ped->rcFmt); /* Find out how many characters will fit on the screen so that we don't do * any needless drawing. */ pText = (PSTR) LocalLock(ped->hText); cchToDraw = ECCchInWidth(ped, hdc, (LPSTR)(pText+ped->screenStart), ped->cch-ped->screenStart, rc.right - rc.left); ichEnd = ped->screenStart + cchToDraw; /* * There is no selection if, * 1. MinSel and MaxSel are equal OR * 2. (This has lost the focus AND Selection is to be hidden) */ fNoSelection = ((ped->ichMinSel == ped->ichMaxSel) || (!ped->fFocus && !ped->fNoHideSel)); while (ichStart < ichEnd) { if (fNoSelection) { fSelStatus = FALSE; iCount = ichEnd - ichStart; } else iCount = SLGetBlkEnd(ped, ichStart, ichEnd, (BOOL FAR *)&fSelStatus); SLDrawLine(ped, hdc, ichStart, iCount, fSelStatus); ichStart += iCount; } if (cchToDraw) { /* Check if password hidden chars are being used. */ if (ped->charPasswordChar) rc.left += ped->cPasswordCharWidth * cchToDraw; else rc.left += LOWORD(GetTextExtent(hdc, (LPSTR)(pText + ped->screenStart), cchToDraw)); } LocalUnlock(ped->hText); /* Check if anything to be erased on the right hand side */ if (rc.left < rc.right) { SetBkMode(hdc, OPAQUE); /* Erase the rectangular area before text is drawn. Note that we inflate * the rect by 1 so that the selection color has a one pixel border * around the text. */ InflateRect((LPRECT)&rc, 0, 1); PaintRect(ped->hwndParent, ped->hwnd, hdc, NULL, (LPRECT)&rc); } SLSetCaretPosition(ped, hdc); } BOOL FAR PASCAL SLScrollText(ped, hdc) register PED ped; HDC hdc; /* effects: Scrolls the text to bring the caret into view. If the text is * scrolled, the current selection is unhighlighted. Returns TRUE if the text * is scrolled else returns false. */ { register PSTR pText; ICH scrollAmount = (ped->rcFmt.right-ped->rcFmt.left)/4/ped->aveCharWidth+1; ICH newScreenStartX = ped->screenStart; if (!ped->fAutoHScroll) return(FALSE); /* Calculate the new starting screen position */ if (ped->ichCaret <= ped->screenStart) { /* Caret is to the left of the starting text on the screen we must * scroll the text backwards to bring it into view. Watch out when * subtracting unsigned numbers when we have the possibility of going * negative. */ if (ped->ichCaret > scrollAmount) newScreenStartX = ped->ichCaret - scrollAmount; else newScreenStartX = 0; } else { pText = (PSTR) LocalLock(ped->hText); if ((ped->ichCaret != ped->screenStart) && (ECCchInWidth(ped, hdc, (LPSTR)(pText+ped->screenStart), ped->ichCaret - ped->screenStart, ped->rcFmt.right-ped->rcFmt.left) < ped->ichCaret - ped->screenStart)) { newScreenStartX = ((ped->ichCaret < scrollAmount*2) ? 0 : ped->ichCaret - scrollAmount*2); } LocalUnlock(ped->hText); } #ifdef DBCS pText = (PSTR) LocalLock(ped->hText); newScreenStartX = ECAdjustIch( ped,pText,newScreenStartX ); LocalUnlock(ped->hText); #endif if (ped->screenStart != newScreenStartX) { ped->screenStart = newScreenStartX; SLDrawText(ped, hdc, 0); /* Caret pos is set by drawtext */ return(TRUE); } return(FALSE); } ICH FAR PASCAL SLInsertText(ped, lpText, cchInsert) register PED ped; LPSTR lpText; register ICH cchInsert; /* effects: Adds up to cchInsert characters from lpText to the ped starting at * ichCaret. If the ped only allows a maximum number of characters, then we * will only add that many characters to the ped and send a EN_MAXTEXT * notification code to the parent of the ec. Also, if !fAutoHScroll, then we * only allow as many chars as will fit in the client rectangle. The number of * characters actually added is returned (could be 0). If we can't allocate * the required space, we notify the parent with EN_ERRSPACE and no characters * are added. */ { HDC hdc; PSTR pText; ICH cchInsertCopy = cchInsert; int textWidth; /* First determine exactly how many characters from lpText we can insert * into the ped. */ if (!ped->fAutoHScroll) { pText = (PSTR)LocalLock(ped->hText); hdc = ECGetEditDC(ped, TRUE); /* If ped->fAutoHScroll bit is not set, then we only insert as many * characters as will fit in the ped->rcFmt rectangle upto a maximum of * ped->cchTextMax - ped->cch characters. Note that if password style is * on, we allow the user to enter as many chars as the number of * password chars which fit in the rect. */ if (ped->cchTextMax <= ped->cch) cchInsert = 0; else { cchInsert = umin(cchInsert, (unsigned)(ped->cchTextMax - ped->cch)); if (ped->charPasswordChar) textWidth = ped->cch * ped->cPasswordCharWidth; else textWidth = LOWORD(GetTextExtent(hdc, (LPSTR)pText, ped->cch)); cchInsert = umin(cchInsert, ECCchInWidth(ped, hdc, lpText, cchInsert, ped->rcFmt.right-ped->rcFmt.left- textWidth)); } LocalUnlock(ped->hText); ECReleaseEditDC(ped, hdc, TRUE); } else { if (ped->cchTextMax <= ped->cch) cchInsert = 0; else cchInsert = umin((unsigned)(ped->cchTextMax - ped->cch), cchInsert); } /* Now try actually adding the text to the ped */ if (cchInsert && !ECInsertText(ped, lpText, cchInsert)) { ECNotifyParent(ped, EN_ERRSPACE); return(0); } if (cchInsert) ped->fDirty = TRUE; /* Set modify flag */ if (cchInsert < cchInsertCopy) /* Notify parent that we couldn't insert all the text requested */ ECNotifyParent(ped, EN_MAXTEXT); /* Update selection extents and the caret position. Note that ECInsertText * updates ped->ichCaret, ped->ichMinSel, and ped->ichMaxSel to all be after * the inserted text. */ return(cchInsert); } ICH PASCAL NEAR SLPasteText(register PED ped) /* effects: Pastes a line of text from the clipboard into the edit control * starting at ped->ichMaxSel. Updates ichMaxSel and ichMinSel to point to * the end of the inserted text. Notifies the parent if space cannot be * allocated. Returns how many characters were inserted. */ { HANDLE hData; LPSTR lpchClip; LPSTR lpchClip2; register ICH cchAdded; ICH clipLength; if (!OpenClipboard(ped->hwnd)) return(0); if (!(hData = GetClipboardData(CF_TEXT))) { CloseClipboard(); return(0); } lpchClip2 = lpchClip = (LPSTR) GlobalLock(hData); /* Find the first carrage return or line feed. Just add text to that point. */ clipLength = (WORD)lstrlen(lpchClip); for (cchAdded = 0; cchAdded < clipLength; cchAdded++) if (*lpchClip2++ == 0x0D) break; /* Insert the text (SLInsertText checks line length) */ cchAdded = SLInsertText(ped, lpchClip, cchAdded); GlobalUnlock(hData); CloseClipboard(); return(cchAdded); } void NEAR PASCAL SLReplaceSelHandler(register PED ped, LPSTR lpText) /* effects: Replaces the text in the current selection with the given text. */ { BOOL fUpdate; HDC hdc; SwapHandle(&lpText); ECEmptyUndo(ped); fUpdate = (BOOL)ECDeleteText(ped); ECEmptyUndo(ped); SwapHandle(&lpText); fUpdate = (BOOL)SLInsertText(ped, lpText, lstrlen(lpText)) || fUpdate; ECEmptyUndo(ped); if (fUpdate) { ECNotifyParent(ped, EN_UPDATE); hdc = ECGetEditDC(ped,FALSE); if (!SLScrollText(ped,hdc)) SLDrawText(ped,hdc,0); ECReleaseEditDC(ped,hdc,FALSE); ECNotifyParent(ped,EN_CHANGE); } } void FAR PASCAL SLCharHandler(ped, keyValue, keyMods) register PED ped; WORD keyValue; int keyMods; /* effects: Handles character input (really, no foolin') */ { register HDC hdc; unsigned char keyPress = LOBYTE(keyValue); BOOL updateText = FALSE; #ifdef DBCS PSTR pText; int InsertTextLen = 0; WORD DBCSkey; #endif if (ped->fMouseDown || ped->fReadOnly) /* Don't do anything if we are in the middle of a mousedown deal or if * this is a read only edit control. */ return; if ((keyPress == BACKSPACE) || (keyPress >= ' ')) { /* Delete the selected text if any */ if (ECDeleteText(ped)) updateText=TRUE; } switch(keyPress) { case BACKSPACE: /* Delete any selected text or delete character left if no sel */ if (!updateText && ped->ichMinSel) { /* There was no selection to delete so we just delete character left if available */ #ifdef DBCS pText = LMHtoP( ped->hText ); if( ECAnsiPrev( ped,pText, pText+ped->ichMinSel) == pText+ped->ichMinSel-2) ped->ichMinSel--; #endif ped->ichMinSel--; (void) ECDeleteText(ped); updateText = TRUE; } break; default: if (keyPress >= ' ') { #ifdef DBCS InsertTextLen = 1; if( IsDBCSLeadByte( keyPress ) ) if( ( DBCSkey = DBCSCombine( ped->hwnd, keyPress ) ) != NULL ) if( SLInsertText( ped, (LPSTR)&DBCSkey, 2 ) == 2){ InsertTextLen = 2; updateText = TRUE; }else MessageBeep(0); else MessageBeep(0); else #endif if (SLInsertText(ped, (LPSTR) &keyPress, 1)) updateText = TRUE; else /* Beep. Since we couldn't add the text */ MessageBeep(0); } else /* User hit an illegal control key */ MessageBeep(0); break; } if (updateText) { /* Dirty flag (ped->fDirty) was set when we inserted text */ ECNotifyParent(ped, EN_UPDATE); hdc = ECGetEditDC(ped,FALSE); if (!SLScrollText(ped,hdc)) #ifdef DBCS SLDrawText(ped,hdc,(ped->ichCaret == 0 ? 0 :ped->ichCaret - InsertTextLen)); #else SLDrawText(ped,hdc,(ped->ichCaret == 0 ? 0 :ped->ichCaret - 1)); #endif ECReleaseEditDC(ped,hdc,FALSE); ECNotifyParent(ped,EN_CHANGE); } } void NEAR PASCAL SLKeyDownHandler(register PED ped, WORD virtKeyCode, int keyMods) /* effects: Handles cursor movement and other VIRT KEY stuff. keyMods allows * us to make SLKeyDownHandler calls and specify if the modifier keys (shift * and control) are up or down. This is useful for imnplementing the * cut/paste/clear messages for single line edit controls. If keyMods == 0, * we get the keyboard state using GetKeyState(VK_SHIFT) etc. Otherwise, the * bits in keyMods define the state of the shift and control keys. */ { HDC hdc; /* Variables we will use for redrawing the updated text */ register ICH newMaxSel = ped->ichMaxSel; ICH newMinSel = ped->ichMinSel; /* Flags for drawing the updated text */ BOOL updateText = FALSE; BOOL changeSelection = FALSE; /* new selection is specified by newMinSel, newMaxSel */ /* Comparisons we do often */ BOOL MinEqMax = (newMaxSel == newMinSel); BOOL MinEqCar = (ped->ichCaret == newMinSel); BOOL MaxEqCar = (ped->ichCaret == newMaxSel); /* State of shift and control keys. */ int scState = 0; /* Combo box support */ BOOL fIsListVisible; BOOL fIsExtendedUI; #ifdef DBCS PSTR pText; #endif if (ped->fMouseDown) { /* If we are in the middle of a mouse down handler, then don't do * anything. ie. ignore keyboard input. */ return; } if (!keyMods) { /* Get state of modifier keys for use later. */ scState = ((GetKeyState(VK_CONTROL) & 0x8000) ? 1 : 0); scState += ((GetKeyState(VK_SHIFT) & 0x8000) ? 2 : 0); } else scState = ((keyMods == NOMODIFY) ? 0 : keyMods); switch(virtKeyCode) { case VK_UP: if (ped->listboxHwnd) { /* Handle Combobox support */ fIsExtendedUI = SendMessage(ped->hwndParent, CB_GETEXTENDEDUI, 0, 0L); fIsListVisible = SendMessage(ped->hwndParent, CB_GETDROPPEDSTATE, 0, 0L); if (!fIsListVisible && fIsExtendedUI) { /* For TandyT */ DropExtendedUIListBox: /* Since an extendedui combo box doesn't do anything on f4, we * turn off the extended ui, send the f4 to drop, and turn it * back on again. */ SendMessage(ped->hwndParent, CB_SETEXTENDEDUI, 0, 0L); SendMessage(ped->listboxHwnd, WM_KEYDOWN, VK_F4, 0L); SendMessage(ped->hwndParent, CB_SETEXTENDEDUI, 1, 0L); return; } else goto SendKeyToListBox; } /* else fall through */ case VK_LEFT: /* If the caret isn't already at 0, we can move left */ if (ped->ichCaret) { switch (scState) { case NONEDOWN: /* Clear selection, move caret left */ #ifdef DBCS pText = LMHtoP( ped->hText ); if( ECAnsiPrev( ped, pText, pText + ped->ichCaret ) == pText + ped->ichCaret - 2 ) ped->ichCaret--; #endif ped->ichCaret--; newMaxSel = newMinSel = ped->ichCaret; break; case CTRLDOWN: /* Clear selection, move caret word left */ ped->ichCaret = LOWORD(ECWord(ped,ped->ichCaret,TRUE)); newMaxSel = newMinSel = ped->ichCaret; break; case SHFTDOWN: /* Extend selection, move caret left */ #ifdef DBCS pText = LMHtoP( ped->hText ); if( ECAnsiPrev( ped, pText, pText + ped->ichCaret ) == pText + ped->ichCaret - 2 ) ped->ichCaret--; #endif ped->ichCaret--; if (MaxEqCar && !MinEqMax) /* Reduce selection extent */ newMaxSel = ped->ichCaret; else /* Extend selection extent */ newMinSel = ped->ichCaret; break; case SHCTDOWN: /* Extend selection, move caret word left */ ped->ichCaret = LOWORD(ECWord(ped,ped->ichCaret,TRUE)); if (MaxEqCar && !MinEqMax) { /* Reduce selection extent */ /* * Hint: Suppose WORD. OR is selected. Cursor between R * and D. Hit select word left, we want to just select the * W and leave cursor before the W. */ newMinSel = ped->ichMinSel; newMaxSel = ped->ichCaret; } else /* Extend selection extent */ newMinSel = ped->ichCaret; break; } changeSelection = TRUE; } else { /* If the user tries to move left and we are at the 0th character and there is a selection, then cancel the selection. */ if (ped->ichMaxSel != ped->ichMinSel && (scState == NONEDOWN || scState == CTRLDOWN)) { changeSelection = TRUE; newMaxSel = newMinSel = ped->ichCaret; } } break; case VK_DOWN: if (ped->listboxHwnd) { /* Handle Combobox support */ fIsExtendedUI = SendMessage(ped->hwndParent, CB_GETEXTENDEDUI, 0, 0L); fIsListVisible = SendMessage(ped->hwndParent, CB_GETDROPPEDSTATE, 0, 0L); if (!fIsListVisible && fIsExtendedUI) { /* For TandyT */ goto DropExtendedUIListBox; } else goto SendKeyToListBox; } /* else fall through */ case VK_RIGHT: /* * If the caret isn't already at ped->cch, we can move right. */ if (ped->ichCaret < ped->cch) { switch (scState) { case NONEDOWN: /* Clear selection, move caret right */ #ifdef DBCS pText = LMHtoP( ped->hText ) + ped->ichCaret; if( ECIsDBCSLeadByte( ped, *pText ) ) ped->ichCaret++; #endif ped->ichCaret++; newMaxSel = newMinSel = ped->ichCaret; break; case CTRLDOWN: /* Clear selection, move caret word right */ ped->ichCaret = HIWORD(ECWord(ped,ped->ichCaret,FALSE)); newMaxSel = newMinSel = ped->ichCaret; break; case SHFTDOWN: /* Extend selection, move caret right */ #ifdef DBCS pText = LMHtoP( ped->hText ) + ped->ichCaret; if( ECIsDBCSLeadByte( ped, *pText ) ) ped->ichCaret++; #endif ped->ichCaret++; if (MinEqCar && !MinEqMax) /* Reduce selection extent */ newMinSel = ped->ichCaret; else /* Extend selection extent */ newMaxSel = ped->ichCaret; break; case SHCTDOWN: /* Extend selection, move caret word right */ ped->ichCaret = HIWORD(ECWord(ped,ped->ichCaret,FALSE)); if (MinEqCar && !MinEqMax) { /* Reduce selection extent */ newMinSel = ped->ichCaret; newMaxSel = ped->ichMaxSel; } else /* Extend selection extent */ newMaxSel = ped->ichCaret; break; } changeSelection = TRUE; } else { /* If the user tries to move right and we are at the last character and there is a selection, then cancel the selection. */ if (ped->ichMaxSel != ped->ichMinSel && (scState == NONEDOWN || scState == CTRLDOWN)) { newMaxSel = newMinSel = ped->ichCaret; changeSelection = TRUE; } } break; case VK_HOME: ped->ichCaret = 0; switch (scState) { case NONEDOWN: case CTRLDOWN: /* Clear selection, move caret home */ newMaxSel = newMinSel = ped->ichCaret; break; case SHFTDOWN: case SHCTDOWN: /* Extend selection, move caret home */ if (MaxEqCar && !MinEqMax) { /* Reduce/negate selection extent */ newMinSel = 0; newMaxSel = ped->ichMinSel; } else /* Extend selection extent */ newMinSel = ped->ichCaret; break; } changeSelection = TRUE; break; case VK_END: newMaxSel = ped->ichCaret = ped->cch; switch (scState) { case NONEDOWN: case CTRLDOWN: /* Clear selection, move caret to end of text */ newMinSel = ped->cch; break; case SHFTDOWN: case SHCTDOWN: /* Extend selection, move caret to end of text */ if (MinEqCar && !MinEqMax) /* Reduce/negate selection extent */ newMinSel = ped->ichMaxSel; /* else Extend selection extent */ break; } changeSelection = TRUE; break; case VK_DELETE: if (ped->fReadOnly) break; switch (scState) { case NONEDOWN: /* Clear selection. If no selection, delete (clear) character * right. */ if ((ped->ichMaxSelcch) && (ped->ichMinSel==ped->ichMaxSel)) { /* Move cursor forwards and simulate a backspace. */ #ifdef DBCS pText = LMHtoP( ped->hText ) + ped->ichCaret; if( ECIsDBCSLeadByte( ped, *pText ) ) ped->ichCaret++; #endif ped->ichCaret++; ped->ichMaxSel = ped->ichMinSel = ped->ichCaret; SLCharHandler(ped, (WORD) BACKSPACE, 0); } if (ped->ichMinSel != ped->ichMaxSel) SLCharHandler(ped, (WORD) BACKSPACE, 0); break; case SHFTDOWN: /* CUT selection ie. remove and copy to clipboard, or if no * selection, delete (clear) character left. */ if (SendMessage(ped->hwnd, WM_COPY, (WORD)0,0L) || (ped->ichMinSel == ped->ichMaxSel)) /* If copy successful, delete the copied text by simulating a * backspace message which will redraw the text and take care * of notifying the parent of changes. Or if there is no * selection, just delete char left. */ SLCharHandler(ped, (WORD) BACKSPACE, 0); break; case CTRLDOWN: /* Delete to end of line if no selection else delete (clear) * selection. */ if ((ped->ichMaxSelcch) && (ped->ichMinSel==ped->ichMaxSel)) { /* Move cursor to end of line and simulate a backspace. */ ped->ichMaxSel = ped->ichCaret = ped->cch; } if (ped->ichMinSel != ped->ichMaxSel) SLCharHandler(ped, (WORD) BACKSPACE, 0); break; } /* No need to update text or selection since BACKSPACE message does it * for us. */ break; case VK_INSERT: switch (scState) { case CTRLDOWN: /* Copy current selection to clipboard */ SendMessage(ped->hwnd, WM_COPY, (WORD)NULL, (LONG)NULL); break; case SHFTDOWN: if (ped->fReadOnly) break; /* Insert contents of clipboard (PASTE) */ /* Unhighlight current selection and delete it, if any */ ECDeleteText(ped); SLPasteText(ped); updateText = TRUE; ECNotifyParent(ped, EN_UPDATE); break; } break; case VK_F4: case VK_PRIOR: case VK_NEXT: /* Send keys to the listbox if we are a part of a combo box. This * assumes the listbox ignores keyup messages which is correct right * now. */ if (ped->listboxHwnd) { SendKeyToListBox: /* Handle Combobox support */ SendMessage(ped->listboxHwnd, WM_KEYDOWN, virtKeyCode, 0L); return; } } if (changeSelection || updateText) { hdc = ECGetEditDC(ped,FALSE); /* Scroll if needed */ SLScrollText(ped,hdc); if (changeSelection) SLChangeSelection(ped,hdc,newMinSel,newMaxSel); if (updateText) SLDrawText(ped,hdc,0); /* SLSetCaretPosition(ped,hdc);*/ ECReleaseEditDC(ped,hdc,FALSE); if (updateText) ECNotifyParent(ped, EN_CHANGE); } } ICH NEAR PASCAL SLMouseToIch(ped, hdc, mousePt) register PED ped; HDC hdc; POINT mousePt; /* effects: Returns the closest cch to where the mouse point is. */ { register PSTR pText; int width = mousePt.x; int textWidth; ICH cch; if (width <= ped->rcFmt.left) { /* Return either the first non visible character or return 0 if at * beginning of text */ if (ped->screenStart) return(ped->screenStart - 1); else return(0); } if (width > ped->rcFmt.right) { pText = LocalLock(ped->hText); /* Return last char in text or one plus the last char visible */ cch = ECCchInWidth(ped, hdc, (LPSTR)(pText+ped->screenStart), ped->cch - ped->screenStart, ped->rcFmt.right-ped->rcFmt.left) + ped->screenStart; LocalUnlock(ped->hText); if (cch >= ped->cch) return(ped->cch); else return(cch+1); } /* Check if password hidden chars are being used. */ if (ped->charPasswordChar) return(umin((width-ped->rcFmt.left)/ped->cPasswordCharWidth,ped->cch)); if (!ped->cch) return(0); pText = LocalLock(ped->hText); cch = ped->cch; while(((textWidth = LOWORD(GetTextExtent(hdc, (LPSTR)(pText+ped->screenStart), cch-ped->screenStart))) > (width-ped->rcFmt.left)) && (cch-ped->screenStart) ) { /* For that "feel" */ if ((textWidth - ped->aveCharWidth/2) < (width-ped->rcFmt.left)) break; cch--; } #ifdef DBCS cch = ECAdjustIch( ped, pText, cch ); #endif LocalUnlock(ped->hText); return(cch); } void NEAR PASCAL SLMouseMotionHandler(ped, message, virtKeyDown, mousePt) register PED ped; WORD message; WORD virtKeyDown; POINT mousePt; { LONG selection; BOOL changeSelection = FALSE; HDC hdc = ECGetEditDC(ped,FALSE); ICH newMaxSel = ped->ichMaxSel; ICH newMinSel = ped->ichMinSel; ICH mouseIch = SLMouseToIch(ped, hdc, mousePt); #ifdef DBCS LPSTR pText; pText = LocalLock( ped->hText ); mouseIch = ECAdjustIch( ped, pText, mouseIch ); LocalUnlock( ped->hText ); #endif switch (message) { case WM_LBUTTONDBLCLK: /* Note that we don't have to worry about this control having the focus * since it got it when the WM_LBUTTONDOWN message was first sent. If * shift key is down, extend selection to word we double clicked on else * clear current selection and select word. */ #ifdef DBCS pText = LocalLock( ped->hText ) + ped->ichCaret; selection = ECWord(ped,ped->ichCaret, (ECIsDBCSLeadByte(ped,*pText) && ped->ichCaret < ped->cch) ? FALSE : TRUE ); LocalUnlock( ped->hText ); #else selection = ECWord(ped,ped->ichCaret,TRUE); #endif newMinSel = LOWORD(selection); newMaxSel = ped->ichCaret = HIWORD(selection); changeSelection = TRUE; /* Set mouse down to false so that the caret isn't reposition on the * mouseup message or on an accidental move... */ ped->fMouseDown = FALSE; break; case WM_MOUSEMOVE: if (ped->fMouseDown) { changeSelection = TRUE; /* Extend selection, move caret word right */ if ((ped->ichMinSel == ped->ichCaret) && (ped->ichMinSel != ped->ichMaxSel)) { /* Reduce selection extent */ newMinSel = ped->ichCaret = mouseIch; newMaxSel = ped->ichMaxSel; } else /* Extend selection extent */ newMaxSel = ped->ichCaret=mouseIch; } break; case WM_LBUTTONDOWN: /* * If we currently don't have the focus yet, try to get it. */ if (!ped->fFocus) { if (!ped->fNoHideSel) /* Clear the selection before setting the focus so that we don't * get refresh problems and flicker. Doesn't matter since the * mouse down will end up changing it anyway. */ ped->ichMinSel = ped->ichMaxSel = ped->ichCaret; SetFocus(ped->hwnd); /* If we are part of a combo box, then this is the first time the * edit control is getting the focus so we just want to highlight * the selection and we don't really want to position the caret. */ if (ped->listboxHwnd) break; } if (ped->fFocus) { /* Only do this if we have the focus since a clever app may not want * to give us the focus at the SetFocus call above. */ ped->fMouseDown = TRUE; SetCapture(ped->hwnd); changeSelection = TRUE; if (!(virtKeyDown & MK_SHIFT)) { /* * If shift key isn't down, move caret to mouse point and clear * old selection */ newMinSel = newMaxSel = ped->ichCaret = mouseIch; } else { /* Shiftkey is down so we want to maintain the current selection * (if any) and just extend or reduce it */ if (ped->ichMinSel == ped->ichCaret) newMinSel = ped->ichCaret = mouseIch; else newMaxSel = ped->ichCaret = mouseIch; } } break; case WM_LBUTTONUP: if (ped->fMouseDown) { ReleaseCapture(); /*SLSetCaretPosition(ped,hdc);*/ ped->fMouseDown = FALSE; } break; } if (changeSelection) { SLScrollText(ped,hdc); SLChangeSelection(ped, hdc, newMinSel, newMaxSel); } ECReleaseEditDC(ped,hdc,FALSE); } void NEAR PASCAL SLPaintHandler(ped,althdc) register PED ped; HDC althdc; /* effects: Handles painting of the edit control window. Draws the border if * necessary and draws the text in its current state. */ { HWND hwnd = ped->hwnd; HBRUSH hBrush; register HDC hdc; PAINTSTRUCT paintstruct; RECT rcEdit; HANDLE hOldFont; /* Had to put in hide/show carets. The first one needs to be done before * beginpaint to correctly paint the caret if part is in the update region * and part is out. The second is for 1.03 compatibility. It breaks * micrografix's worksheet edit control if not there. */ HideCaret(hwnd); /* Allow subclassing hdc */ if (!althdc) hdc = BeginPaint(hwnd, (PAINTSTRUCT FAR *)&paintstruct); else hdc = althdc; HideCaret(hwnd); if (!ped->fNoRedraw && IsWindowVisible(ped->hwnd)) { /* Erase the background since we don't do it in the erasebkgnd message. */ hBrush = GetControlBrush(ped->hwnd, hdc, CTLCOLOR_EDIT); FillWindow(ped->hwndParent, hwnd, hdc, hBrush); if (ped->fBorder) { GetClientRect(hwnd, (LPRECT)&rcEdit); DrawFrame(hdc, (LPRECT)&rcEdit, 1, DF_WINDOWFRAME); } if (ped->hFont) /* We have to select in the font since this may be a subclassed dc * or a begin paint dc which hasn't been initialized with out fonts * like ECGetEditDC does. */ hOldFont = SelectObject(hdc, ped->hFont); SLDrawText(ped, hdc, 0); if (ped->hFont && hOldFont) SelectObject(hdc, hOldFont); } ShowCaret(hwnd); if (!althdc) EndPaint(hwnd, (LPPAINTSTRUCT)&paintstruct); ShowCaret(hwnd); } void NEAR PASCAL SLSetFocusHandler(ped) register PED ped; /* effects: Gives the edit control the focus and notifies the parent * EN_SETFOCUS. */ { register HDC hdc; if (!ped->fFocus) { UpdateWindow(ped->hwnd); ped->fFocus = TRUE; /* Set focus */ /* We don't want to muck with the caret since it isn't created. */ hdc = ECGetEditDC(ped,TRUE); /* Show the current selection. Only if the selection was hidden when we * lost the focus, must we invert (show) it. */ if (!ped->fNoHideSel) SLDrawText(ped, hdc, 0); /* Create the caret. Add in the +1 because we have an extra pixel for * highlighting around the text. If the font is at least as wide as the * system font, use a wide caret else use a 1 pixel wide caret. */ CreateCaret(ped->hwnd, (HBITMAP)NULL, (ped->cxSysCharWidth > ped->aveCharWidth ? 1 : 2), ped->lineHeight+1); SLSetCaretPosition(ped,hdc); ECReleaseEditDC(ped,hdc,TRUE); ShowCaret(ped->hwnd); } /* Notify parent we have the focus */ ECNotifyParent(ped, EN_SETFOCUS); } void NEAR PASCAL SLKillFocusHandler(ped, newFocusHwnd) register PED ped; HWND newFocusHwnd; /* effects: The edit control loses the focus and notifies the parent via * EN_KILLFOCUS. */ { RECT rcEdit; if (ped->fFocus) { /* Destroy the caret */ HideCaret(ped->hwnd); DestroyCaret(); ped->fFocus = FALSE; /* Clear focus */ /* Do this only if we still have the focus. But we always notify the * parent that we lost the focus whether or not we originally had the * focus. */ /* Hide the current selection if needed */ if (!ped->fNoHideSel && (ped->ichMinSel != ped->ichMaxSel)) { GetClientRect(ped->hwnd, (LPRECT)&rcEdit); if (ped->fBorder && rcEdit.right-rcEdit.left && rcEdit.bottom-rcEdit.top) { /* Don't invalidate the border so that we avoid flicker */ InflateRect((LPRECT)&rcEdit, -1, -1); } InvalidateRect(ped->hwnd, (LPRECT)&rcEdit, FALSE); UpdateWindow(ped->hwnd); #if 0 SLSetSelectionHandler(ped, ped->ichCaret, ped->ichCaret); #endif } } /* If we aren't a combo box, notify parent that we lost the focus. */ if (!ped->listboxHwnd) ECNotifyParent(ped, EN_KILLFOCUS); else { /* This editcontrol is part of a combo box and is losing the focus. If * the focus is NOT being sent to another control in the combo box * window, then it means the combo box is losing the focus. So we will * notify the combo box of this fact. */ if (!IsChild(ped->hwndParent, newFocusHwnd)) { /* Focus is being sent to a window which is not a child of the combo * box window which implies that the combo box is losing the focus. * Send a message to the combo box informing him of this fact so * that he can clean up... */ SendMessage(ped->hwndParent, CBEC_KILLCOMBOFOCUS, 0, 0L); } } } /*******************/ /* SLEditWndProc() */ /*******************/ LONG FAR PASCAL SLEditWndProc(hwnd, ped, message, wParam, lParam) HWND hwnd; register PED ped; WORD message; register WORD wParam; LONG lParam; /* effects: Class procedure for all single line edit controls. Dispatches all messages to the appropriate handlers which are named as follows: SL (single line) prefixes all single line edit control procedures while EC (edit control) prefixes all common handlers. The SLEditWndProc only handles messages specific to single line edit controls. */ { /* Dispatch the various messages we can receive */ switch (message) { case WM_CLEAR: /* wParam - not used lParam - not used */ /* * Call SLKeyDownHandler with a VK_DELETE keycode to clear the selected * text. */ if (ped->ichMinSel != ped->ichMaxSel) SLKeyDownHandler(ped, VK_DELETE, NOMODIFY); break; case WM_CHAR: /* wParam - the value of the key lParam - modifiers, repeat count etc (not used) */ if (!ped->fEatNextChar) SLCharHandler(ped, wParam, 0); else ped->fEatNextChar = FALSE; break; case WM_CUT: /* wParam - not used lParam - not used */ /* Call SLKeyDownHandler with a VK_DELETE keycode to cut the selected * text. (Delete key with shift modifier.) This is needed so that apps * can send us WM_PASTE messages. */ if (ped->ichMinSel != ped->ichMaxSel) SLKeyDownHandler(ped, VK_DELETE, SHFTDOWN); break; case WM_ERASEBKGND: /* wParam - device context handle lParam - not used */ /* We do nothing on this message and we don't want DefWndProc to do * anything, so return 1 */ return(1L); break; case WM_GETDLGCODE: /* wParam - not used lParam - not used */ return(DLGC_WANTCHARS | DLGC_HASSETSEL | DLGC_WANTARROWS); break; case WM_KEYDOWN: /* wParam - virt keycode of the given key lParam - modifiers such as repeat count etc. (not used) */ SLKeyDownHandler(ped, wParam, 0); break; case WM_KILLFOCUS: /* wParam - handle of the window that receives the input focus lParam - not used */ SLKillFocusHandler(ped, (HWND)wParam); break; case WM_MOUSEMOVE: case WM_LBUTTONDBLCLK: case WM_LBUTTONDOWN: case WM_LBUTTONUP: /* wParam - contains a value that indicates which virtual keys are down lParam - contains x and y coords of the mouse cursor */ SLMouseMotionHandler(ped, message, wParam, MAKEPOINT(lParam)); break; case WM_CREATE: /* wParam - handle to window being created lParam - points to a CREATESTRUCT that contains copies of parameters passed to the CreateWindow function. */ return(SLCreateHandler(hwnd, ped, (LPCREATESTRUCT) lParam)); break; case WM_PAINT: /* wParam - not used - actually sometimes used as a hdc when subclassing lParam - not used */ SLPaintHandler(ped, wParam); break; case WM_PASTE: /* wParam - not used lParam - not used */ /* Call SLKeyDownHandler with a SHIFT VK_INSERT keycode to paste the * clipboard into the edit control. This is needed so that apps can * send us WM_PASTE messages. */ SLKeyDownHandler(ped, VK_INSERT, SHFTDOWN); break; case WM_SETFOCUS: /* wParam - handle of window that loses the input focus (may be NULL) lParam - not used */ SLSetFocusHandler(ped); break; case WM_SETTEXT: /* wParam - not used lParam - points to a null-terminated string that is used to set the window text. */ return(SLSetTextHandler(ped, (LPSTR)lParam)); break; case WM_SIZE: /* wParam - defines the type of resizing fullscreen, sizeiconic, sizenormal etc. lParam - new width in LOWORD, new height in HIGHWORD of client area */ SLSizeHandler(ped); return(0L); break; case WM_SYSKEYDOWN: if (ped->listboxHwnd && /* Check if we are in a combo box */ (lParam & 0x20000000L)) /* Check if the alt key is down */ { /* * Handle Combobox support. We want alt up or down arrow to behave * like F4 key which completes the combo box selection */ ped->fEatNextChar = FALSE; if (lParam & 0x1000000) { /* This is an extended key such as the arrow keys not on the * numeric keypad so just drop the combobox. */ if (wParam == VK_DOWN || wParam == VK_UP) goto DropCombo; else goto foo; } if (GetKeyState(VK_NUMLOCK) < 0) { ped->fEatNextChar = FALSE; /* If numlock down, just send all system keys to dwp */ goto foo; } else /* Num lock is up. Eat the characters generated by the key board * driver. */ ped->fEatNextChar = TRUE; if (!(wParam == VK_DOWN || wParam == VK_UP)) goto foo; DropCombo: if (SendMessage(ped->hwndParent, CB_GETEXTENDEDUI, 0, 0L) & 0x00000001) { /* Extended ui doesn't honor VK_F4. */ if (SendMessage(ped->hwndParent, CB_GETDROPPEDSTATE, 0, 0L)) return(SendMessage(ped->hwndParent, CB_SHOWDROPDOWN, 0, 0L)); else return(SendMessage(ped->hwndParent, CB_SHOWDROPDOWN, 1, 0L)); } else return(SendMessage(ped->listboxHwnd, WM_KEYDOWN, VK_F4, 0L)); } foo: if (wParam == VK_BACK) { SendMessage(ped->hwnd, EM_UNDO, 0, 0L); break; } goto PassToDefaultWindowProc; break; case EM_GETLINE: /* wParam - line number to copy (always the first line for SL) lParam - buffer to copy text to. FIrst word is max # of bytes to copy */ return(ECGetTextHandler(ped, (*(WORD FAR *)lParam), (LPSTR)lParam)); break; case EM_LINELENGTH: /* wParam - ignored lParam - ignored */ return((LONG)ped->cch); break; case EM_SETSEL: /* wParam - not used lParam - starting pos in lowword ending pos in high word */ SLSetSelectionHandler(ped, LOWORD(lParam), HIWORD(lParam)); break; case EM_REPLACESEL: /* wParam - not used lParam - points to a null terminated string of replacement text */ SLReplaceSelHandler(ped, (LPSTR)lParam); break; case WM_UNDO: case EM_UNDO: SLUndoHandler(ped); break; default: PassToDefaultWindowProc: return(DefWindowProc(hwnd,message,wParam,lParam)); break; } /* switch (message) */ return(1L); } /* SLEditWndProc */