diff --git a/include/wx/msw/textctrl.h b/include/wx/msw/textctrl.h index d7a478f8c8..97f45501a8 100644 --- a/include/wx/msw/textctrl.h +++ b/include/wx/msw/textctrl.h @@ -40,6 +40,12 @@ public: virtual wxString GetValue() const override; virtual wxString GetRange(long from, long to) const override; +#if wxUSE_RICHEDIT + virtual wxString GetRTFValue() const override; + virtual void SetRTFValue(const wxString& val) override; + virtual bool IsRTFSupported() override { return IsRich(); } +#endif // wxUSE_RICHEDIT + virtual bool IsEmpty() const; virtual void WriteText(const wxString& text) override; diff --git a/include/wx/osx/core/private.h b/include/wx/osx/core/private.h index 5eea1ad09d..3d868ab53b 100644 --- a/include/wx/osx/core/private.h +++ b/include/wx/osx/core/private.h @@ -718,6 +718,8 @@ public : virtual bool CanClipMaxLength() const { return false; } virtual void SetMaxLength(unsigned long WXUNUSED(len)) {} + virtual bool IsRTFSupported() { return false; } + virtual bool CanForceUpper() { return false; } virtual void ForceUpper() {} diff --git a/include/wx/osx/textctrl.h b/include/wx/osx/textctrl.h index 1dc6ea6031..acfce799af 100644 --- a/include/wx/osx/textctrl.h +++ b/include/wx/osx/textctrl.h @@ -111,6 +111,7 @@ public: virtual wxString GetRTFValue() const override; virtual void SetRTFValue(const wxString& val) override; + virtual bool IsRTFSupported() override { return IsMultiLine(); } // callbacks void OnDropFiles(wxDropFilesEvent& event); diff --git a/include/wx/textctrl.h b/include/wx/textctrl.h index 4e81eae4f7..44956d4829 100644 --- a/include/wx/textctrl.h +++ b/include/wx/textctrl.h @@ -103,10 +103,10 @@ const wxTextCoord wxInvalidTextCoord = -2; // wxTextCtrl file types // ---------------------------------------------------------------------------- -// For now only wxOSX supports RTF in wxTextCtrl. -#ifdef __WXOSX__ +// wxOSX and wxMSW support RTF in wxTextCtrl. +#if defined(__WXOSX__) || (defined(__WXMSW__) && wxUSE_RICHEDIT) #define wxHAS_TEXTCTRL_RTF -#endif // __WXOSX__ +#endif enum wxTextCtrlFileType { @@ -691,7 +691,7 @@ public: virtual void SetValue(const wxString& value) = 0; // Returns whether the RTF-related functions below can be used. - static bool IsRTFSupported(); + virtual bool IsRTFSupported() { return false; } // Base class implementations simply assert, if IsRTFSupported() returns // true, the port must override these functions to really implement them. diff --git a/interface/wx/textctrl.h b/interface/wx/textctrl.h index c1d63a1c92..f0fb0f97ce 100644 --- a/interface/wx/textctrl.h +++ b/interface/wx/textctrl.h @@ -76,7 +76,7 @@ enum wxTextCtrlFileType /** Rich Text Format. - This format is only supported under macOS currently. + This format is only supported under macOS and MSW. @since 3.3.0 */ @@ -1659,14 +1659,15 @@ public: Returns @c true if text controls support reading and writing RTF (Rich Text Format). - Currently this function only returns true in wxOSX, which is the only - port implementing RTF support in wxTextCtrl. + This function only returns @true in wxOSX (for multiline contrls) and + wxMSW (for rich controls), which are the only ports implementing + RTF support in wxTextCtrl. @since 3.3.0 @see GetRTFValue(), SetRTFValue() */ - static bool IsRTFSupported(); + bool IsRTFSupported(); /** Returns the content of a multiline text control as RTF (Rich Text diff --git a/samples/text/text.cpp b/samples/text/text.cpp index 53e9ddde7b..f9009fa5cc 100644 --- a/samples/text/text.cpp +++ b/samples/text/text.cpp @@ -479,13 +479,12 @@ bool MyApp::OnInit() file_menu->Append(TEXT_LOAD, "&Load file\tCtrl-O", "Load the sample file into text control"); - if ( wxTextCtrl::IsRTFSupported() ) - { +#ifdef wxHAS_TEXTCTRL_RTF file_menu->Append(TEXT_SAVE_RTF, "&Save file as RTF", "Save the text control contents to file"); file_menu->Append(TEXT_LOAD_RTF, "&Load RTF file", "Load the sample file into text control"); - } +#endif file_menu->AppendSeparator(); file_menu->Append(TEXT_RICH_TEXT_TEST, "Show Rich Text Editor"); @@ -534,11 +533,10 @@ bool MyApp::OnInit() menuText->Append(TEXT_SELECT, "&Select characters 4 to 8\tCtrl-I"); menuText->Append(TEXT_SET, "&Set the first text zone value\tCtrl-E"); menuText->Append(TEXT_CHANGE, "&Change the first text zone value\tShift-Ctrl-E"); - if ( wxTextCtrl::IsRTFSupported() ) - { +#ifdef wxHAS_TEXTCTRL_RTF menuText->AppendSeparator(); menuText->Append(TEXT_SET_RTF, "Set the rich text zone value from rich text formatted content"); - } +#endif menuText->AppendSeparator(); menuText->Append(TEXT_MOVE_ENDTEXT, "Move cursor to the end of &text"); menuText->Append(TEXT_MOVE_ENDENTRY, "Move cursor to the end of &entry"); diff --git a/src/common/textcmn.cpp b/src/common/textcmn.cpp index 7ef11e3041..d31f761f9d 100644 --- a/src/common/textcmn.cpp +++ b/src/common/textcmn.cpp @@ -931,16 +931,6 @@ bool wxTextCtrlBase::SetDefaultStyle(const wxTextAttr& style) return true; } -// static -bool wxTextAreaBase::IsRTFSupported() -{ -#ifdef wxHAS_TEXTCTRL_RTF - return true; -#else - return false; -#endif -} - wxString wxTextAreaBase::GetRTFValue() const { wxFAIL_MSG("Not implemented for the current platform."); diff --git a/src/msw/textctrl.cpp b/src/msw/textctrl.cpp index 56ca8d80a7..3c9da618d0 100644 --- a/src/msw/textctrl.cpp +++ b/src/msw/textctrl.cpp @@ -1114,6 +1114,92 @@ void wxTextCtrl::DoSetValue(const wxString& value, int flags) } } +#if wxUSE_RICHEDIT + +DWORD wxCALLBACK MSWEditStreamOutCallback(DWORD_PTR dwCookie, LPBYTE pbBuff, + LONG cb, LONG* pcb) +{ + // write the text + std::string* psEntry = reinterpret_cast(dwCookie); + psEntry->append(reinterpret_cast(pbBuff), cb); + *pcb = cb; + + return 0; +} + +wxString wxTextCtrl::GetRTFValue() const +{ + if ( !IsRich() ) + { + wxFAIL_MSG("RTF support is only available for rich controls!"); + return wxEmptyString; + } + + // The Rich Edit control must write to a char buffer, + // which we will later convert to a Unicode wxString using the current code page. + // + // We will reserve enough space assuming that the RTF will be twice as large + // as the unencoded content. + std::string buffer; + buffer.reserve(GetLastPosition() * 2); + + // Use a EDITSTREAMCALLBACK to stream text out from the control. + EDITSTREAM es{ 0 }; + es.dwError = 0; + es.pfnCallback = MSWEditStreamOutCallback; + // our callback will write to a char buffer (i.e., std::string) + es.dwCookie = reinterpret_cast(&buffer); + + // Do NOT call with "(CP_UTF8 << 16) | SF_USECODEPAGE", as this will format the + // output RTF to UTF-8 with a "urtf1" header (note the 'u'). + // Although this is a more modern format, few programs + // (including Microsoft Wordpad) recognize it. + // + // Instead, write the content to an ANSI string using the current code page + // (which will use a "rtf1" header). The control will encode any Unicode values + // outside of the current code page with "\'[0-9A-F][0-9A-F]" syntax that most + // parsers would support. + // + // Finally, use SFF_PLAINRTF so that the generated RTF is more portable between + // RTF readers. + ::SendMessage(GetHwnd(), EM_STREAMOUT, + (WPARAM)(SF_RTF | SFF_PLAINRTF), (LPARAM)&es); + + // Valid RTF is supposed to consist of 7-bit ASCII characters only, + // anything else is escaped. + return wxString::FromAscii(buffer.c_str(), buffer.length()); +} + +void wxTextCtrl::SetRTFValue(const wxString& val) +{ + wxCHECK_RET(IsRich(), "RTF support is only available for rich controls!"); + + SETTEXTEX textInfo{ 0 }; + textInfo.flags = ST_DEFAULT | ST_UNICODE; + textInfo.codepage = 1200; + + // Setting from Unicode will fail if control is read-only + // (this is an undocumented "feature"), so we need to toggle that temporarily. + const bool + isReadOnly = (::GetWindowLong(GetHwnd(), GWL_STYLE) & ES_READONLY) != 0; + if ( isReadOnly ) + { + ::SendMessage(GetHwnd(), EM_SETREADONLY, FALSE, 0); + } + + ::SendMessage(GetHwnd(), EM_SETTEXTEX, (WPARAM)&textInfo, + (LPARAM)static_cast(val.wc_str())); + + if ( isReadOnly ) + { + ::SendMessage(GetHwnd(), EM_SETREADONLY, TRUE, 0); + } + + SetInsertionPoint(0); +} + +#endif // wxUSE_RICHEDIT + void wxTextCtrl::WriteText(const wxString& value) { DoWriteText(value);