X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/0376ed540882131da043250a4341389f18f34bf6..9f7948af90ec2e3b907944e7d979f0863910c5c8:/src/msw/textctrl.cpp diff --git a/src/msw/textctrl.cpp b/src/msw/textctrl.cpp index f70aa21b7c..91ca4d8ce4 100644 --- a/src/msw/textctrl.cpp +++ b/src/msw/textctrl.cpp @@ -73,16 +73,6 @@ #endif // wxUSE_RICHEDIT -// ---------------------------------------------------------------------------- -// private functions -// ---------------------------------------------------------------------------- - -#if wxUSE_RICHEDIT - -DWORD CALLBACK wxRichEditStreamIn(DWORD dwCookie, BYTE *buf, LONG cb, LONG *pcb); - -#endif // wxUSE_RICHEDIT - // ---------------------------------------------------------------------------- // private classes // ---------------------------------------------------------------------------- @@ -135,14 +125,14 @@ wxBEGIN_FLAGS( wxTextCtrlStyle ) wxFLAGS_MEMBER(wxDOUBLE_BORDER) wxFLAGS_MEMBER(wxRAISED_BORDER) wxFLAGS_MEMBER(wxSTATIC_BORDER) - wxFLAGS_MEMBER(wxNO_BORDER) + wxFLAGS_MEMBER(wxBORDER) // standard window styles wxFLAGS_MEMBER(wxTAB_TRAVERSAL) wxFLAGS_MEMBER(wxCLIP_CHILDREN) wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW) wxFLAGS_MEMBER(wxWANTS_CHARS) - wxFLAGS_MEMBER(wxNO_FULL_REPAINT_ON_RESIZE) + wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE) wxFLAGS_MEMBER(wxALWAYS_SHOW_SB ) wxFLAGS_MEMBER(wxVSCROLL) wxFLAGS_MEMBER(wxHSCROLL) @@ -537,6 +527,52 @@ wxString wxTextCtrl::GetRange(long from, long to) const int len = GetWindowTextLength(GetHwnd()); if ( len > from ) { + if ( to == -1 ) + to = len; + +#if !wxUSE_UNICODE + // we must use EM_STREAMOUT if we don't want to lose all characters + // not representable in the current character set (EM_GETTEXTRANGE + // simply replaces them with question marks...) + if ( GetRichVersion() > 1 ) + { + // we must have some encoding, otherwise any 8bit chars in the + // control are simply *lost* (replaced by '?') + wxFontEncoding encoding = wxFONTENCODING_SYSTEM; + + wxFont font = m_defaultStyle.GetFont(); + if ( !font.Ok() ) + font = GetFont(); + + if ( font.Ok() ) + { + encoding = font.GetEncoding(); + } + + if ( encoding == wxFONTENCODING_SYSTEM ) + { + encoding = wxLocale::GetSystemEncoding(); + } + + if ( encoding == wxFONTENCODING_SYSTEM ) + { + encoding = wxFONTENCODING_ISO8859_1; + } + + str = StreamOut(encoding); + + if ( !str.empty() ) + { + // we have to manually extract the required part, luckily + // this is easy in this case as EOL characters in str are + // just LFs because we remove CRs in wxRichEditStreamOut + str = str.Mid(from, to - from); + } + } + + // StreamOut() wasn't used or failed, try to do it in normal way + if ( str.empty() ) +#endif // !wxUSE_UNICODE { // alloc one extra WORD as needed by the control wxStringBuffer tmp(str, ++len); @@ -544,7 +580,7 @@ wxString wxTextCtrl::GetRange(long from, long to) const TEXTRANGE textRange; textRange.chrg.cpMin = from; - textRange.chrg.cpMax = to == -1 ? len : to; + textRange.chrg.cpMax = to; textRange.lpstrText = p; (void)SendMessage(GetHwnd(), EM_GETTEXTRANGE, @@ -601,6 +637,9 @@ void wxTextCtrl::SetValue(const wxString& value) if ( (value.length() > 0x400) || (value != GetValue()) ) { DoWriteText(value, FALSE /* not selection only */); + + // for compatibility, don't move the cursor when doing SetValue() + SetInsertionPoint(0); } else // same text { @@ -613,19 +652,21 @@ void wxTextCtrl::SetValue(const wxString& value) // mark the control as being not dirty - we changed its text, not the // user DiscardEdits(); - - // for compatibility, don't move the cursor when doing SetValue() - SetInsertionPoint(0); } #if wxUSE_RICHEDIT && (!wxUSE_UNICODE || wxUSE_UNICODE_MSLU) -DWORD CALLBACK wxRichEditStreamIn(DWORD dwCookie, BYTE *buf, LONG cb, LONG *pcb) +// TODO: using memcpy() would improve performance a lot for big amounts of text + +DWORD CALLBACK +wxRichEditStreamIn(DWORD dwCookie, BYTE *buf, LONG cb, LONG *pcb) { *pcb = 0; + const wchar_t ** const ppws = (const wchar_t **)dwCookie; + wchar_t *wbuf = (wchar_t *)buf; - const wchar_t *wpc = *(const wchar_t **)dwCookie; + const wchar_t *wpc = *ppws; while ( cb && *wpc ) { *wbuf++ = *wpc++; @@ -634,56 +675,77 @@ DWORD CALLBACK wxRichEditStreamIn(DWORD dwCookie, BYTE *buf, LONG cb, LONG *pcb) (*pcb) += sizeof(wchar_t); } - *(const wchar_t **)dwCookie = wpc; + *ppws = wpc; return 0; } -// from utils.cpp -extern WXDLLIMPEXP_BASE long wxEncodingToCodepage(wxFontEncoding encoding); - -#if wxUSE_UNICODE_MSLU -bool wxTextCtrl::StreamIn(const wxString& value, - wxFontEncoding WXUNUSED(encoding), - bool selectionOnly) +// helper struct used to pass parameters from wxTextCtrl to wxRichEditStreamOut +struct wxStreamOutData { - const wchar_t *wpc = value.c_str(); -#else // !wxUSE_UNICODE_MSLU -bool wxTextCtrl::StreamIn(const wxString& value, - wxFontEncoding encoding, - bool selectionOnly) + wchar_t *wpc; + size_t len; +}; + +DWORD CALLBACK +wxRichEditStreamOut(DWORD dwCookie, BYTE *buf, LONG cb, LONG *pcb) { - // we have to use EM_STREAMIN to force richedit control 2.0+ to show any - // text in the non default charset -- otherwise it thinks it knows better - // than we do and always shows it in the default one + *pcb = 0; - // first get the Windows code page for this encoding - long codepage = wxEncodingToCodepage(encoding); - if ( codepage == -1 ) + wxStreamOutData *data = (wxStreamOutData *)dwCookie; + + const wchar_t *wbuf = (const wchar_t *)buf; + wchar_t *wpc = data->wpc; + while ( cb ) { - // unknown encoding - return FALSE; + wchar_t wch = *wbuf++; + + // turn "\r\n" into "\n" on the fly + if ( wch != L'\r' ) + *wpc++ = wch; + else + data->len--; + + cb -= sizeof(wchar_t); + (*pcb) += sizeof(wchar_t); } - // next translate to Unicode using this code page - int len = ::MultiByteToWideChar(codepage, 0, value, -1, NULL, 0); + data->wpc = wpc; + + return 0; +} + + +#if wxUSE_UNICODE_MSLU + #define UNUSED_IF_MSLU(param) +#else + #define UNUSED_IF_MSLU(param) param +#endif + +bool +wxTextCtrl::StreamIn(const wxString& value, + wxFontEncoding UNUSED_IF_MSLU(encoding), + bool selectionOnly) +{ +#if wxUSE_UNICODE_MSLU + const wchar_t *wpc = value.c_str(); +#else // !wxUSE_UNICODE_MSLU + wxCSConv conv(encoding); + + const size_t len = conv.MB2WC(NULL, value, value.length()); #if wxUSE_WCHAR_T wxWCharBuffer wchBuf(len); + wchar_t *wpc = wchBuf.data(); #else wchar_t *wchBuf = (wchar_t *)malloc((len + 1)*sizeof(wchar_t)); + wchar_t *wpc = wchBuf; #endif - if ( !::MultiByteToWideChar(codepage, 0, value, -1, - (wchar_t *)(const wchar_t *)wchBuf, len) ) - { - wxLogLastError(_T("MultiByteToWideChar")); - } - - // finally, stream it in the control - const wchar_t *wpc = wchBuf; + conv.MB2WC(wpc, value, value.length()); #endif // wxUSE_UNICODE_MSLU + // finally, stream it in the control EDITSTREAM eds; wxZeroMemory(eds); eds.dwCookie = (DWORD)&wpc; @@ -716,6 +778,69 @@ bool wxTextCtrl::StreamIn(const wxString& value, return TRUE; } +#if !wxUSE_UNICODE_MSLU + +wxString +wxTextCtrl::StreamOut(wxFontEncoding encoding, bool selectionOnly) const +{ + wxString out; + + const int len = GetWindowTextLength(GetHwnd()); + +#if wxUSE_WCHAR_T + wxWCharBuffer wchBuf(len); + wchar_t *wpc = wchBuf.data(); +#else + wchar_t *wchBuf = (wchar_t *)malloc((len + 1)*sizeof(wchar_t)); + wchar_t *wpc = wchBuf; +#endif + + wxStreamOutData data; + data.wpc = wpc; + data.len = len; + + EDITSTREAM eds; + wxZeroMemory(eds); + eds.dwCookie = (DWORD)&data; + eds.pfnCallback = wxRichEditStreamOut; + + ::SendMessage + ( + GetHwnd(), + EM_STREAMOUT, + SF_TEXT | SF_UNICODE | (selectionOnly ? SFF_SELECTION : 0), + (LPARAM)&eds + ); + + if ( eds.dwError ) + { + wxLogLastError(_T("EM_STREAMOUT")); + } + else // streamed out ok + { + // NUL-terminate the string because its length could have been + // decreased by wxRichEditStreamOut + *(wchBuf.data() + data.len) = L'\0'; + + // now convert to the given encoding (this is a possibly lossful + // conversion but what else can we do) + wxCSConv conv(encoding); + size_t lenNeeded = conv.WC2MB(NULL, wchBuf, 0); + if ( lenNeeded++ ) + { + conv.WC2MB(wxStringBuffer(out, lenNeeded), wchBuf, lenNeeded); + } + } + +#if !wxUSE_WCHAR_T + free(wchBuf); +#endif // !wxUSE_WCHAR_T + + return out; +} + +#endif // !wxUSE_UNICODE_MSLU + #endif // wxUSE_RICHEDIT void wxTextCtrl::WriteText(const wxString& value) @@ -767,6 +892,10 @@ void wxTextCtrl::DoWriteText(const wxString& value, bool selectionOnly) wxFontEncoding encoding = font.GetEncoding(); if ( encoding != wxFONTENCODING_SYSTEM ) { + // we have to use EM_STREAMIN to force richedit control 2.0+ + // to show any text in the non default charset -- otherwise + // it thinks it knows better than we do and always shows it + // in the default one done = StreamIn(valueDos, encoding, selectionOnly); } } @@ -1127,6 +1256,23 @@ void wxTextCtrl::DoSetSelection(long from, long to, bool scrollCaret) #endif // Win32/16 } +// ---------------------------------------------------------------------------- +// 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 // ---------------------------------------------------------------------------- @@ -1136,13 +1282,7 @@ 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 */); - SendMessage(GetHwnd(), EM_REPLACESEL, -#ifdef __WIN32__ - TRUE, -#else - FALSE, -#endif - (LPARAM)value.c_str()); + DoWriteText(value, TRUE /* selection only */); } void wxTextCtrl::Remove(long from, long to) @@ -1150,25 +1290,16 @@ void wxTextCtrl::Remove(long from, long to) Replace(from, to, wxEmptyString); } -bool wxTextCtrl::LoadFile(const wxString& file) +bool wxTextCtrl::IsModified() const { - if ( wxTextCtrlBase::LoadFile(file) ) - { - // update the size limit if needed - AdjustSpaceLimit(); - - return TRUE; - } - - return FALSE; + return SendMessage(GetHwnd(), EM_GETMODIFY, 0, 0) != 0; } -bool wxTextCtrl::IsModified() const +void wxTextCtrl::MarkDirty() { - return SendMessage(GetHwnd(), EM_GETMODIFY, 0, 0) != 0; + SendMessage(GetHwnd(), EM_SETMODIFY, TRUE, 0L); } -// Makes 'unmodified' void wxTextCtrl::DiscardEdits() { SendMessage(GetHwnd(), EM_SETMODIFY, FALSE, 0L); @@ -1179,6 +1310,10 @@ 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 @@ -1226,6 +1361,99 @@ bool wxTextCtrl::PositionToXY(long pos, long *x, long *y) const return TRUE; } +wxTextCtrlHitTestResult +wxTextCtrl::HitTest(const wxPoint& pt, wxTextCoord *col, wxTextCoord *row) 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; + + // finally translate to column/row + if ( !PositionToXY(pos, col, row) ) + { + wxFAIL_MSG( _T("PositionToXY() not expected to fail in HitTest()") ); + } + + return rc; +} + +// ---------------------------------------------------------------------------- +// +// ---------------------------------------------------------------------------- + void wxTextCtrl::ShowPosition(long pos) { HWND hWnd = GetHwnd(); @@ -1451,7 +1679,7 @@ void wxTextCtrl::OnChar(wxKeyEvent& event) switch ( event.GetKeyCode() ) { case WXK_RETURN: - if ( !(m_windowStyle & wxTE_MULTILINE) ) + if ( !HasFlag(wxTE_MULTILINE) ) { wxCommandEvent event(wxEVT_COMMAND_TEXT_ENTER, m_windowId); InitCommandEvent(event); @@ -1464,10 +1692,26 @@ void wxTextCtrl::OnChar(wxKeyEvent& event) break; case WXK_TAB: - // always produce navigation event - even if we process TAB + // always produce navigation event -- even if we process TAB // ourselves the fact that we got here means that the user code - // decided to skip processing of this TAB - probably to let it + // decided to skip processing of this TAB -- probably to let it // do its default job. + + // 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 ( FindFocus() == this ) { wxNavigationKeyEvent eventNav; eventNav.SetDirection(!event.ShiftDown());