//---------------------------------------------------------------------------- // ObjectWindows // (C) Copyright 1991, 1994 by Borland International, All Rights Reserved // // Implementation of class TEdit. This defines the basic behavior // of all edit controls. //---------------------------------------------------------------------------- #pragma hdrignore SECTION #include #include #include #include #include #include #if !defined(SECTION) || SECTION == 1 // // Class pointer to an edit control that is trying to regain focus after losing // it with invalid contents. Is 0 in all other conditions. // TEdit* TEdit::ValidatorReFocus = 0; DEFINE_RESPONSE_TABLE1(TEdit, TStatic) EV_COMMAND(CM_EDITCUT, CmEditCut), EV_COMMAND(CM_EDITCOPY, CmEditCopy), EV_COMMAND(CM_EDITPASTE, CmEditPaste), EV_COMMAND(CM_EDITDELETE, CmEditDelete), EV_COMMAND(CM_EDITCLEAR, CmEditClear), EV_COMMAND(CM_EDITUNDO, CmEditUndo), EV_COMMAND_ENABLE(CM_EDITCUT, CmSelectEnable), EV_COMMAND_ENABLE(CM_EDITCOPY, CmSelectEnable), EV_COMMAND_ENABLE(CM_EDITDELETE, CmSelectEnable), EV_COMMAND_ENABLE(CM_EDITPASTE, CmPasteEnable), EV_COMMAND_ENABLE(CM_EDITCLEAR, CmCharsEnable), EV_COMMAND_ENABLE(CM_EDITUNDO, CmModEnable), EV_NOTIFY_AT_CHILD(EN_ERRSPACE, ENErrSpace), EV_WM_CHAR, EV_WM_KEYDOWN, EV_WM_GETDLGCODE, EV_WM_SETFOCUS, EV_WM_KILLFOCUS, EV_WM_CHILDINVALID, END_RESPONSE_TABLE; // // constructor for a TEdit object // // by default, edit control has a border and its text is left-justified // // multiline edit control has horizontal vertical scroll bars // TEdit::TEdit(TWindow* parent, int id, const char far* text, int x, int y, int w, int h, uint textLen, bool multiline, TModule* module) : TStatic(parent, id, text, x, y, w, h, textLen, module) { // // undo the styles set by TStatic, & addin edit styles // Attr.Style &= ~SS_LEFT; Attr.Style |= ES_LEFT | ES_AUTOHSCROLL | WS_BORDER | WS_TABSTOP; if (multiline) Attr.Style |= ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | WS_HSCROLL; Validator = 0; } // // constructor for TEdit associated with a MS-Windows interface element // created by MS-Windows from a resource definition // // by default, data transfer is enabled // TEdit::TEdit(TWindow* parent, int resourceId, uint textLen, TModule* module) : TStatic(parent, resourceId, textLen, module) { EnableTransfer(); Validator = 0; } TEdit::~TEdit() { delete (TStreamableBase*)Validator; } // // beeps when edit control runs out of space // void TEdit::ENErrSpace() { MessageBeep(0); DefaultProcessing(); // give parent a chance to process } // // Responds to the GetDlgCode query according to the // current state of the control. If the edit control // contains valid input, then TABs are allowed for // changing focus. Otherwise, requests that TABs be // sent to Self, where we will generate the Invalid // message (See WMKeyDown below). // uint TEdit::EvGetDlgCode(MSG far* msg) { uint retVal = (uint)TStatic::EvGetDlgCode(msg); if (!IsValid(false)) retVal |= DLGC_WANTTAB; return retVal; } // // Validates Self whenever a character is entered. Allows // the character entry to be processed normally, then validates // the result and restores Self's text to its original state // if there is an incorrect entry. // // By default, the SupressFill parameter of the IsValidInput // method call to the Validator is set to False, so that it // is free to modify the string, if it is so configured. // void TEdit::EvChar(uint key, uint repeatCount, uint flags) { if (Validator && key != VK_BACK) { int oldBuffLen = GetTextLen(); char far* oldBuff = new char[oldBuffLen+1]; GetText(oldBuff, oldBuffLen+1); uint startSel, endSel; GetSelection(startSel, endSel); bool wasAppending = endSel == oldBuffLen; bool preMsgModify = IsModified(); // Save (pre) MODIFY flag TStatic::EvChar(key, repeatCount, flags); // Process the new char... bool postMsgModify= IsModified(); // Save (post) MODIFY flag GetSelection(startSel, endSel); int buffLen = GetTextLen(); char far* buff = LockBuffer(max((int)TextLen,max(oldBuffLen,buffLen))+1); // Run the result of the edit through the validator. If incorrect, // then restore the original text. Otherwise, range check & position // the selection as needed. // if (!Validator->HasOption(voOnAppend) || wasAppending && endSel == buffLen) { if (!Validator->IsValidInput(buff, false)) { strcpy(buff, oldBuff); // Restore old buffer postMsgModify = preMsgModify; // Restore old modify state too! } else { if (wasAppending) startSel = endSel = strlen(buff); // may have autoFilled--move to end } UnlockBuffer(buff, true); SetSelection(startSel, endSel); } else { if (endSel == buffLen && !Validator->IsValidInput(buff, false)) Validator->Error(); UnlockBuffer(buff); } HandleMessage(EM_SETMODIFY, (WPARAM)postMsgModify); delete [] oldBuff; } else TStatic::EvChar(key, repeatCount, flags); } // // If the TAB key is sent to the Edit Control, check the validity before // allowing the focus to change. The control will only get a TAB if // EvGetDlgCode(above) allows it, which is done when the control contains // invalid input (we re-validate here just for completeness, in case descendants // redefine any of this behavior). // // We need to validate on TAB focus-changes because there is a case not handled // by EvKillFocus: when focus is lost to an OK or CANCEL button by tabbing. // // Otherwise, for validators with the OnAppend option, perform an input // validation if the selection moves to the end. i.e. it becomes appending. // void TEdit::EvKeyDown(uint key, uint /*repeatCount*/, uint /*flags*/) { if (key == VK_TAB && !IsValid(true)) return; if (Validator && Validator->HasOption(voOnAppend)) { uint startSel, endSel; GetSelection(startSel, endSel); int buffLen = GetTextLen(); // length of buffer bool wasAppending = endSel == buffLen; DefaultProcessing(); if (!wasAppending) { GetSelection(startSel, endSel); char far* buff = LockBuffer(); if (endSel == strlen(buff) && !Validator->IsValidInput(buff, false)) Validator->Error(); UnlockBuffer(buff); } } else DefaultProcessing(); } // // Validates this whenever the focus is about to be lost. // Holds onto the focus if this is not valid. Checks first // to make sure that the focus is not being taken by either // (a) another app, or (b) a Cancel button, or (c) an OK // button (in which case CanClose will validate); in each case, // we don't want to validate. // void TEdit::EvKillFocus(HWND hWndGetFocus) { // If another validator is attempting to regain focus, then let it // if (Validator && !ValidatorReFocus) { // The window belongs to us if any of the window handles has an object // attached // HWND hWnd = hWndGetFocus; while (hWnd && !GetWindowPtr(hWnd)) hWnd = ::GetParent(hWnd); if (hWnd) { int btnId = ::GetDlgCtrlID(hWndGetFocus); // Note that we do not allow IsValid to post the message // box, since the change of focus resulting from that message // will interfere with the change we are in the process of // completing. Instead, post a message to the Parent informing // it of the validation failure, and providing it with a handle // to us. // if (btnId != IDCANCEL && btnId != IDOK && !IsValid(false)) { ValidatorReFocus = this; Parent->PostMessage(WM_CHILDINVALID, WPARAM(HWindow)); } } } TControl::EvKillFocus(hWndGetFocus); } // // We need to make sure the anti-oscillation flag is cleared // void TEdit::EvSetFocus(HWND hWndLostFocus) { // If we're getting focus back because of invalid input, then clear the // anti-oscillation flag // if (ValidatorReFocus == this) ValidatorReFocus = 0; TControl::EvSetFocus(hWndLostFocus); } // // handler for input validation message sent by parent // void TEdit::EvChildInvalid(HWND) { ValidatorError(); } void TEdit::ValidatorError() { if (Validator) { SetFocus(); Validator->Error(); } } void TEdit::Clear() { DeleteSubText(0, uint(-1)); } bool TEdit::CanClose() { bool okToClose = TStatic::CanClose(); if (okToClose) if (IsWindowEnabled() && !IsValid(true)) { ValidatorReFocus = this; SetFocus(); return false; } return okToClose; } // // This function is called for Cut/Copy/Delete menu items to determine // whether or not the item is enabled. // void TEdit::CmSelectEnable(TCommandEnabler& commandHandler) { uint sPos, ePos; GetSelection(sPos, ePos); commandHandler.Enable(sPos != ePos); } // // This function is called for the Paste menu item to determine whether or // not the item is enabled. // void TEdit::CmPasteEnable(TCommandEnabler& commandHandler) { TClipboard clipboard(*this); commandHandler.Enable(clipboard.IsClipboardFormatAvailable(CF_TEXT)); } // // This function is called for the Clear menu item to determine whether or // not the item is enabled. // void TEdit::CmCharsEnable(TCommandEnabler& commandHandler) { commandHandler.Enable(!(GetNumLines() == 1 && GetLineLength(0) == 0)); } // // This function is called for the Undo menu item to determine whether or // not the item is enabled. // void TEdit::CmModEnable(TCommandEnabler& commandHandler) { commandHandler.Enable(IsModified()); } // // returns the length of line number "LineNumber" // // if -1 is passed as the line number, the following applies: // - returns the length of the line upon which the caret is positioned // - if text is selected on the line, returns the line length minus the // number of selected characters // - if selected text spans more than one line, returns the length minus // the number of selected characters // int TEdit::GetLineLength(int lineNumber) const { return (int)CONST_CAST(TEdit*,this)->HandleMessage(EM_LINELENGTH, (lineNumber > -1) ? GetLineIndex(lineNumber) : -1); } // // returns the text of line number "lineNumber" (0-terminated) // returns false if an error occurs or if the text doesn't fit in "TextString" // bool TEdit::GetLine(char far* textString, int strSize, int lineNumber) const { if (strSize <= 0) return false; bool success = strSize >= GetLineLength(lineNumber) + 1; if (strSize < sizeof(int)) { textString[0] = 0; return success; } ((int far*)textString)[0] = strSize; int bytesCopied = (int)CONST_CAST(TEdit*,this)-> HandleMessage(EM_GETLINE, lineNumber, (LPARAM)textString); textString[bytesCopied] = '\0'; // Windows returns non-0 terminated string if (bytesCopied != (int)CONST_CAST(TEdit*,this)->HandleMessage(EM_LINELENGTH,GetLineIndex(lineNumber))) return false; return success; } // // Lock the edit control's buffer, optionally resizing it first, and return // a far pointer to the beginning of it, or 0 if failure. // Must call UnlockBuffer when finished with buffer. // char far* TEdit::LockBuffer(uint newSize) { // make a copy if can't get handle. Single line edit controls or Win32s will // fail the GetHandle(), so do it the hard way. // if (!(GetWindowLong(GWL_STYLE)&ES_MULTILINE) #if defined(BI_PLAT_WIN32) || (::GetVersion()&0x80000000) // Win32s & Chicago #endif ) { if (!newSize) newSize = GetTextLen()+1; char* buffer = new char[newSize]; GetText(buffer, newSize); return buffer; } HLOCAL hBuffer = GetHandle(); #if defined(BI_PLAT_WIN32) if (newSize) { hBuffer = LocalReAlloc(hBuffer, newSize, LHND); if (!hBuffer) return 0; } return STATIC_CAST(char*,LocalLock(hBuffer)); #else uint16 editDS = FP_SEG(GlobalLock((HINSTANCE)GetWindowWord(GWW_HINSTANCE))); asm push DS; _DS = editDS; if (newSize) { hBuffer = LocalReAlloc(hBuffer, newSize, LHND); if (!hBuffer) { asm pop DS; GlobalUnlock((HGLOBAL)GlobalHandle(editDS)); return 0; } } char far* returnValue = (char far*)MK_FP(_DS,LocalLock(hBuffer)); asm pop DS; return returnValue; #endif } // // Unlock the edit control's buffer locked by LockBuffer. // Also informs control of new handle if indicated. Should update handle // if buffer is resized or written to. // Ignores call if buffer is 0 // void TEdit::UnlockBuffer(const char far* buffer, bool updateHandle) { if (!buffer) return; // if a copy was made on lock, copy it back if requested & free buffer // if (!(GetWindowLong(GWL_STYLE)&ES_MULTILINE) #if defined(BI_PLAT_WIN32) || (::GetVersion()&0x80000000) // Win32s & Chicago #endif ) { if (updateHandle) SetText(buffer); delete [] CONST_CAST(char far*,buffer); return; } #if defined(BI_PLAT_WIN32) HLOCAL hBuffer = LocalHandle((LPVOID)buffer); LocalUnlock(hBuffer); #else uint16 editDS = FP_SEG(buffer); asm push DS; _DS = editDS; HLOCAL hBuffer = LocalHandle((void near*)FP_OFF(buffer)); LocalUnlock(hBuffer); asm pop DS; GlobalUnlock((HGLOBAL)GlobalHandle(editDS)); // unlock LockBuffer's lock #endif // // handle may have moved or buffer contents written on // if (updateHandle) SetHandle(hBuffer); } // // Similar to strstr(), but is case sensitive or insensitive, uses Windows // string functions to work with different language drivers // static const char far* strstrcd(const char far* str1, const char far* str2, bool caseSens) { PRECONDITION(str1 && str2); int len2 = strlen(str2); char far* p = (char far*)str1; const char far* endp = str1 + strlen(str1) - len2 + 1; if (caseSens) while (p < endp) { char c = p[len2]; // must term p to match str2 len p[len2] = 0; // for strcmp to work. if (strcmp(p, str2) == 0) { p[len2] = c; return p; } p[len2] = c; p++; } else while (p < endp) { char c = p[len2]; p[len2] = 0; if (strcmpi(p, str2) == 0) { p[len2] = c; return p; } p[len2] = c; p++; } return 0; } // // Similar to strstrcd(), but searches backwards. Needs the length of str1 // to know where to start. // static const char far* strrstrcd(const char far* str1, uint len1, const char far* str2, bool caseSens) { PRECONDITION(str1 && str2); int len2 = strlen(str2); char far* p = (char far*)(str1 + len1 - len2); if (caseSens) while (p >= str1) { char c = p[len2]; // must term p to match str2 len p[len2] = 0; // for strcmp to work. if (strcmp(p, str2) == 0) { p[len2] = c; return p; } p[len2] = c; p--; } else while (p >= str1) { char c = p[len2]; p[len2] = 0; if (strcmpi(p, str2) == 0) { p[len2] = c; return p; } p[len2] = c; p--; } return 0; } // // searchs for and selects the given text and returns the offset of the text // or -1 if the text is not found // // if "startPos" = -1 then it is assumed that the start pos is the // end/beginning (depending on direction) of the current selection // int TEdit::Search(uint startPos, const char far* text, bool caseSensitive, bool wholeWord, bool up) { if (!text || !text[0]) return -1; if (startPos == (uint)-1) { uint sBeg, sEnd; GetSelection(sBeg, sEnd); startPos = up ? sBeg : sEnd; } int textLen = strlen(text); // // Lock the text buffer to get the pointer, and search thru it for the text // const char far* buffer = LockBuffer(); const char far* pos; for (;;) { if (up) pos = strrstrcd(buffer, startPos, text, caseSensitive); else pos = strstrcd(buffer+startPos, text, caseSensitive); // // If whole-word matching is enabled and we have a match so far, then make // sure the match is on word boundaries. // if (wholeWord && pos) { if (pos > buffer && isalnum(pos[-1]) || // Match is in preceding word textLen < strlen(pos) && isalnum(pos[textLen])) { startPos = (uint)(pos-buffer) + !up; continue; // Skip this match and keep searching } } break; // Out of for loop } // If we've got a match, select that text, cleanup & return. // if (pos) { uint sBeg = (uint)(pos - buffer); UnlockBuffer(buffer); SetSelection(sBeg, sBeg + textLen); #if defined(BI_PLAT_WIN32) HandleMessage(WM_KEYDOWN, VK_RIGHT); SetSelection(sBeg, sBeg + textLen); #endif return sBeg; } UnlockBuffer(buffer); return -1; } // // deletes the selected text; returns false if no text is selected // bool TEdit::DeleteSelection() { uint startPos, endPos; GetSelection(startPos, endPos); if (startPos != endPos) { HandleMessage(WM_CLEAR); return true; } return false; } // // deletes text in the range "startPos .. endPos" // // returns false if an error occurs // bool TEdit::DeleteSubText(uint startPos, uint endPos) { if (SetSelection(startPos, endPos)) return DeleteSelection(); else return false; } // // deletes the text at line number "lineNumber" (deletes the line containing // the caret if "lineNumber" is -1 // // returns false if "lineNumber" is greater than the number of lines // bool TEdit::DeleteLine(int lineNumber) { if (lineNumber == -1) lineNumber = GetLineFromPos(GetLineIndex(-1)); int firstPos = GetLineIndex(lineNumber); if (firstPos != -1) { int lastPos = GetLineIndex(lineNumber + 1); if (lastPos == -1) lastPos = firstPos + GetLineLength(lineNumber); if (firstPos == 0 && firstPos == lastPos) { SetText(""); return true; } else { return DeleteSubText(firstPos, lastPos); } } return false; } // // retrieves the text of the associated edit control between the passed // positions // void TEdit::GetSubText(char far* textString, uint startPos, uint endPos) const { int tempIndex = 0; if (endPos >= startPos) { bool okToContinue = true; int startLine = GetLineFromPos(startPos); int endLine = GetLineFromPos(endPos); int startChar = startPos - GetLineIndex(startLine); int endChar = endPos - GetLineIndex(endLine); for (int tempLine = startLine; tempLine <= endLine && okToContinue; tempLine++) { int tempLineLength = GetLineLength(tempLine); char* line = new char [tempLineLength+1]; int tempStart = tempLine == startLine ? startChar : 0; int tempEnd; if (tempLine == endLine) tempEnd = (endChar > tempLineLength) ? tempLineLength : endChar; else tempEnd = tempLineLength; int tempSize = tempEnd - tempStart; if (GetLine(line, tempLineLength + 1, tempLine)) { // // can happen if we're indexing the CR/LF // if (tempSize > 0) { memcpy(&textString[tempIndex], &line[tempStart], tempSize); tempIndex += tempSize; } if (tempLine != endLine) { textString[tempIndex++] = 0x0d; // CR textString[tempIndex++] = 0x0a; // LF } } else okToContinue = false; delete [] line; } } textString[tempIndex] = '\0'; } // // Return name of predefined Windows edit class // char far* TEdit::GetClassName() { return "EDIT"; } // // limits the amount of text that an edit control can have to the value of // TextLen // void TEdit::SetupWindow() { TStatic::SetupWindow(); if (TextLen != 0) HandleMessage(EM_LIMITTEXT, TextLen - 1); } bool TEdit::IsValid(bool reportError) { if (Validator) { char far* buffer = LockBuffer(); bool valid = reportError ? Validator->Valid(buffer) : Validator->IsValid(buffer); UnlockBuffer(buffer); return valid; } return true; } void TEdit::SetValidator(TValidator* validator) { delete Validator; Validator = validator; } // // transfers state information for TEdit controls // // delegates to the Validator if there is one & it has the transfer option set, // allowing the Validator to convert the text to/from the appropriate type. // else passes to base, TStatic. // // the return value is the size (in bytes) of the transfer data // uint TEdit::Transfer(void* buffer, TTransferDirection direction) { if (Validator && Validator->HasOption(voTransfer) && GetNumLines() <= 1) { CHECK(GetWindowTextLength() < TextLen); char* text = new char[TextLen]; GetText(text, TextLen); uint result = Validator->Transfer(text, buffer, direction); if (result == 0) result = TStatic::Transfer(buffer, direction); else if (direction == tdSetData) SetText(text); delete [] text; return result; } return TStatic::Transfer(buffer, direction); } #endif #if !defined(SECTION) || SECTION == 2 IMPLEMENT_STREAMABLE1(TEdit, TStatic); // // reads an instance of TEdit from the passed ipstream // void* TEdit::Streamer::Read(ipstream& is, uint32 /*version*/) const { ReadBaseObject((TStatic*)GetObject(), is); is >> GetObject()->Validator; return GetObject(); } // // writes the TEdit to the passed opstream // void TEdit::Streamer::Write(opstream& os) const { WriteBaseObject((TStatic*)GetObject(), os); os << GetObject()->Validator; } #endif