X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/e015d1f7da1bff9cc3993519ef06ce7fe49dc64f..e2c922f53f6a8f08e8a82817c75981822dbe9ca1:/src/msw/textctrl.cpp diff --git a/src/msw/textctrl.cpp b/src/msw/textctrl.cpp index 882a6f60e9..7dd6a84fcf 100644 --- a/src/msw/textctrl.cpp +++ b/src/msw/textctrl.cpp @@ -5,15 +5,15 @@ // Modified by: // Created: 04/01/98 // RCS-ID: $Id$ -// Copyright: (c) Julian Smart and Markus Holzem -// Licence: wxWindows license +// Copyright: (c) Julian Smart +// Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // ============================================================================ // declarations // ============================================================================ -#ifdef __GNUG__ +#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) #pragma implementation "textctrl.h" #endif @@ -38,6 +38,7 @@ #include "wx/intl.h" #include "wx/log.h" #include "wx/app.h" + #include "wx/menu.h" #endif #include "wx/module.h" @@ -51,62 +52,24 @@ #include #include "wx/msw/private.h" +#include "wx/msw/winundef.h" #include #include -#include - -#if wxUSE_RICHEDIT && (!defined(__GNUWIN32_OLD__) || defined(__CYGWIN10__)) - #include -#endif - -// old mingw32 doesn't define this -#ifndef CFM_CHARSET - #define CFM_CHARSET 0x08000000 -#endif // CFM_CHARSET - -#ifndef CFM_BACKCOLOR - #define CFM_BACKCOLOR 0x04000000 -#endif - -// cygwin does not have these defined for richedit -#ifndef ENM_LINK - #define ENM_LINK 0x04000000 -#endif -#ifndef EM_AUTOURLDETECT - #define EM_AUTOURLDETECT (WM_USER + 91) +#ifndef __WXWINCE__ +#include #endif -#ifndef EN_LINK - #define EN_LINK 0x070b - - typedef struct _enlink - { - NMHDR nmhdr; - UINT msg; - WPARAM wParam; - LPARAM lParam; - CHARRANGE chrg; - } ENLINK; -#endif // ENLINK - -#ifndef SF_UNICODE - #define SF_UNICODE 0x0010 -#endif +#if wxUSE_RICHEDIT -// Watcom C++ doesn't define this -#ifndef SCF_ALL -#define SCF_ALL 0x0004 +// old mingw32 has richedit stuff directly in windows.h and doesn't have +// richedit.h at all +#if !defined(__GNUWIN32_OLD__) || defined(__CYGWIN10__) + #include #endif -// ---------------------------------------------------------------------------- -// private functions -// ---------------------------------------------------------------------------- - -#if wxUSE_RICHEDIT - -DWORD CALLBACK wxRichEditStreamIn(DWORD dwCookie, BYTE *buf, LONG cb, LONG *pcb); +#include "wx/msw/missing.h" #endif // wxUSE_RICHEDIT @@ -143,26 +106,104 @@ IMPLEMENT_DYNAMIC_CLASS(wxRichEditModule, wxModule) // event tables and other macros // ---------------------------------------------------------------------------- +#if wxUSE_EXTENDED_RTTI +WX_DEFINE_FLAGS( wxTextCtrlStyle ) + +wxBEGIN_FLAGS( wxTextCtrlStyle ) + // new style border flags, we put them first to + // use them for streaming out + wxFLAGS_MEMBER(wxBORDER_SIMPLE) + wxFLAGS_MEMBER(wxBORDER_SUNKEN) + wxFLAGS_MEMBER(wxBORDER_DOUBLE) + wxFLAGS_MEMBER(wxBORDER_RAISED) + wxFLAGS_MEMBER(wxBORDER_STATIC) + wxFLAGS_MEMBER(wxBORDER_NONE) + + // old style border flags + wxFLAGS_MEMBER(wxSIMPLE_BORDER) + wxFLAGS_MEMBER(wxSUNKEN_BORDER) + wxFLAGS_MEMBER(wxDOUBLE_BORDER) + wxFLAGS_MEMBER(wxRAISED_BORDER) + wxFLAGS_MEMBER(wxSTATIC_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(wxFULL_REPAINT_ON_RESIZE) + wxFLAGS_MEMBER(wxALWAYS_SHOW_SB ) + wxFLAGS_MEMBER(wxVSCROLL) + wxFLAGS_MEMBER(wxHSCROLL) + + wxFLAGS_MEMBER(wxTE_PROCESS_ENTER) + wxFLAGS_MEMBER(wxTE_PROCESS_TAB) + wxFLAGS_MEMBER(wxTE_MULTILINE) + wxFLAGS_MEMBER(wxTE_PASSWORD) + wxFLAGS_MEMBER(wxTE_READONLY) + wxFLAGS_MEMBER(wxHSCROLL) + wxFLAGS_MEMBER(wxTE_RICH) + wxFLAGS_MEMBER(wxTE_RICH2) + wxFLAGS_MEMBER(wxTE_AUTO_URL) + wxFLAGS_MEMBER(wxTE_NOHIDESEL) + wxFLAGS_MEMBER(wxTE_LEFT) + wxFLAGS_MEMBER(wxTE_CENTRE) + wxFLAGS_MEMBER(wxTE_RIGHT) + wxFLAGS_MEMBER(wxTE_DONTWRAP) + wxFLAGS_MEMBER(wxTE_LINEWRAP) + wxFLAGS_MEMBER(wxTE_WORDWRAP) + +wxEND_FLAGS( wxTextCtrlStyle ) + +IMPLEMENT_DYNAMIC_CLASS_XTI(wxTextCtrl, wxControl,"wx/textctrl.h") + +wxBEGIN_PROPERTIES_TABLE(wxTextCtrl) + wxEVENT_PROPERTY( TextUpdated , wxEVT_COMMAND_TEXT_UPDATED , wxCommandEvent ) + wxEVENT_PROPERTY( TextEnter , wxEVT_COMMAND_TEXT_ENTER , wxCommandEvent ) + + wxPROPERTY( Font , wxFont , SetFont , GetFont ,, 0 /*flags*/ , wxT("Helpstring") , wxT("group") ) + wxPROPERTY( Value , wxString , SetValue, GetValue, wxString() , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) + wxPROPERTY_FLAGS( WindowStyle , wxTextCtrlStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style +wxEND_PROPERTIES_TABLE() + +wxBEGIN_HANDLERS_TABLE(wxTextCtrl) +wxEND_HANDLERS_TABLE() + +wxCONSTRUCTOR_6( wxTextCtrl , wxWindow* , Parent , wxWindowID , Id , wxString , Value , wxPoint , Position , wxSize , Size , long , WindowStyle) +#else IMPLEMENT_DYNAMIC_CLASS(wxTextCtrl, wxControl) +#endif + BEGIN_EVENT_TABLE(wxTextCtrl, wxControl) EVT_CHAR(wxTextCtrl::OnChar) EVT_DROP_FILES(wxTextCtrl::OnDropFiles) +#if wxUSE_RICHEDIT + EVT_RIGHT_UP(wxTextCtrl::OnRightClick) +#endif + EVT_MENU(wxID_CUT, wxTextCtrl::OnCut) EVT_MENU(wxID_COPY, wxTextCtrl::OnCopy) EVT_MENU(wxID_PASTE, wxTextCtrl::OnPaste) EVT_MENU(wxID_UNDO, wxTextCtrl::OnUndo) EVT_MENU(wxID_REDO, wxTextCtrl::OnRedo) + EVT_MENU(wxID_CLEAR, wxTextCtrl::OnDelete) + EVT_MENU(wxID_SELECTALL, wxTextCtrl::OnSelectAll) EVT_UPDATE_UI(wxID_CUT, wxTextCtrl::OnUpdateCut) EVT_UPDATE_UI(wxID_COPY, wxTextCtrl::OnUpdateCopy) EVT_UPDATE_UI(wxID_PASTE, wxTextCtrl::OnUpdatePaste) EVT_UPDATE_UI(wxID_UNDO, wxTextCtrl::OnUpdateUndo) EVT_UPDATE_UI(wxID_REDO, wxTextCtrl::OnUpdateRedo) + EVT_UPDATE_UI(wxID_CLEAR, wxTextCtrl::OnUpdateDelete) + EVT_UPDATE_UI(wxID_SELECTALL, wxTextCtrl::OnUpdateSelectAll) #ifdef __WIN16__ EVT_ERASE_BACKGROUND(wxTextCtrl::OnEraseBackground) #endif + + EVT_SET_FOCUS(wxTextCtrl::OnSetFocus) END_EVENT_TABLE() // ============================================================================ @@ -177,9 +218,20 @@ void wxTextCtrl::Init() { #if wxUSE_RICHEDIT m_verRichEdit = 0; +#endif // wxUSE_RICHEDIT + m_privateContextMenu = NULL; m_suppressNextUpdate = FALSE; -#endif // wxUSE_RICHEDIT + m_isNativeCaretShown = true; +} + +wxTextCtrl::~wxTextCtrl() +{ + if (m_privateContextMenu) + { + delete m_privateContextMenu; + m_privateContextMenu = NULL; + } } bool wxTextCtrl::Create(wxWindow *parent, wxWindowID id, @@ -258,8 +310,6 @@ bool wxTextCtrl::Create(wxWindow *parent, wxWindowID id, // have we managed to load any richedit version? if ( !s_errorGiven ) { - msStyle |= ES_AUTOVSCROLL; - m_verRichEdit = verRichEdit; if ( m_verRichEdit == 1 ) { @@ -370,17 +420,9 @@ void wxTextCtrl::AdoptAttributesFromHWND() WXDWORD wxTextCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const { - // default border for the text controls is the sunken one - if ( (style & wxBORDER_MASK) == wxBORDER_DEFAULT ) - { - style |= wxBORDER_SUNKEN; - } - long msStyle = wxControl::MSWGetStyle(style, exstyle); - // default styles - msStyle |= ES_LEFT; - + // styles which we alaways add by default if ( style & wxTE_MULTILINE ) { wxASSERT_MSG( !(style & wxTE_PROCESS_ENTER), @@ -388,7 +430,20 @@ WXDWORD wxTextCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const msStyle |= ES_MULTILINE | ES_WANTRETURN; if ( !(style & wxTE_NO_VSCROLL) ) - msStyle |= WS_VSCROLL; + { + // always adjust the vertical scrollbar automatically if we have it + msStyle |= WS_VSCROLL | ES_AUTOVSCROLL; + +#if wxUSE_RICHEDIT + // we have to use this style for the rich edit controls because + // without it the vertical scrollbar never appears at all in + // richedit 3.0 because of our ECO_NOHIDESEL hack (search for it) + if ( style & wxTE_RICH2 ) + { + msStyle |= ES_DISABLENOSCROLL; + } +#endif // wxUSE_RICHEDIT + } style |= wxTE_PROCESS_ENTER; } @@ -399,8 +454,13 @@ WXDWORD wxTextCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const msStyle |= ES_AUTOHSCROLL; } - if ( style & wxHSCROLL ) - msStyle |= WS_HSCROLL | ES_AUTOHSCROLL; + // note that wxTE_DONTWRAP is the same as wxHSCROLL so if we have a horz + // scrollbar, there is no wrapping -- which makes sense + if ( style & wxTE_DONTWRAP ) + { + // automatically scroll the control horizontally as necessary + msStyle |= WS_HSCROLL; + } if ( style & wxTE_READONLY ) msStyle |= ES_READONLY; @@ -408,17 +468,16 @@ WXDWORD wxTextCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const if ( style & wxTE_PASSWORD ) msStyle |= ES_PASSWORD; - if ( style & wxTE_AUTO_SCROLL ) - msStyle |= ES_AUTOHSCROLL; - if ( style & wxTE_NOHIDESEL ) msStyle |= ES_NOHIDESEL; + // note that we can't do do "& wxTE_LEFT" as wxTE_LEFT == 0 if ( style & wxTE_CENTRE ) msStyle |= ES_CENTER; - - if ( style & wxTE_RIGHT ) + else if ( style & wxTE_RIGHT ) msStyle |= ES_RIGHT; + else + msStyle |= ES_LEFT; // ES_LEFT is 0 as well but for consistency... return msStyle; } @@ -468,29 +527,77 @@ wxString wxTextCtrl::GetRange(long from, long to) const int len = GetWindowTextLength(GetHwnd()); if ( len > from ) { - // alloc one extra WORD as needed by the control - wxChar *p = str.GetWriteBuf(++len); + if ( to == -1 ) + to = len; - TEXTRANGE textRange; - textRange.chrg.cpMin = from; - textRange.chrg.cpMax = to == -1 ? len : to; - textRange.lpstrText = p; +#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; - (void)SendMessage(GetHwnd(), EM_GETTEXTRANGE, 0, (LPARAM)&textRange); + wxFont font = m_defaultStyle.GetFont(); + if ( !font.Ok() ) + font = GetFont(); - if ( m_verRichEdit > 1 ) - { - // RichEdit 2.0 uses just CR ('\r') for the newlines which is - // neither Unix nor Windows style - convert it to something - // reasonable - for ( ; *p; p++ ) + if ( font.Ok() ) + { + encoding = font.GetEncoding(); + } + + if ( encoding == wxFONTENCODING_SYSTEM ) { - if ( *p == _T('\r') ) - *p = _T('\n'); + 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); } } - str.UngetWriteBuf(); + // 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); + wxChar *p = tmp; + + TEXTRANGE textRange; + textRange.chrg.cpMin = from; + textRange.chrg.cpMax = to; + textRange.lpstrText = p; + + (void)SendMessage(GetHwnd(), EM_GETTEXTRANGE, + 0, (LPARAM)&textRange); + + if ( m_verRichEdit > 1 ) + { + // RichEdit 2.0 uses just CR ('\r') for the + // newlines which is neither Unix nor Windows + // style - convert it to something reasonable + for ( ; *p; p++ ) + { + if ( *p == _T('\r') ) + *p = _T('\n'); + } + } + } if ( m_verRichEdit == 1 ) { @@ -530,24 +637,36 @@ void wxTextCtrl::SetValue(const wxString& value) if ( (value.length() > 0x400) || (value != GetValue()) ) { DoWriteText(value, FALSE /* not selection only */); - - // 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); } + else // same text + { + // still send an event for consistency + SendUpdateEvent(); + } + + // we should reset the modified flag even if the value didn't really change + + // mark the control as being not dirty - we changed its text, not the + // user + DiscardEdits(); } #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++; @@ -556,75 +675,98 @@ DWORD CALLBACK wxRichEditStreamIn(DWORD dwCookie, BYTE *buf, LONG cb, LONG *pcb) (*pcb) += sizeof(wchar_t); } - *(const wchar_t **)dwCookie = wpc; + *ppws = wpc; return 0; } -extern long wxEncodingToCodepage(wxFontEncoding encoding); // from utils.cpp +// helper struct used to pass parameters from wxTextCtrl to wxRichEditStreamOut +struct wxStreamOutData +{ + wchar_t *wpc; + size_t len; +}; -#ifdef __WXWINE__ -bool wxTextCtrl::StreamIn(const wxString& value, - wxFontEncoding WXUNUSED(encoding), - bool selectionOnly) +DWORD CALLBACK +wxRichEditStreamOut(DWORD dwCookie, BYTE *buf, LONG cb, LONG *pcb) { - return FALSE; + *pcb = 0; + + wxStreamOutData *data = (wxStreamOutData *)dwCookie; + + const wchar_t *wbuf = (const wchar_t *)buf; + wchar_t *wpc = data->wpc; + while ( cb ) + { + 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); + } + + data->wpc = wpc; + + return 0; } -#else + #if wxUSE_UNICODE_MSLU -bool wxTextCtrl::StreamIn(const wxString& value, - wxFontEncoding WXUNUSED(encoding), - bool selectionOnly) + #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 -bool wxTextCtrl::StreamIn(const wxString& value, - wxFontEncoding encoding, - bool selectionOnly) -{ - // 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 - - // first get the Windows code page for this encoding - long codepage = wxEncodingToCodepage(encoding); - if ( codepage == -1 ) - { - // unknown encoding - return FALSE; - } + wxCSConv conv(encoding); - // next translate to Unicode using this code page - int len = ::MultiByteToWideChar(codepage, 0, value, -1, NULL, 0); + 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; // the cast below is needed for broken (very) old mingw32 headers eds.pfnCallback = (EDITSTREAMCALLBACK)wxRichEditStreamIn; - if ( !::SendMessage(GetHwnd(), EM_STREAMIN, - SF_TEXT | - SF_UNICODE | - (selectionOnly ? SFF_SELECTION : 0), - (LPARAM)&eds) || eds.dwError ) + // we're going to receive 2 EN_CHANGE notifications if we got any selection + // (same problem as in DoWriteText()) + if ( selectionOnly && HasSelection() ) + { + // so suppress one of them + m_suppressNextUpdate = TRUE; + } + + ::SendMessage(GetHwnd(), EM_STREAMIN, + SF_TEXT | + SF_UNICODE | + (selectionOnly ? SFF_SELECTION : 0), + (LPARAM)&eds); + + if ( eds.dwError ) { wxLogLastError(_T("EM_STREAMIN")); } @@ -636,9 +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 - // __WXWINE__ - + + 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) @@ -676,7 +878,7 @@ void wxTextCtrl::DoWriteText(const wxString& value, bool selectionOnly) } #endif // wxUSE_UNICODE_MSLU -#if !wxUSE_UNICODE && !defined(__WXWINE__) +#if !wxUSE_UNICODE // next check if the text we're inserting must be shown in a non // default charset -- this only works for RichEdit > 1.0 if ( GetRichVersion() > 1 ) @@ -690,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); } } @@ -700,18 +906,38 @@ void wxTextCtrl::DoWriteText(const wxString& value, bool selectionOnly) if ( !done ) #endif // wxUSE_RICHEDIT { + // in some cases we get 2 EN_CHANGE notifications after the SendMessage + // call below which is confusing for the client code and so should be + // avoided + // + // these cases are: (a) plain EDIT controls if EM_REPLACESEL is used + // and there is a non empty selection currently and (b) rich text + // controls in any case + if ( #if wxUSE_RICHEDIT - // rich edit text control sends us 2 EN_CHANGE events when we send - // WM_SETTEXT to it, we have to suppress one of them to make wxTextCtrl - // behaviour consistent - if ( IsRich() ) + IsRich() || +#endif // wxUSE_RICHEDIT + (selectionOnly && HasSelection()) ) { m_suppressNextUpdate = TRUE; } -#endif // wxUSE_RICHEDIT ::SendMessage(GetHwnd(), selectionOnly ? EM_REPLACESEL : WM_SETTEXT, 0, (LPARAM)valueDos.c_str()); + + // OTOH, non rich text controls don't generate any events at all when + // we use WM_SETTEXT -- have to emulate them here + if ( !selectionOnly +#if wxUSE_RICHEDIT + && !IsRich() +#endif // wxUSE_RICHEDIT + ) + { + // Windows already sends an update event for single-line + // controls. + if ( m_windowStyle & wxTE_MULTILINE ) + SendUpdateEvent(); + } } AdjustSpaceLimit(); @@ -722,11 +948,20 @@ void wxTextCtrl::AppendText(const wxString& text) SetInsertionPointEnd(); WriteText(text); + +#if wxUSE_RICHEDIT + if ( IsMultiLine() && GetRichVersion() > 1 ) + { + // setting the caret to the end and showing it simply doesn't work for + // RichEdit 2.0 -- force it to still do what we want + ::SendMessage(GetHwnd(), EM_LINESCROLL, 0, GetNumberOfLines()); + } +#endif // wxUSE_RICHEDIT } void wxTextCtrl::Clear() { - ::SetWindowText(GetHwnd(), wxT("")); + ::SetWindowText(GetHwnd(), wxEmptyString); #if wxUSE_RICHEDIT if ( !IsRich() ) @@ -735,7 +970,11 @@ void wxTextCtrl::Clear() // rich edit controls send EN_UPDATE from WM_SETTEXT handler themselves // but the normal ones don't -- make Clear() behaviour consistent by // always sending this event - SendUpdateEvent(); + + // Windows already sends an update event for single-line + // controls. + if ( m_windowStyle & wxTE_MULTILINE ) + SendUpdateEvent(); } } @@ -786,14 +1025,19 @@ void wxTextCtrl::Paste() } } -bool wxTextCtrl::CanCopy() const +bool wxTextCtrl::HasSelection() const { - // Can copy if there's a selection long from, to; GetSelection(&from, &to); return from != to; } +bool wxTextCtrl::CanCopy() const +{ + // Can copy if there's a selection + return HasSelection(); +} + bool wxTextCtrl::CanCut() const { return CanCopy() && IsEditable(); @@ -840,6 +1084,14 @@ void wxTextCtrl::SetInsertionPoint(long pos) void wxTextCtrl::SetInsertionPointEnd() { + // we must not do anything if the caret is already there because calling + // SetInsertionPoint() thaws the controls if Freeze() had been called even + // if it doesn't actually move the caret anywhere and so the simple fact of + // doing it results in horrible flicker when appending big amounts of text + // to the control in a few chunks (see DoAddText() test in the text sample) + if ( GetInsertionPoint() == GetLastPosition() ) + return; + long pos; #if wxUSE_RICHEDIT @@ -959,7 +1211,44 @@ void wxTextCtrl::DoSetSelection(long from, long to, bool scrollCaret) if ( scrollCaret ) { +#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 } #else // Win16 // WPARAM is 0: selection is scrolled into view @@ -976,18 +1265,12 @@ 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) { - Replace(from, to, _T("")); + Replace(from, to, wxEmptyString); } bool wxTextCtrl::LoadFile(const wxString& file) @@ -1008,7 +1291,11 @@ bool wxTextCtrl::IsModified() const return SendMessage(GetHwnd(), EM_GETMODIFY, 0, 0) != 0; } -// Makes 'unmodified' +void wxTextCtrl::MarkDirty() +{ + SendMessage(GetHwnd(), EM_SETMODIFY, TRUE, 0L); +} + void wxTextCtrl::DiscardEdits() { SendMessage(GetHwnd(), EM_SETMODIFY, FALSE, 0L); @@ -1112,13 +1399,36 @@ wxString wxTextCtrl::GetLineText(long lineNo) const len += sizeof(WORD); wxString str; - wxChar *buf = str.GetWriteBuf(len); + { + 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 - *(WORD *)buf = (WORD)len; - len = (size_t)::SendMessage(GetHwnd(), EM_GETLINE, lineNo, (LPARAM)buf); - buf[len] = 0; + // 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--; + } - str.UngetWriteBuf(len); + buf[len] = 0; + tmp.SetLength(len); + } return str; } @@ -1159,6 +1469,27 @@ bool wxTextCtrl::CanRedo() const 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 // ---------------------------------------------------------------------------- @@ -1244,7 +1575,7 @@ bool wxTextCtrl::MSWShouldPreProcessMessage(WXMSG* pMsg) void wxTextCtrl::OnChar(wxKeyEvent& event) { - switch ( event.KeyCode() ) + switch ( event.GetKeyCode() ) { case WXK_RETURN: if ( !(m_windowStyle & wxTE_MULTILINE) ) @@ -1286,26 +1617,39 @@ long wxTextCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) if ( nMsg == WM_GETDLGCODE ) { - // we always want the chars and the arrows + // 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; - // we may have several different cases: - // 1. normal case: both TAB and ENTER are used for dialog 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 pass - // to the next control - - // the multiline edit control should always get for itself - if ( HasFlag(wxTE_PROCESS_ENTER) || HasFlag(wxTE_MULTILINE) ) - lDlgCode |= DLGC_WANTMESSAGE; - - if ( HasFlag(wxTE_PROCESS_TAB) ) - lDlgCode |= DLGC_WANTTAB; - - lRc |= lDlgCode; + 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 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; @@ -1393,11 +1737,6 @@ WXHBRUSH wxTextCtrl::OnCtlColor(WXHDC pDC, WXHWND WXUNUSED(pWnd), WXUINT WXUNUSE #endif // wxUSE_CTL3D HDC hdc = (HDC)pDC; - if (GetParent()->GetTransparentBackground()) - SetBkMode(hdc, TRANSPARENT); - else - SetBkMode(hdc, OPAQUE); - wxColour colBack = GetBackgroundColour(); if (!IsEnabled() && (GetWindowStyle() & wxTE_MULTILINE) == 0) @@ -1494,8 +1833,10 @@ bool wxTextCtrl::AdjustSpaceLimit() bool wxTextCtrl::AcceptsFocus() const { - // we don't want focus if we can't be edited - return IsEditable() && wxControl::AcceptsFocus(); + // 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 @@ -1544,6 +1885,19 @@ void wxTextCtrl::OnRedo(wxCommandEvent& WXUNUSED(event)) Redo(); } +void wxTextCtrl::OnDelete(wxCommandEvent& WXUNUSED(event)) +{ + long from, to; + GetSelection(& from, & to); + if (from != -1 && to != -1) + Remove(from, to); +} + +void wxTextCtrl::OnSelectAll(wxCommandEvent& WXUNUSED(event)) +{ + SetSelection(-1, -1); +} + void wxTextCtrl::OnUpdateCut(wxUpdateUIEvent& event) { event.Enable( CanCut() ); @@ -1569,6 +1923,53 @@ void wxTextCtrl::OnUpdateRedo(wxUpdateUIEvent& event) event.Enable( CanRedo() ); } +void wxTextCtrl::OnUpdateDelete(wxUpdateUIEvent& event) +{ + long from, to; + GetSelection(& from, & to); + event.Enable(from != -1 && to != -1 && from != to && IsEditable()) ; +} + +void wxTextCtrl::OnUpdateSelectAll(wxUpdateUIEvent& event) +{ + event.Enable(GetLastPosition() > 0); +} + +void wxTextCtrl::OnRightClick(wxMouseEvent& event) +{ +#if wxUSE_RICHEDIT + if (IsRich()) + { + if (!m_privateContextMenu) + { + m_privateContextMenu = new wxMenu; + m_privateContextMenu->Append(wxID_UNDO, _("&Undo")); + m_privateContextMenu->Append(wxID_REDO, _("&Redo")); + m_privateContextMenu->AppendSeparator(); + m_privateContextMenu->Append(wxID_CUT, _("Cu&t")); + m_privateContextMenu->Append(wxID_COPY, _("&Copy")); + m_privateContextMenu->Append(wxID_PASTE, _("&Paste")); + m_privateContextMenu->Append(wxID_CLEAR, _("&Delete")); + m_privateContextMenu->AppendSeparator(); + m_privateContextMenu->Append(wxID_SELECTALL, _("Select &All")); + } + PopupMenu(m_privateContextMenu, event.GetPosition()); + return; + } + else +#endif + event.Skip(); +} + +void wxTextCtrl::OnSetFocus(wxFocusEvent& WXUNUSED(event)) +{ + // be sure the caret remains invisible if the user had hidden it + if ( !m_isNativeCaretShown ) + { + ::HideCaret(GetHwnd()); + } +} + // the rest of the file only deals with the rich edit controls #if wxUSE_RICHEDIT @@ -1578,7 +1979,6 @@ void wxTextCtrl::OnUpdateRedo(wxUpdateUIEvent& event) bool wxTextCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) { -#ifndef __WXWINE__ NMHDR *hdr = (NMHDR* )lParam; switch ( hdr->code ) { @@ -1665,8 +2065,7 @@ bool wxTextCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) } return TRUE; } -#endif - + // not processed, leave it to the base class return wxTextCtrlBase::MSWOnNotify(idCtrl, lParam, result); } @@ -1723,9 +2122,6 @@ bool wxTextCtrl::SetForegroundColour(const wxColour& colour) bool wxTextCtrl::SetStyle(long start, long end, const wxTextAttr& style) { -#ifdef __WXWINE__ - return FALSE; -#else if ( !IsRich() ) { // can't do it with normal text control @@ -1734,7 +2130,9 @@ bool wxTextCtrl::SetStyle(long start, long end, const wxTextAttr& style) // the richedit 1.0 doesn't handle setting background colour, so don't // even try to do anything if it's the only thing we want to change - if ( m_verRichEdit == 1 && !style.HasFont() && !style.HasTextColour() ) + if ( m_verRichEdit == 1 && !style.HasFont() && !style.HasTextColour() && + !style.HasLeftIndent() && !style.HasRightIndent() && !style.HasAlignment() && + !style.HasTabs() ) { // nothing to do: return TRUE if there was really nothing to do and // FALSE if we failed to set bg colour @@ -1848,6 +2246,81 @@ bool wxTextCtrl::SetStyle(long start, long end, const wxTextAttr& style) wxLogDebug(_T("SendMessage(EM_SETCHARFORMAT, SCF_SELECTION) failed")); } + // now do the paragraph formatting + PARAFORMAT2 pf; + wxZeroMemory(pf); + // we can't use PARAFORMAT2 with RichEdit 1.0, so pretend it is a simple + // PARAFORMAT in that case +#if wxUSE_RICHEDIT2 + if ( m_verRichEdit == 1 ) + { + // this is the only thing the control is going to grok + pf.cbSize = sizeof(PARAFORMAT); + } + else +#endif + { + // PARAFORMAT or PARAFORMAT2 + pf.cbSize = sizeof(pf); + } + + if (style.HasAlignment()) + { + pf.dwMask |= PFM_ALIGNMENT; + if (style.GetAlignment() == wxTEXT_ALIGNMENT_RIGHT) + pf.wAlignment = PFA_RIGHT; + else if (style.GetAlignment() == wxTEXT_ALIGNMENT_CENTRE) + pf.wAlignment = PFA_CENTER; + else if (style.GetAlignment() == wxTEXT_ALIGNMENT_JUSTIFIED) + pf.wAlignment = PFA_JUSTIFY; + else + pf.wAlignment = PFA_LEFT; + } + + if (style.HasLeftIndent()) + { + pf.dwMask |= PFM_STARTINDENT; + + // Convert from 1/10 mm to TWIPS + pf.dxStartIndent = (int) (((double) style.GetLeftIndent()) * mm2twips / 10.0) ; + + // TODO: do we need to specify dxOffset? + } + + if (style.HasRightIndent()) + { + pf.dwMask |= PFM_RIGHTINDENT; + + // Convert from 1/10 mm to TWIPS + pf.dxRightIndent = (int) (((double) style.GetRightIndent()) * mm2twips / 10.0) ; + } + + if (style.HasTabs()) + { + pf.dwMask |= PFM_TABSTOPS; + + const wxArrayInt& tabs = style.GetTabs(); + + pf.cTabCount = wxMin(tabs.GetCount(), MAX_TAB_STOPS); + size_t i; + for (i = 0; i < (size_t) pf.cTabCount; i++) + { + // Convert from 1/10 mm to TWIPS + pf.rgxTabs[i] = (int) (((double) tabs[i]) * mm2twips / 10.0) ; + } + } + + if (pf.dwMask != 0) + { + // do format the selection + bool ok = ::SendMessage(GetHwnd(), EM_SETPARAFORMAT, + 0, (LPARAM) &pf) != 0; + if ( !ok ) + { + wxLogDebug(_T("SendMessage(EM_SETPARAFORMAT, 0) failed")); + } + } + if ( changeSel ) { // restore the original selection @@ -1855,7 +2328,6 @@ bool wxTextCtrl::SetStyle(long start, long end, const wxTextAttr& style) } return ok; -#endif } bool wxTextCtrl::SetDefaultStyle(const wxTextAttr& style) @@ -1871,6 +2343,137 @@ bool wxTextCtrl::SetDefaultStyle(const wxTextAttr& style) return TRUE; } +bool wxTextCtrl::GetStyle(long position, wxTextAttr& style) +{ + if ( !IsRich() ) + { + // can't do it with normal text control + return FALSE; + } + + // initialize CHARFORMAT struct +#if wxUSE_RICHEDIT2 + CHARFORMAT2 cf; +#else + CHARFORMAT cf; +#endif + + wxZeroMemory(cf); + + // we can't use CHARFORMAT2 with RichEdit 1.0, so pretend it is a simple + // CHARFORMAT in that case +#if wxUSE_RICHEDIT2 + if ( m_verRichEdit == 1 ) + { + // this is the only thing the control is going to grok + cf.cbSize = sizeof(CHARFORMAT); + } + else +#endif + { + // CHARFORMAT or CHARFORMAT2 + cf.cbSize = sizeof(cf); + } + // we can only change the format of the selection, so select the range we + // want and restore the old selection later + long startOld, endOld; + GetSelection(&startOld, &endOld); + + // but do we really have to change the selection? + bool changeSel = position != startOld || position != endOld; + + if ( changeSel ) + { + DoSetSelection(position, position, FALSE /* don't scroll caret into view */); + } + + // get the selection formatting + (void) ::SendMessage(GetHwnd(), EM_GETCHARFORMAT, + SCF_SELECTION, (LPARAM)&cf) ; + + LOGFONT lf; + lf.lfHeight = cf.yHeight; + lf.lfWidth = 0; + lf.lfCharSet = ANSI_CHARSET; // FIXME: how to get correct charset? + lf.lfClipPrecision = 0; + lf.lfEscapement = 0; + wxStrcpy(lf.lfFaceName, cf.szFaceName); + if (cf.dwEffects & CFE_ITALIC) + lf.lfItalic = TRUE; + lf.lfOrientation = 0; + lf.lfPitchAndFamily = cf.bPitchAndFamily; + lf.lfQuality = 0; + if (cf.dwEffects & CFE_STRIKEOUT) + lf.lfStrikeOut = TRUE; + if (cf.dwEffects & CFE_UNDERLINE) + lf.lfUnderline = TRUE; + if (cf.dwEffects & CFE_BOLD) + lf.lfWeight = FW_BOLD; + + wxFont font = wxCreateFontFromLogFont(& lf); + if (font.Ok()) + { + style.SetFont(font); + } + style.SetTextColour(wxColour(cf.crTextColor)); + +#if wxUSE_RICHEDIT2 + if ( m_verRichEdit != 1 ) + { + // cf.dwMask |= CFM_BACKCOLOR; + style.SetBackgroundColour(wxColour(cf.crBackColor)); + } +#endif // wxUSE_RICHEDIT2 + + // now get the paragraph formatting + PARAFORMAT2 pf; + wxZeroMemory(pf); + // we can't use PARAFORMAT2 with RichEdit 1.0, so pretend it is a simple + // PARAFORMAT in that case +#if wxUSE_RICHEDIT2 + if ( m_verRichEdit == 1 ) + { + // this is the only thing the control is going to grok + pf.cbSize = sizeof(PARAFORMAT); + } + else +#endif + { + // PARAFORMAT or PARAFORMAT2 + pf.cbSize = sizeof(pf); + } + + // do format the selection + (void) ::SendMessage(GetHwnd(), EM_GETPARAFORMAT, 0, (LPARAM) &pf) ; + + style.SetLeftIndent( (int) ((double) pf.dxStartIndent * twips2mm * 10.0) ); + style.SetRightIndent( (int) ((double) pf.dxRightIndent * twips2mm * 10.0) ); + + if (pf.wAlignment == PFA_CENTER) + style.SetAlignment(wxTEXT_ALIGNMENT_CENTRE); + else if (pf.wAlignment == PFA_RIGHT) + style.SetAlignment(wxTEXT_ALIGNMENT_RIGHT); + else if (pf.wAlignment == PFA_JUSTIFY) + style.SetAlignment(wxTEXT_ALIGNMENT_JUSTIFIED); + else + style.SetAlignment(wxTEXT_ALIGNMENT_LEFT); + + wxArrayInt tabStops; + size_t i; + for (i = 0; i < (size_t) pf.cTabCount; i++) + { + tabStops[i] = (int) ((double) (pf.rgxTabs[i] & 0xFFFF) * twips2mm * 10.0) ; + } + + if ( changeSel ) + { + // restore the original selection + DoSetSelection(startOld, endOld, FALSE); + } + + return TRUE; +} + #endif // ---------------------------------------------------------------------------- @@ -1890,6 +2493,7 @@ void wxRichEditModule::OnExit() if ( ms_hRichEdit[i] ) { ::FreeLibrary(ms_hRichEdit[i]); + ms_hRichEdit[i] = NULL; } } }