+ // if from and to are both -1, it means (in wxWidgets) that all text should
+ // be selected - translate into Windows convention
+ if ( (from == -1) && (to == -1) )
+ {
+ from = 0;
+ to = -1;
+ }
+
+ DoSetSelection(from, to);
+}
+
+void wxTextCtrl::DoSetSelection(long from, long to, bool scrollCaret)
+{
+ HWND hWnd = GetHwnd();
+
+#if wxUSE_RICHEDIT
+ if ( IsRich() )
+ {
+ CHARRANGE range;
+ range.cpMin = from;
+ range.cpMax = to;
+ ::SendMessage(hWnd, EM_EXSETSEL, 0, (LPARAM) &range);
+ }
+ else
+#endif // wxUSE_RICHEDIT
+ {
+ ::SendMessage(hWnd, EM_SETSEL, (WPARAM)from, (LPARAM)to);
+ }
+
+ if ( scrollCaret && !IsFrozen() )
+ {
+#if wxUSE_RICHEDIT
+ // richedit 3.0 (i.e. the version living in riched20.dll distributed
+ // with Windows 2000 and beyond) doesn't honour EM_SCROLLCARET when
+ // emulating richedit 2.0 unless the control has focus or ECO_NOHIDESEL
+ // option is set (but it does work ok in richedit 1.0 mode...)
+ //
+ // so to make it work we either need to give focus to it here which
+ // will probably create many problems (dummy focus events; window
+ // containing the text control being brought to foreground
+ // unexpectedly; ...) or to temporarily set ECO_NOHIDESEL which may
+ // create other problems too -- and in fact it does because if we turn
+ // on/off this style while appending the text to the control, the
+ // vertical scrollbar never appears in it even if we append tons of
+ // text and to work around this the only solution I found was to use
+ // ES_DISABLENOSCROLL
+ //
+ // this is very ugly but I don't see any other way to make this work
+ if ( GetRichVersion() > 1 )
+ {
+ if ( !HasFlag(wxTE_NOHIDESEL) )
+ {
+ ::SendMessage(GetHwnd(), EM_SETOPTIONS,
+ ECOOP_OR, ECO_NOHIDESEL);
+ }
+ //else: everything is already ok
+ }
+#endif // wxUSE_RICHEDIT
+
+ ::SendMessage(hWnd, EM_SCROLLCARET, (WPARAM)0, (LPARAM)0);
+
+#if wxUSE_RICHEDIT
+ // restore ECO_NOHIDESEL if we changed it
+ if ( GetRichVersion() > 1 && !HasFlag(wxTE_NOHIDESEL) )
+ {
+ ::SendMessage(GetHwnd(), EM_SETOPTIONS,
+ ECOOP_AND, ~ECO_NOHIDESEL);
+ }
+#endif // wxUSE_RICHEDIT
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Working with files
+// ----------------------------------------------------------------------------
+
+bool wxTextCtrl::LoadFile(const wxString& file)
+{
+ if ( wxTextCtrlBase::LoadFile(file) )
+ {
+ // update the size limit if needed
+ AdjustSpaceLimit();
+
+ return true;
+ }
+
+ return false;
+}
+
+// ----------------------------------------------------------------------------
+// Editing
+// ----------------------------------------------------------------------------
+
+void wxTextCtrl::Replace(long from, long to, const wxString& value)
+{
+ // Set selection and remove it
+ DoSetSelection(from, to, false /* don't scroll caret into view */);
+
+ DoWriteText(value, true /* selection only */);
+}
+
+void wxTextCtrl::Remove(long from, long to)
+{
+ Replace(from, to, wxEmptyString);
+}
+
+bool wxTextCtrl::IsModified() const
+{
+ return ::SendMessage(GetHwnd(), EM_GETMODIFY, 0, 0) != 0;
+}
+
+void wxTextCtrl::MarkDirty()
+{
+ ::SendMessage(GetHwnd(), EM_SETMODIFY, TRUE, 0L);
+}
+
+void wxTextCtrl::DiscardEdits()
+{
+ ::SendMessage(GetHwnd(), EM_SETMODIFY, FALSE, 0L);
+}
+
+int wxTextCtrl::GetNumberOfLines() const
+{
+ return (int)::SendMessage(GetHwnd(), EM_GETLINECOUNT, (WPARAM)0, (LPARAM)0);
+}
+
+// ----------------------------------------------------------------------------
+// Positions <-> coords
+// ----------------------------------------------------------------------------
+
+long wxTextCtrl::XYToPosition(long x, long y) const
+{
+ // This gets the char index for the _beginning_ of this line
+ long charIndex = ::SendMessage(GetHwnd(), EM_LINEINDEX, (WPARAM)y, (LPARAM)0);
+
+ return charIndex + x;
+}
+
+bool wxTextCtrl::PositionToXY(long pos, long *x, long *y) const
+{
+ HWND hWnd = GetHwnd();
+
+ // This gets the line number containing the character
+ long lineNo;
+#if wxUSE_RICHEDIT
+ if ( IsRich() )
+ {
+ lineNo = ::SendMessage(hWnd, EM_EXLINEFROMCHAR, 0, (LPARAM)pos);
+ }
+ else
+#endif // wxUSE_RICHEDIT
+ {
+ lineNo = ::SendMessage(hWnd, EM_LINEFROMCHAR, (WPARAM)pos, 0);
+ }
+
+ if ( lineNo == -1 )
+ {
+ // no such line
+ return false;
+ }
+
+ // This gets the char index for the _beginning_ of this line
+ long charIndex = ::SendMessage(hWnd, EM_LINEINDEX, (WPARAM)lineNo, (LPARAM)0);
+ if ( charIndex == -1 )
+ {
+ return false;
+ }
+
+ // The X position must therefore be the different between pos and charIndex
+ if ( x )
+ *x = pos - charIndex;
+ if ( y )
+ *y = lineNo;
+
+ return true;
+}
+
+wxTextCtrlHitTestResult
+wxTextCtrl::HitTest(const wxPoint& pt, long *posOut) const
+{
+ // first get the position from Windows
+ LPARAM lParam;
+
+#if wxUSE_RICHEDIT
+ POINTL ptl;
+ if ( IsRich() )
+ {
+ // for rich edit controls the position is passed iva the struct fields
+ ptl.x = pt.x;
+ ptl.y = pt.y;
+ lParam = (LPARAM)&ptl;
+ }
+ else
+#endif // wxUSE_RICHEDIT
+ {
+ // for the plain ones, we are limited to 16 bit positions which are
+ // combined in a single 32 bit value
+ lParam = MAKELPARAM(pt.x, pt.y);
+ }
+
+ LRESULT pos = ::SendMessage(GetHwnd(), EM_CHARFROMPOS, 0, lParam);
+
+ if ( pos == -1 )
+ {
+ // this seems to indicate an error...
+ return wxTE_HT_UNKNOWN;
+ }
+
+#if wxUSE_RICHEDIT
+ if ( !IsRich() )
+#endif // wxUSE_RICHEDIT
+ {
+ // for plain EDIT controls the higher word contains something else
+ pos = LOWORD(pos);
+ }
+
+
+ // next determine where it is relatively to our point: EM_CHARFROMPOS
+ // always returns the closest character but we need to be more precise, so
+ // double check that we really are where it pretends
+ POINTL ptReal;
+
+#if wxUSE_RICHEDIT
+ // FIXME: we need to distinguish between richedit 2 and 3 here somehow but
+ // we don't know how to do it
+ if ( IsRich() )
+ {
+ ::SendMessage(GetHwnd(), EM_POSFROMCHAR, (WPARAM)&ptReal, pos);
+ }
+ else
+#endif // wxUSE_RICHEDIT
+ {
+ LRESULT lRc = ::SendMessage(GetHwnd(), EM_POSFROMCHAR, pos, 0);
+
+ if ( lRc == -1 )
+ {
+ // this is apparently returned when pos corresponds to the last
+ // position
+ ptReal.x =
+ ptReal.y = 0;
+ }
+ else
+ {
+ ptReal.x = LOWORD(lRc);
+ ptReal.y = HIWORD(lRc);
+ }
+ }
+
+ wxTextCtrlHitTestResult rc;
+
+ if ( pt.y > ptReal.y + GetCharHeight() )
+ rc = wxTE_HT_BELOW;
+ else if ( pt.x > ptReal.x + GetCharWidth() )
+ rc = wxTE_HT_BEYOND;
+ else
+ rc = wxTE_HT_ON_TEXT;
+
+ if ( posOut )
+ *posOut = pos;
+
+ return rc;
+}
+
+// ----------------------------------------------------------------------------
+//
+// ----------------------------------------------------------------------------
+
+void wxTextCtrl::ShowPosition(long pos)
+{
+ HWND hWnd = GetHwnd();
+
+ // To scroll to a position, we pass the number of lines and characters
+ // to scroll *by*. This means that we need to:
+ // (1) Find the line position of the current line.
+ // (2) Find the line position of pos.
+ // (3) Scroll by (pos - current).
+ // For now, ignore the horizontal scrolling.
+
+ // Is this where scrolling is relative to - the line containing the caret?
+ // Or is the first visible line??? Try first visible line.
+// int currentLineLineNo1 = (int)::SendMessage(hWnd, EM_LINEFROMCHAR, (WPARAM)-1, (LPARAM)0L);
+
+ int currentLineLineNo = (int)::SendMessage(hWnd, EM_GETFIRSTVISIBLELINE, (WPARAM)0, (LPARAM)0L);
+
+ int specifiedLineLineNo = (int)::SendMessage(hWnd, EM_LINEFROMCHAR, (WPARAM)pos, (LPARAM)0L);
+
+ int linesToScroll = specifiedLineLineNo - currentLineLineNo;
+
+ if (linesToScroll != 0)
+ (void)::SendMessage(hWnd, EM_LINESCROLL, (WPARAM)0, (LPARAM)linesToScroll);
+}
+
+long wxTextCtrl::GetLengthOfLineContainingPos(long pos) const
+{
+ return ::SendMessage(GetHwnd(), EM_LINELENGTH, (WPARAM)pos, 0);
+}
+
+int wxTextCtrl::GetLineLength(long lineNo) const
+{
+ long pos = XYToPosition(0, lineNo);
+
+ return GetLengthOfLineContainingPos(pos);
+}
+
+wxString wxTextCtrl::GetLineText(long lineNo) const
+{
+ size_t len = (size_t)GetLineLength(lineNo) + 1;
+
+ // there must be at least enough place for the length WORD in the
+ // buffer
+ len += sizeof(WORD);
+
+ wxString str;
+ {
+ wxStringBufferLength tmp(str, len);
+ wxChar *buf = tmp;
+
+ *(WORD *)buf = (WORD)len;
+ len = (size_t)::SendMessage(GetHwnd(), EM_GETLINE, lineNo, (LPARAM)buf);
+
+#if wxUSE_RICHEDIT
+ if ( IsRich() )
+ {
+ // remove the '\r' returned by the rich edit control, the user code
+ // should never see it
+ if ( buf[len - 2] == _T('\r') && buf[len - 1] == _T('\n') )
+ {
+ buf[len - 2] = _T('\n');
+ len--;
+ }
+ }
+#endif // wxUSE_RICHEDIT
+
+ // remove the '\n' at the end, if any (this is how this function is
+ // supposed to work according to the docs)
+ if ( buf[len - 1] == _T('\n') )
+ {
+ len--;
+ }
+
+ buf[len] = 0;
+ tmp.SetLength(len);
+ }
+
+ return str;
+}
+
+void wxTextCtrl::SetMaxLength(unsigned long len)
+{
+#if wxUSE_RICHEDIT
+ if ( IsRich() )
+ {
+ ::SendMessage(GetHwnd(), EM_EXLIMITTEXT, 0, len ? len : 0x7fffffff);
+ }
+ else
+#endif // wxUSE_RICHEDIT
+ {
+ if ( len >= 0xffff )
+ {
+ // this will set it to a platform-dependent maximum (much more
+ // than 64Kb under NT)
+ len = 0;
+ }
+
+ ::SendMessage(GetHwnd(), EM_LIMITTEXT, len, 0);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Undo/redo
+// ----------------------------------------------------------------------------
+
+void wxTextCtrl::Undo()
+{
+ if (CanUndo())
+ {
+ ::SendMessage(GetHwnd(), EM_UNDO, 0, 0);
+ }
+}
+
+void wxTextCtrl::Redo()
+{
+ if (CanRedo())
+ {
+#if wxUSE_RICHEDIT
+ if (GetRichVersion() > 1)
+ ::SendMessage(GetHwnd(), EM_REDO, 0, 0);
+ else
+#endif
+ // Same as Undo, since Undo undoes the undo, i.e. a redo.
+ ::SendMessage(GetHwnd(), EM_UNDO, 0, 0);
+ }
+}
+
+bool wxTextCtrl::CanUndo() const
+{
+ return ::SendMessage(GetHwnd(), EM_CANUNDO, 0, 0) != 0;
+}
+
+bool wxTextCtrl::CanRedo() const
+{
+#if wxUSE_RICHEDIT
+ if (GetRichVersion() > 1)
+ return ::SendMessage(GetHwnd(), EM_CANREDO, 0, 0) != 0;
+ else
+#endif
+ return ::SendMessage(GetHwnd(), EM_CANUNDO, 0, 0) != 0;
+}
+
+// ----------------------------------------------------------------------------
+// caret handling (Windows only)
+// ----------------------------------------------------------------------------
+
+bool wxTextCtrl::ShowNativeCaret(bool show)
+{
+ if ( show != m_isNativeCaretShown )
+ {
+ if ( !(show ? ::ShowCaret(GetHwnd()) : ::HideCaret(GetHwnd())) )
+ {
+ // not an error, may simply indicate that it's not shown/hidden
+ // yet (i.e. it had been hidden/showh 2 times before)
+ return false;
+ }
+
+ m_isNativeCaretShown = show;
+ }
+
+ return true;
+}
+
+// ----------------------------------------------------------------------------
+// implemenation details
+// ----------------------------------------------------------------------------
+
+void wxTextCtrl::Command(wxCommandEvent & event)
+{
+ SetValue(event.GetString());
+ ProcessCommand (event);
+}
+
+void wxTextCtrl::OnDropFiles(wxDropFilesEvent& event)
+{
+ // By default, load the first file into the text window.
+ if (event.GetNumberOfFiles() > 0)
+ {
+ LoadFile(event.GetFiles()[0]);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// kbd input processing
+// ----------------------------------------------------------------------------
+
+bool wxTextCtrl::MSWShouldPreProcessMessage(WXMSG* pMsg)
+{
+ MSG *msg = (MSG *)pMsg;
+
+ // check for our special keys here: if we don't do it and the parent frame
+ // uses them as accelerators, they wouldn't work at all, so we disable
+ // usual preprocessing for them
+ if ( msg->message == WM_KEYDOWN )
+ {
+ WORD vkey = (WORD) msg->wParam;
+ if ( (HIWORD(msg->lParam) & KF_ALTDOWN) == KF_ALTDOWN )
+ {
+ if ( vkey == VK_BACK )
+ return false;
+ }
+ else // no Alt
+ {
+ // we want to process some Ctrl-foo and Shift-bar but no key
+ // combinations without either Ctrl or Shift nor with both of them
+ // pressed
+ const int ctrl = wxIsCtrlDown(),
+ shift = wxIsShiftDown();
+ switch ( ctrl + shift )
+ {
+ default:
+ wxFAIL_MSG( _T("how many modifiers have we got?") );
+ // fall through
+
+ case 0:
+ case 2:
+ break;
+
+ case 1:
+ // either Ctrl or Shift pressed
+ if ( ctrl )
+ {
+ switch ( vkey )
+ {
+ case 'C':
+ case 'V':
+ case 'X':
+ case VK_INSERT:
+ case VK_DELETE:
+ case VK_HOME:
+ case VK_END:
+ return false;
+ }
+ }
+ else // Shift is pressed
+ {
+ if ( vkey == VK_INSERT || vkey == VK_DELETE )
+ return false;
+ }
+ }
+ }
+ }
+
+ return wxControl::MSWShouldPreProcessMessage(pMsg);
+}
+
+void wxTextCtrl::OnChar(wxKeyEvent& event)
+{
+ switch ( event.GetKeyCode() )
+ {
+ case WXK_RETURN:
+ if ( !HasFlag(wxTE_MULTILINE) )
+ {
+ wxCommandEvent event(wxEVT_COMMAND_TEXT_ENTER, m_windowId);
+ InitCommandEvent(event);
+ event.SetString(GetValue());
+ if ( GetEventHandler()->ProcessEvent(event) )
+ return;
+ }
+ //else: multiline controls need Enter for themselves
+
+ break;
+
+ case WXK_TAB:
+ // ok, so this is getting absolutely ridiculous but I don't see
+ // any other way to fix this bug: when a multiline text control is
+ // inside a wxFrame, we need to generate the navigation event as
+ // otherwise nothing happens at all, but when the same control is
+ // created inside a dialog, IsDialogMessage() *does* switch focus
+ // all by itself and so if we do it here as well, it is advanced
+ // twice and goes to the next control... to prevent this from
+ // happening we're doing this ugly check, the logic being that if
+ // we don't have focus then it had been already changed to the next
+ // control
+ //
+ // the right thing to do would, of course, be to understand what
+ // the hell is IsDialogMessage() doing but this is beyond my feeble
+ // forces at the moment unfortunately
+ if ( !(m_windowStyle & wxTE_PROCESS_TAB))
+ {
+ if ( FindFocus() == this )
+ {
+ int flags = 0;
+ if (!event.ShiftDown())
+ flags |= wxNavigationKeyEvent::IsForward ;
+ if (event.ControlDown())
+ flags |= wxNavigationKeyEvent::WinChange ;
+ if (Navigate(flags))
+ return;
+ }
+ }
+ else
+ {
+ // Insert tab since calling the default Windows handler
+ // doesn't seem to do it
+ WriteText(wxT("\t"));
+ return;
+ }
+ break;
+ }
+
+ // no, we didn't process it
+ event.Skip();
+}
+
+WXLRESULT wxTextCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
+{
+ WXLRESULT lRc = wxTextCtrlBase::MSWWindowProc(nMsg, wParam, lParam);
+
+ if ( nMsg == WM_GETDLGCODE )
+ {
+ // we always want the chars and the arrows: the arrows for navigation
+ // and the chars because we want Ctrl-C to work even in a read only
+ // control
+ long lDlgCode = DLGC_WANTCHARS | DLGC_WANTARROWS;
+
+ if ( IsEditable() )
+ {
+ // we may have several different cases:
+ // 1. normal case: both TAB and ENTER are used for dlg navigation
+ // 2. ctrl which wants TAB for itself: ENTER is used to pass to the
+ // next control in the dialog
+ // 3. ctrl which wants ENTER for itself: TAB is used for dialog
+ // navigation
+ // 4. ctrl which wants both TAB and ENTER: Ctrl-ENTER is used to go
+ // to the next control
+
+ // the multiline edit control should always get <Return> for itself
+ if ( HasFlag(wxTE_PROCESS_ENTER) || HasFlag(wxTE_MULTILINE) )
+ lDlgCode |= DLGC_WANTMESSAGE;
+
+ if ( HasFlag(wxTE_PROCESS_TAB) )
+ lDlgCode |= DLGC_WANTTAB;
+
+ lRc |= lDlgCode;
+ }
+ else // !editable
+ {
+ // NB: use "=", not "|=" as the base class version returns the
+ // same flags is this state as usual (i.e. including
+ // DLGC_WANTMESSAGE). This is strange (how does it work in the
+ // native Win32 apps?) but for now live with it.
+ lRc = lDlgCode;
+ }
+ }
+
+ return lRc;
+}
+
+// ----------------------------------------------------------------------------
+// text control event processing
+// ----------------------------------------------------------------------------
+
+bool wxTextCtrl::SendUpdateEvent()
+{
+ switch ( m_updatesCount )
+ {
+ case 0:
+ // remember that we've got an update
+ m_updatesCount++;
+ break;
+
+ case 1:
+ // we had already sent one event since the last control modification
+ return false;
+
+ default:
+ wxFAIL_MSG( _T("unexpected wxTextCtrl::m_updatesCount value") );
+ // fall through
+
+ case -1:
+ // we hadn't updated the control ourselves, this event comes from
+ // the user, don't need to ignore it nor update the count
+ break;
+ }
+
+ wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, GetId());
+ InitCommandEvent(event);
+
+ return ProcessCommand(event);
+}
+
+bool wxTextCtrl::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
+{
+ switch ( param )
+ {
+ case EN_SETFOCUS:
+ case EN_KILLFOCUS:
+ {
+ wxFocusEvent event(param == EN_KILLFOCUS ? wxEVT_KILL_FOCUS
+ : wxEVT_SET_FOCUS,
+ m_windowId);
+ event.SetEventObject(this);
+ GetEventHandler()->ProcessEvent(event);
+ }
+ break;
+
+ case EN_CHANGE:
+ SendUpdateEvent();
+ break;
+
+ case EN_MAXTEXT:
+ // the text size limit has been hit -- try to increase it
+ if ( !AdjustSpaceLimit() )
+ {
+ wxCommandEvent event(wxEVT_COMMAND_TEXT_MAXLEN, m_windowId);
+ InitCommandEvent(event);
+ event.SetString(GetValue());
+ ProcessCommand(event);
+ }
+ break;
+
+ // the other edit notification messages are not processed
+ default:
+ return false;
+ }
+
+ // processed
+ return true;
+}
+
+WXHBRUSH wxTextCtrl::MSWControlColor(WXHDC hDC, WXHWND hWnd)
+{
+ if ( !IsEnabled() && !HasFlag(wxTE_MULTILINE) )
+ return MSWControlColorDisabled(hDC);
+
+ return wxTextCtrlBase::MSWControlColor(hDC, hWnd);
+}
+
+bool wxTextCtrl::HasSpaceLimit(unsigned int *len) const
+{
+ // HACK: we try to automatically extend the limit for the amount of text
+ // to allow (interactively) entering more than 64Kb of text under
+ // Win9x but we shouldn't reset the text limit which was previously
+ // set explicitly with SetMaxLength()
+ //
+ // Unfortunately there is no EM_GETLIMITTEXTSETBYUSER and so we don't
+ // know the limit we set (if any). We could solve this by storing the
+ // limit we set in wxTextCtrl but to save space we prefer to simply
+ // test here the actual limit value: we consider that SetMaxLength()
+ // can only be called for small values while EN_MAXTEXT is only sent
+ // for large values (in practice the default limit seems to be 30000
+ // but make it smaller just to be on the safe side)
+ *len = ::SendMessage(GetHwnd(), EM_GETLIMITTEXT, 0, 0);
+ return *len < 10001;
+
+}
+
+bool wxTextCtrl::AdjustSpaceLimit()
+{
+ unsigned int limit;
+ if ( HasSpaceLimit(&limit) )
+ return false;
+
+ unsigned int len = ::GetWindowTextLength(GetHwnd());
+ if ( len >= limit )
+ {
+ // increment in 32Kb chunks
+ SetMaxLength(len + 0x8000);
+ }
+
+ // we changed the limit
+ return true;
+}
+
+bool wxTextCtrl::AcceptsFocus() const
+{
+ // we don't want focus if we can't be edited unless we're a multiline
+ // control because then it might be still nice to get focus from keyboard
+ // to be able to scroll it without mouse
+ return (IsEditable() || IsMultiLine()) && wxControl::AcceptsFocus();
+}
+
+wxSize wxTextCtrl::DoGetBestSize() const
+{
+ int cx, cy;
+ wxGetCharSize(GetHWND(), &cx, &cy, GetFont());
+
+ int wText = DEFAULT_ITEM_WIDTH;
+
+ int hText = cy;
+ if ( m_windowStyle & wxTE_MULTILINE )
+ {
+ hText *= wxMax(wxMin(GetNumberOfLines(), 10), 2);
+ }
+ //else: for single line control everything is ok
+
+ // we have to add the adjustments for the control height only once, not
+ // once per line, so do it after multiplication above
+ hText += EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy) - cy;
+
+ return wxSize(wText, hText);
+}
+
+// ----------------------------------------------------------------------------
+// standard handlers for standard edit menu events
+// ----------------------------------------------------------------------------
+
+void wxTextCtrl::OnCut(wxCommandEvent& WXUNUSED(event))
+{
+ Cut();
+}
+
+void wxTextCtrl::OnCopy(wxCommandEvent& WXUNUSED(event))