//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1991, 1996 by Borland International, All Rights Reserved
//
/// \file
/// Implementation of class TEdit. This defines the basic behavior
/// of all edit controls.
//----------------------------------------------------------------------------
#include <owl/pch.h>
#include <owl/edit.h>
#include <owl/validate.h>
#include <owl/validate.rh>
#include <owl/updown.h>
#if !defined(__CYGWIN__)
//JJH
# if !defined(WINELIB)
# include <dos.h>
# endif // !WINELIB
#endif
#include <string.h>
#include <ctype.h>
#include <algorithm>
#if defined(__BORLANDC__)
# pragma option -w-ccc // Disable "Condition is always true/false"
# pragma option -w-inl // Disable warning "Functions containing 'statement' is not expanded inline".
#endif
using namespace std;
namespace owl {
OWL_DIAGINFO;
/// \class TEdit
///A TEdit is an interface object that represents an edit control interface element.
///A TEdit object must be used to create an edit control in a parent TWindow. A TEdit
///can be used to facilitate communication between your application and the edit controls
///of a TDialog. This class is streamable.
///
///There are two styles of edit control objects: single line and multiline. Multiline edit
///controls allow editing of multiple lines of text.
///
///The position of the first character in an edit control is zero. For a multiline edit control,
///the position numbers continue sequentially from line to line; line breaks count as two characters.
///
///Most of TEdit's member functions manage the edit control's text. TEdit also includes some
///automatic member response functions that respond to selections from the edit control's parent window menu,
///including cut, copy, and paste. Two important member functions inherited from TEdit's base class
///(TStatic) are GetText and SetText.
//
/// Class pointer to an edit control that is trying to regain focus after losing
/// it with invalid contents. Is 0 in all other conditions.
//
/// Used to prevent 'oscillation' when a validated window with invalid
/// input is losing focus to another validated window with invalid input
/// Without this flag, the two windows will fight for focus
//
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_UDN_DELTAPOS(UINT_MAX, EvUpDown),
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;
//
/// Constructs an edit control object with a parent window (parent).
//
/// Sets the creation attributes of the edit control and fills its Attr data members with the
/// specified control ID (Id), position (x, y) relative to the origin of the parent window's client area,
/// width (w), and height (h).
/// If text buffer length (textLimit) is 0 or 1, there is no explicit limit to the number of characters
/// that can be entered. Otherwise textLimit - 1 characters can be entered. By default, initial text (text)
/// in the edit control is left-justified and the edit control has a border. Multiline edit controls have
/// horizontal and vertical scroll bars
TEdit::TEdit(TWindow* parent, int id, LPCTSTR 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), Validator(0), Requirement(roNone)
{
// Undo the styles set by TStatic, & add in edit styles
//
ModifyStyle(SS_LEFT, ES_LEFT | ES_AUTOHSCROLL |WS_BORDER | WS_TABSTOP);
ModifyExStyle(0, WS_EX_CLIENTEDGE);
if (multiline)
Attr.Style |= ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | WS_HSCROLL;
}
//
/// String-aware overload
//
TEdit::TEdit(TWindow* parent, int id, const tstring& 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), Validator(0), Requirement(roNone)
{
// Undo the styles set by TStatic, & add in edit styles
//
ModifyStyle(SS_LEFT, ES_LEFT | ES_AUTOHSCROLL |WS_BORDER | WS_TABSTOP);
ModifyExStyle(0, WS_EX_CLIENTEDGE);
if (multiline)
Attr.Style |= ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | WS_HSCROLL;
}
//
/// Constructor for TEdit associated with a MS-Windows interface element created by MS-Windows from a resource definition
//
/// Constructs a TEdit object to be associated with an edit control of a TDialog.
/// Invokes the TStatic constructor with identical parameters. The resourceID
/// parameter must correspond to an edit resource that you define. Enables the data transfer mechanism
/// by calling EnableTransfer.
TEdit::TEdit(TWindow* parent, int resourceId, uint textLen, TModule* module)
: TStatic(parent, resourceId, textLen, module), Validator(0), Requirement(roNone)
{
EnableTransfer();
}
//
/// Constructs a TEdit object to encapsulate (alias) an existing control.
//
TEdit::TEdit(THandle hWnd, TModule* module)
: TStatic(hWnd, module), Validator(0), Requirement(roNone)
{
EnableTransfer();
}
//
/// Destructor for a TEdit control
//
TEdit::~TEdit()
{
SetValidator(0);
}
//
/// Beeps when edit control runs out of space
//
/// Sounds a beep in response to an error notification message that is sent when the
/// edit control unsuccessfully attempts to allocate more memory.
//
void
TEdit::ENErrSpace()
{
MessageBeep(0);
DefaultProcessing(); // give parent a chance to process
}
//
/// Responds to WM_GETDLGCODE messages that are sent to a dialog box associated with
/// a control. EvGetDlgCode allows the dialog manager to intercept a message that
/// would normally go to a control and then ask the control if it wants to process
/// this message. If not, the dialog manager processes the message. The msg
/// parameter indicates the kind of message, for example a control, command, or edit
/// message, sent to the dialog box manager.
//
/// If the edit control contains valid input, then TABs are allowed for changing
/// focus. Otherwise, request that TABs be sent to Self, where we will generate the
/// Invalid message (See EvKeyDown()).
//
/// Tabulator handling is needed, because if EvKillFocus simply disregards validating
/// upon focus changes to the Ok/Cancel buttons, then tabbing to either of those
/// controls from an invalid field would bypass validation, an undesired side-effect.
/// Or simply, if focus is set to the Cancel button because it was pressed, we don't
/// want validation; if focus was set via tabbing, we do want validation. So tabulator
/// handling is captured since EvKillFocus won't know the difference.
//
uint
TEdit::EvGetDlgCode(const MSG* 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();
TAPointer<tchar> oldBuff(new tchar[oldBuffLen+1]);
GetText(oldBuff, oldBuffLen+1);
int 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();
// We add 1024 as a temporary fix to buffer size due to potential appending
// to the buffer and we do not know how many fill bytes might get added.
//
LPTSTR buff = LockBuffer(std::max((int)TextLimit, std::max(oldBuffLen, buffLen)) + sizeof(tchar) + 1024);
// 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)) {
::_tcscpy(buff, oldBuff); // Restore old buffer
postMsgModify = preMsgModify; // Restore old modify state too!
}
else {
if (wasAppending)
startSel = endSel = static_cast<int>(::_tcslen(buff)); // may have autoFilled--move to end
}
UnlockBuffer(buff, true);
SetSelection(startSel, endSel);
}
else {
if (endSel == buffLen && !Validator->IsValidInput(buff, false))
Validator->Error(this);
UnlockBuffer(buff);
}
SendMessage(EM_SETMODIFY, (TParam1)postMsgModify);
}
else
TStatic::EvChar(key, repeatCount, flags);
}
//
/// EvKeyDown translates the virtual key code into a movement. key indicates the
/// virtual key code of the pressed key, repeatCount holds the number of times the
/// same key is pressed, flags contains one of the messages that translates to a
/// virtual key (VK) code for the mode indicators. If the Tab key is sent to the
/// edit control, EvKeyDown checks 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.
/// See EvGetDlgCode for more information.
//
/// 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(false))
{
ValidatorError();
return;
}
if (Validator && Validator->HasOption(voOnAppend)) {
int startSel, endSel;
GetSelection(startSel, endSel);
int buffLen = GetTextLen(); // length of buffer
bool wasAppending = endSel == buffLen;
DefaultProcessing();
if (!wasAppending) {
GetSelection(startSel, endSel);
LPTSTR buff = LockBuffer();
bool err = endSel == static_cast<int>(::_tcslen(buff)) && !Validator->IsValidInput(buff, false);
UnlockBuffer(buff);
if (err)
Validator->Error(this);
}
}
else
DefaultProcessing();
}
//
/// Validates this whenever the focus is about to be lost.
//
/// In response to a WM_KILLFOCUS message sent to a window that is losing the
/// keyboard, EvKillFocus hides and then destroys the caret. EvKillFocus validates
/// text whenever the focus is about to be lost and will reclaim the focus if the text
/// is not valid. It doesn't kill the focus if another application, a Cancel button, or
/// an OK button (in which case CanClose is called to validate text if pressed) has the
/// focus. See EvGetDlgCode for more information.
//
/// \note Causes focus to be reclaimed via WM_CHILDINVALID. 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(THandle hWndGetFocus)
{
// If another validator is attempting to regain focus, then let it
//
if ((Validator || IsRequired()) && !ValidatorReFocus)
{
// The window getting focus belongs to this app if any of the window
// handles has an OWL object attached that we can get at.
//
THandle 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;
GetParentO()->PostMessage(WM_CHILDINVALID, TParam1(TWindow::GetHandle()));
}
}
}
TControl::EvKillFocus(hWndGetFocus);
}
//
/// Handle the set focus message and make sure the anti-oscillation flag is cleared
//
void
TEdit::EvSetFocus(THandle 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);
}
//
/// Handles up-down messages from an up-down control and adjusts contents if there
/// is a validator to help.
// !Y.B think about it, this message send before change not after ??????????????
bool
TEdit::EvUpDown(TNmUpDown & /*not*/)
{
if (Validator) {
int startSel, endSel;
GetSelection(startSel, endSel);
LPTSTR buff = LockBuffer();
tstring s = buff;
bool changed = Validator->Adjust(s, startSel, endSel, 1) != 0; // !CQ need +- 1
if (changed)
::_tcscpy(buff, s.c_str());
UnlockBuffer(buff, changed);
SetSelection(startSel, endSel);
}
return false; // always allow changes for now
}
//
/// Handle input validation message sent by parent
//
void
TEdit::EvChildInvalid(THandle)
{
ValidatorError();
}
//
/// Handles validation errors that occur as a result of validating the edit control.
//
void
TEdit::ValidatorError()
{
IsValid(true);
}
//
/// Overrides TStatic's virtual member function and clears all text.
//
void
TEdit::Clear()
{
DeleteSubText(0, -1);
}
//
/// Checks to see if all child windows can be closed before closing the current
/// window. If any child window returns false, CanClose returns false and terminates
/// the process. If all child windows can be closed, CanClose returns true.
//
bool
TEdit::CanClose()
{
return TStatic::CanClose() && (!IsWindowEnabled() || IsValid(true));
}
//
/// This function is called for Cut/Copy/Delete menu items to determine
/// whether or not the item is enabled.
//
void
TEdit::CmSelectEnable(TCommandEnabler& commandHandler)
{
int 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, false);
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());
}
//
/// Return the length of line number "lineNumber"
//
/// From a multiline edit control, GetLineLength returns the number of characters in
/// the line specified by lineNumber. If it is -1, the following applies:
/// - if no text is selected, GetLineLength returns the length of the line
/// where the caret is positioned;
/// - if text is selected on the line, GetLineLength returns the line
/// length minus the number of selected characters;
/// - if selected text spans more than one line, GetLineLength returns
/// the length of the lines minus the number of selected characters.
//
int
TEdit::GetLineLength(int lineNumber) const
{
return (int)CONST_CAST(TEdit*,this)->SendMessage(EM_LINELENGTH,
(lineNumber > -1) ? GetLineIndex(lineNumber) : -1);
}
//
/// Return the text of line number "lineNumber" (0-terminated)
///
/// Retrieves a line of text (whose line number is supplied) from the edit control
/// and returns it in str (NULL-terminated). strSize indicates how many characters
/// to retrieve. GetLine returns false if it is unable to retrieve the text or if
/// the supplied buffer is too small.
///
/// \note Requires a buffer of at least 2 bytes, even when the line length is 1
//
bool
TEdit::GetLine(LPTSTR textString, int strSize, int lineNumber) const
{
if (strSize <= 0)
return false;
int lineLength = GetLineLength(lineNumber);
bool success = strSize >= lineLength + 1;
// The first 16bit word of the buffer is used by EM_GETLINE as the buffer
// size. Always need a buffer of at least 2 bytes to pass this word.
//
if (static_cast<unsigned int>(strSize) < sizeof(int16)) { //JJH added static cast
textString[0] = 0;
return lineLength == 0; // If lineLen was 0, only then are we successful
}
((int16 *)textString)[0] = int16(strSize);
int bytesCopied = (int)CONST_CAST(TEdit*,this)->
SendMessage(EM_GETLINE, lineNumber, TParam2(textString));
textString[bytesCopied] = 0; // Windows returns non-0 terminated string
if (bytesCopied != lineLength)
return false;
return success;
}
//
/// For use with CopyText.
//
struct TEditGetLine
{
const TEdit& edit;
int line;
TEditGetLine(const TEdit& c, int line_) : edit(c), line(line_) {}
int operator()(LPTSTR buf, int buf_size)
{
bool ok = edit.GetLine(buf, buf_size, line);
WARN(!ok, _T("TEdit::GetLine failed, line = ") << line);
static_cast<void>(ok); // Suppress not-in-use warning.
return buf_size - 1; // line length
}
};
//
/// String-aware overload
/// If the length of the returned string differs from GetLineLength an error occured.
//
tstring
TEdit::GetLine(int lineNumber) const
{
return CopyText(GetLineLength(lineNumber), TEditGetLine(*this, lineNumber));
}
//
/// Lock the edit control's buffer, optionally resizing it first, and return
/// a pointer to the beginning of it, or 0 if failure.
/// Must call UnlockBuffer when finished with buffer.
//
LPTSTR
TEdit::LockBuffer(uint newSize)
{
// Single line edit controls or Win32s will fail the GetMemHandle(),
// so do it the hard way and make a copy in this case.
// We also copy to avoid a problem when using the new XP common controls.
//
static const auto v = GetCommCtrlVersion();
if (!(GetWindowLong(GWL_STYLE) & ES_MULTILINE) || v >= 0x60000)
{
if (!newSize)
newSize = GetTextLen() + 1;
LPTSTR buffer = new tchar[newSize];
GetText(buffer, newSize);
return buffer;
}
HLOCAL hBuffer = GetMemHandle();
if (newSize)
{
hBuffer = LocalReAlloc(hBuffer, newSize * sizeof(tchar), LHND);
if (!hBuffer)
return 0;
}
return static_cast<LPTSTR>(LocalLock(hBuffer));
}
//
/// Unlock the edit control's buffer locked by LockBuffer.
//
/// If the contents were changed (buffer is resized or written to), updateHandle should be true.
/// Ignores call if buffer is 0
//
void
TEdit::UnlockBuffer(LPCTSTR buffer, bool updateHandle)
{
if (!buffer)
return;
// If a copy was made on lock, copy it back if requested, and then free the buffer.
//
static const auto v = GetCommCtrlVersion();
if (!(GetWindowLong(GWL_STYLE) & ES_MULTILINE) || v >= 0x60000)
{
if (updateHandle)
SetText(buffer);
delete[] const_cast<LPTSTR>(buffer);
return;
}
HLOCAL hBuffer = LocalHandle(const_cast<LPTSTR>(buffer));
LocalUnlock(hBuffer);
// Handle may have moved or buffer contents written.
//
if (updateHandle)
SetMemHandle(hBuffer);
}
//
/// Similar to strstr(), but is case sensitive or insensitive, uses Windows
/// string functions to work with different language drivers
//
static LPCTSTR
strstrcd(LPCTSTR str1, LPCTSTR str2, bool caseSens)
{
PRECONDITION(str1 && str2);
int len2 = static_cast<int>(::_tcslen(str2));
LPTSTR p = (LPTSTR)str1;
LPCTSTR endp = str1 + ::_tcslen(str1) - len2 + 1;
if (caseSens)
while (p < endp) {
tchar c = p[len2]; // must term p to match str2 len
p[len2] = 0; // for _tcscmp to work.
if (_tcscmp(p, str2) == 0) {
p[len2] = c;
return p;
}
p[len2] = c;
#if defined(BI_DBCS_SUPPORT) && !defined(UNICODE)
LPTSTR p2 = ::AnsiNext(p);
if (p2 <= p)
break;
p = p2;
#else
p++;
#endif
}
else
while (p < endp) {
tchar c = p[len2];
p[len2] = 0;
if (_tcsicmp(p, str2) == 0) {
p[len2] = c;
return p;
}
p[len2] = c;
#if defined(BI_DBCS_SUPPORT) && !defined(UNICODE)
LPTSTR p2 = ::AnsiNext(p);
if (p2 <= p)
break;
p = p2;
#else
p++;
#endif
}
return 0;
}
//
/// Similar to strstrcd(), but searches backwards. Needs the length of str1
/// to know where to start.
//
static LPCTSTR
strrstrcd(LPCTSTR str1, uint len1, LPCTSTR str2,
bool caseSens)
{
PRECONDITION(str1 && str2);
int len2 = static_cast<int>(::_tcslen(str2));
LPTSTR p = (LPTSTR)(str1 + len1 - len2);
#if defined(BI_DBCS_SUPPORT) && !defined(UNICODE)
if (p >= str1)
p = ::AnsiPrev(str1, p+1);
#endif
if (caseSens)
while (p >= str1) {
tchar c = p[len2]; // must term p to match str2 len
p[len2] = 0; // for _tcscmp to work.
if (_tcscmp(p, str2) == 0) {
p[len2] = c;
return p;
}
p[len2] = c;
#if defined(BI_DBCS_SUPPORT) && !defined(UNICODE)
LPTSTR p2 = ::AnsiPrev(str1, p);
if (p2 >= p)
break;
p = p2;
#else
p--;
#endif
}
else
while (p >= str1) {
tchar c = p[len2];
p[len2] = 0;
if (_tcsicmp(p, str2) == 0) {
p[len2] = c;
return p;
}
p[len2] = c;
#if defined(BI_DBCS_SUPPORT) && !defined(UNICODE)
LPTSTR p2 = ::AnsiPrev(str1, p);
if (p2 >= p)
break;
p = p2;
#else
p--;
#endif
}
return 0;
}
//
/// Returns the current caret position.
///
/// \note the Windows edit control can only handle position-ranges between
/// 0..2^16 characters
///
/// \return the index of the text input-caret, counting zero-based
/// from the first character in the text of the control.
///
/// \sa <a href="https://docs.microsoft.com/en-us/windows/win32/controls/em-getsel">EM_GETSEL message</a>
/// in the Windows API for more technical insight, or
/// \sa <a href="https://docs.microsoft.com/en-us/windows/win32/controls/bumper-edit-control-reference-messages">
/// Edit Control Messages</a> for a more general overview.
///
/// \exception TXOwl is thrown on failure.
//
auto TEdit::GetCurrentPosition() const -> int
{
PRECONDITION(GetHandle());
TRange selection = GetSelection();
if (selection.IsValidRange()) {
// we don't know the caret position yet...
// CharFromPos would not report positions above that range
// because the relevant information we get back is only of WORD size...
if (selection.cpMin > USHRT_MAX || selection.cpMax > USHRT_MAX)
throw TXOwl{_T("Character position is out of range")}; // sorry, you reached the limits of this Windows-API
/* we dont know which end of the range is the caret-position,
so we have to ask explicitly for it... */
TPoint caretpos = GetCaretPos();
// CharFromPos wouldn't be able to handle positions above that range (can any editcontrol?)
if ( caretpos.x > SHRT_MAX || caretpos.x < SHRT_MIN
|| caretpos.y > SHRT_MAX || caretpos.y < SHRT_MIN)
throw TXOwl{_T("Character position is out of range")}; // Windows-API limit
auto cfp = CharFromPos(static_cast<int16>(caretpos.x), static_cast<int16>(caretpos.y));
CHECK(-1 != cfp); // (65535, 65535) error - position out of bounds - shouldn't happen
return LOWORD(cfp);
}
return selection.cpMin;
}
//
/// searches for and selects the given text and returns the offset of the text or -1 if the text is not found
//
/// Performs either a case-sensitive or case-insensitive search for the supplied
/// text. If the text is found, the matching text is selected, and Search returns
/// the position of the beginning of the matched text. If the text is not found in
/// the edit control's text, Search returns -1. If -1 is passed as startPos, then
/// the search starts from either the end or the beginning of the currently selected
/// text, depending on the search direction.
//
int
TEdit::Search(int startPos, LPCTSTR text, bool caseSensitive,
bool wholeWord, bool up)
{
if (!text || !text[0])
return -1;
if (startPos == -1) {
int sBeg, sEnd;
GetSelection(sBeg, sEnd);
startPos = up ? sBeg : sEnd;
}
int textLen = static_cast<int>(::_tcslen(text));
// Lock the text buffer to get the pointer, and search thru it for the text
//
LPCTSTR buffer = LockBuffer();
LPCTSTR 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 defined(BI_DBCS_SUPPORT)
LPTSTR prevPos;
if (pos > buffer)
prevPos = ::AnsiPrev(buffer, pos);
if ((pos > buffer && _istalnum(*prevPos)) || // Match is in preceding word
(textLen < static_cast<int>(::_tcslen(pos)) && _istalnum(pos[textLen])))
{
if (up)
startPos = static_cast<int>(prevPos - buffer + ::_tcslen(text));
else
startPos = static_cast<int>(::AnsiNext(pos) - buffer);
continue; // Skip this match and keep searching
}
#else
if ((pos > buffer && _istalnum(pos[-1])) || // Match is in preceding word
(textLen < static_cast<int>(::_tcslen(pos)) && _istalnum(pos[textLen])))
{
startPos = (uint)(pos-buffer) + !up;
continue; // Skip this match and keep searching
}
#endif
}
break; // Out of for loop
}
// If we've got a match, select that text, cleanup & return.
//
if (pos) {
int sBeg = static_cast<int>(pos - buffer);
UnlockBuffer(buffer);
SetSelection(sBeg, sBeg + textLen);
SendMessage(WM_KEYDOWN, VK_RIGHT);
SetSelection(sBeg, sBeg + textLen);
return sBeg;
}
UnlockBuffer(buffer);
return -1;
}
//
/// Deletes the currently selected text, and returns false if no text is selected.
//
bool
TEdit::DeleteSelection()
{
int startPos, endPos;
GetSelection(startPos, endPos);
if (startPos != endPos) {
SendMessage(WM_CLEAR);
return true;
}
return false;
}
//
/// Deletes the text between the starting and ending positions
/// specified by startPos and endPos, respectively.
/// DeleteSubText returns true if successful.
//
bool
TEdit::DeleteSubText(int startPos, int endPos)
{
if (SetSelection(startPos, endPos))
return DeleteSelection();
else
return false;
}
//
/// Deletes the text in the line specified by lineNumber in a multiline edit control.
/// If -1 passed, deletes the current line. DeleteLine does not delete the line break
/// and affects no other lines. Returns true if successful. Returns false if lineNumber
/// is not -1 and is out of range or if an error occurs.
//
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(_T(""));
return true;
}
else {
return DeleteSubText(firstPos, lastPos);
}
}
return false;
}
//
/// Retrieve the text of the associated edit control between the given positions.
/// Note that the buffer must be large enough to hold the text, otherwise buffer
/// overrun will occur. The destination is always null-terminated.
///
/// NB! This function is deprecated. Use GetTextRange instead.
//
void
TEdit::GetSubText(LPTSTR textBuf, int startPos, int endPos) const
{
WARN(true, _T("TEdit::GetSubText is deprecated. Use GetTextRange instead."));
if (!textBuf) return;
tstring t = GetTextRange(TRange(startPos, endPos));
_tcscpy(textBuf, t.c_str());
}
//
/// Retrieves a specified range of text from the edit control.
/// The range is half-open; i.e. range [0,2) for "abc" returns "ab".
/// An empty string is returned if either
///
/// - the control is empty (no text), or
/// - the range is invalid (empty or inverted), or
/// - whole range is beyond the extents of the actual text.
///
/// If the end of the range is beyond the valid range then it is limited to the valid range.
/// A special case is the range [0, -1). It will return the full text.
///
/// \todo Must be tested
//
tstring
TEdit::GetTextRange(const TRange& r) const
{
const int begin = r.cpMin;
int end = r.cpMax; // May be adjusted.
// Check input arguments against EM_GETTEXTRANGE requirements.
// Note that [0, -1) for an empty control is not valid for EM_GETTEXTRANGE,
// but we'll ignore that, since no buffer issues are involved here.
// Otherwise we reject negative positions, as well as empty and inverted ranges.
// EM_GETTEXTRANGE would not null-terminate the result in these cases,
// so to adhere to the same policy we warn about these cases.
if (begin == 0 && end == -1)
{
return GetText();
}
tstring s;
if (begin < 0 || end < 0)
{
WARN(true, _T("Arguments out of range"));
return s;
}
else if (begin == end)
{
WARN(true, _T("Empty range"));
return s;
}
else if (begin > end)
{
WARN(true, _T("Inverted range"));
return s;
}
// Return empty if the entire range is outside the extents of the actual text.
// This is valid for EM_GETTEXTRANGE so we wont complain.
int n = GetTextLen();
if (begin >= n)
return s;
// Limit end to the actual text size.
end = std::min(end, n);
// Calculate line indexes and char indexes (within lines).
// Then append line by line to the result string, cropping
// the start and end line specially.
const int startLine = GetLineFromPos(begin);
const int startChar = begin - GetLineIndex(startLine);
const int endLine = GetLineFromPos(end);
const int endChar = end - GetLineIndex(endLine);
for (int i = startLine; i <= endLine; ++i)
{
tstring line = GetLine(i) + _T("\r\n"); // CRLF-terminated
int b = (i == startLine) ? startChar : 0;
int e = (i == endLine) ? endChar : static_cast<int>(line.length());
CHECK(b <= e); // sanity check
s.append(line, b, e - b);
}
return s;
}
//
/// Functional style overload
//
TEdit::TRange
TEdit::GetSelection() const
{
TRange r;
GetSelection(r.cpMin, r.cpMax);
return r;
}
//
/// Return name of predefined Windows edit class
//
auto TEdit::GetWindowClassName() -> TWindowClassName
{
return TWindowClassName{_T("EDIT")};
}
//
/// \note If the textLimit data member is nonzero, SetupWindow limits the number of
/// characters that can be entered into the edit control to textLimit -1.
//
void
TEdit::SetupWindow()
{
TStatic::SetupWindow();
if (TextLimit != 0)
LimitText(TextLimit - 1);
else
LimitText(0); // This will remove the 32K limit under WinNT and later
}
//
/// Always returns true if the TEdit object does not have an associated TValidator
/// object (i.e. if TEdit.Validator == 0) and it is not required.
/// If the edit control is required, requirement is validated.
/// If the edit control has a validator, and the reportError parameter is set to true,
/// then IsValid calls the validator's Valid method. If the reportError parameter is
/// false, IsValid calls the validator's IsValid method.
//
bool
TEdit::IsValid(bool reportError)
{
bool valid = true;
// Perform validation if needed.
//
if (Validator || IsRequired())
{
// Make re-focus anti-oscillation flag exception-safe.
//
struct TRefocusGuard
{
bool Dismissed;
~TRefocusGuard() { if (!Dismissed) ValidatorReFocus = 0; }
}
reFocusGuard = {false};
// Set if re-focusing might be needed after error message is displayed.
//
bool reFocus = GetFocus() != *this;
if (reportError) ValidatorReFocus = this;
// Lock the buffer to perform validation.
//
LPTSTR buffer = LockBuffer();
// If requirement option set, check if requirement met.
//
if (IsRequired())
{
valid = *buffer;
if (valid && (GetRequired() == roNonBlank))
valid = _tcsspn(buffer, _T(" \t\n\v\f\r\xA0")) != _tcslen(buffer);
if (!valid && reportError)
MessageBox(LoadString((GetRequired() == roNonBlank) ? IDS_VALNONBLANK : IDS_VALREQUIRED), LoadString(IDS_VALCAPTION), MB_ICONERROR | MB_OK);
}
// If still valid and validator present, have it validate.
//
if (valid && Validator)
valid = reportError ? Validator->Valid(buffer, this) : Validator->IsValid(buffer);
// Unlock the buffer
//
UnlockBuffer(buffer);
// Give us focus if needed or reset anti-oscillation.
//
if (reportError && !valid && reFocus)
{
SetFocus();
reFocusGuard.Dismissed = true;
}
}
return valid;
}
//
/// Sets a new validator for this control, can be 0. Cleans up the old validator
//
void
TEdit::SetValidator(TValidator* validator)
{
delete (TStreamableBase*)Validator; // upcast to avoid explicit call to dtor
Validator = validator;
}
//
/// Transfers state information for TEdit controls
//
/// Transfers information for TEdit controls and sends information to the Validator
/// if one exists, and if it has the transfer option set. Transfer can perform type
/// conversion when validators are in place, for example, when TRangeValidator
/// transfers integers. The return value is the size (in bytes) of the transfer
/// data.
//
/// 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(static_cast<uint>(GetWindowTextLength()) < TextLimit);
// Allocate a buffer for the validator.
// Use a "big enough" size; there's no protocol for the buffer size, unfortunately.
//
const size_t bigEnough = max<size_t>(1024, TextLimit + 1);
vector<tchar> text(bigEnough);
GetText(&text[0], static_cast<int>(text.size()));
uint result = Validator->Transfer(&text[0], buffer, direction);
if (result == 0)
result = TStatic::Transfer(buffer, direction);
else if (direction == tdSetData)
SetText(&text[0]);
return result;
}
return TStatic::Transfer(buffer, direction);
}
IMPLEMENT_STREAMABLE1(TEdit, TStatic);
#if OWL_PERSISTENT_STREAMS
//
/// Reads an instance of TEdit from the given ipstream.
//
void*
TEdit::Streamer::Read(ipstream& is, uint32 version) const
{
auto t = GetObject(); // Get "this" instance.
CHECK(t);
ReadBaseObject<TStatic>(t, is);
is >> t->Validator;
if (version < 2)
t->Requirement = TEdit::roNone;
else
{
auto r = static_cast<int>(TEdit::roNone);
is >> r;
t->Requirement = static_cast<TEdit::TRequireOption>(r);
}
return t;
}
//
/// Writes the TEdit to the given opstream.
//
void
TEdit::Streamer::Write(opstream& os) const
{
auto t = GetObject(); // Get "this" instance.
CHECK(t);
WriteBaseObject<TStatic>(t, os);
os << t->Validator;
os << static_cast<int>(t->Requirement);
}
#endif
} // OWL namespace
↑ V1004 The 'str2' pointer was used unsafely after it was verified against nullptr. Check lines: 637, 638.
↑ V1004 The 'str1' pointer was used unsafely after it was verified against nullptr. Check lines: 637, 640.
↑ V1004 The 'str2' pointer was used unsafely after it was verified against nullptr. Check lines: 689, 690.
↑ V1004 The 'str1' pointer was used unsafely after it was verified against nullptr. Check lines: 689, 691.
↑ V821 Decreased performance. The 'reFocusGuard' variable can be constructed in a lower level scope.