X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/ea98f11c2fbcd33ffc9b9771b8046c7353f51fcf..4f082fb3a0685321b174f8b7beb8ea357da227a2:/src/msw/textentry.cpp diff --git a/src/msw/textentry.cpp b/src/msw/textentry.cpp index 161035b59f..a91e8f33ca 100644 --- a/src/msw/textentry.cpp +++ b/src/msw/textentry.cpp @@ -34,6 +34,8 @@ #include "wx/textcompleter.h" #include "wx/dynlib.h" +#include + #include "wx/msw/private.h" #if wxUSE_UXTHEME @@ -43,7 +45,7 @@ #define GetEditHwnd() ((HWND)(GetEditHWND())) // ---------------------------------------------------------------------------- -// wxIEnumString implements IEnumString interface +// Classes used by auto-completion implementation. // ---------------------------------------------------------------------------- // standard VC6 SDK (WINVER == 0x0400) does not know about IAutoComplete @@ -55,11 +57,14 @@ #include "wx/msw/ole/oleutils.h" #include -#include #if defined(__MINGW32__) || defined (__WATCOMC__) || defined(__CYGWIN__) // needed for IID_IAutoComplete, IID_IAutoComplete2 and ACO_AUTOSUGGEST #include + + #ifndef ACO_AUTOAPPEND + #define ACO_AUTOAPPEND 0x02 + #endif #endif #ifndef ACO_UPDOWNKEYDROPSLIST @@ -70,24 +75,95 @@ #define SHACF_FILESYS_ONLY 0x00000010 #endif -DEFINE_GUID(CLSID_AutoComplete, +#ifndef SHACF_FILESYS_DIRS + #define SHACF_FILESYS_DIRS 0x00000020 +#endif + +namespace +{ + +// Normally this interface and its IID are defined in shobjidl.h header file +// included in the platform SDK but MinGW and Cygwin don't have it so redefine +// the interface ourselves and, as long as we do it all, do it for all +// compilers to ensure we have the same behaviour for all of them and to avoid +// the need to check for concrete compilers and maybe even their versions. +class IAutoCompleteDropDown : public IUnknown +{ +public: + virtual HRESULT wxSTDCALL GetDropDownStatus(DWORD *, LPWSTR *) = 0; + virtual HRESULT wxSTDCALL ResetEnumerator() = 0; +}; + +DEFINE_GUID(wxIID_IAutoCompleteDropDown, + 0x3cd141f4, 0x3c6a, 0x11d2, 0xbc, 0xaa, 0x00, 0xc0, 0x4f, 0xd9, 0x29, 0xdb); + +DEFINE_GUID(wxCLSID_AutoComplete, 0x00bb2763, 0x6a77, 0x11d0, 0xa5, 0x35, 0x00, 0xc0, 0x4f, 0xd7, 0xd0, 0x62); +// Small helper class which can be used to ensure thread safety even when +// wxUSE_THREADS==0 (and hence wxCriticalSection does nothing). +class CSLock +{ +public: + CSLock(CRITICAL_SECTION& cs) : m_cs(&cs) + { + ::EnterCriticalSection(m_cs); + } + + ~CSLock() + { + ::LeaveCriticalSection(m_cs); + } + +private: + CRITICAL_SECTION * const m_cs; + + wxDECLARE_NO_COPY_CLASS(CSLock); +}; + +} // anonymity namespace + +// Implementation of string enumerator used by wxTextAutoCompleteData. This +// class simply forwards to wxTextCompleter associated with it. +// +// Notice that Next() method of this class is called by IAutoComplete +// background thread and so we must care about thread safety here. class wxIEnumString : public IEnumString { public: wxIEnumString() { - m_index = 0; + Init(); } - wxIEnumString(const wxIEnumString& other) - : m_strings(other.m_strings), - m_index(other.m_index) + void ChangeCompleter(wxTextCompleter *completer) { + // Indicate to Next() that it should bail out as soon as possible. + { + CSLock lock(m_csRestart); + + m_restart = TRUE; + } + + // Now try to enter this critical section to ensure that Next() doesn't + // use the old pointer any more before changing it (this is vital as + // the old pointer will be destroyed after we return). + CSLock lock(m_csCompleter); + + m_completer = completer; } - DECLARE_IUNKNOWN_METHODS; + void UpdatePrefix(const wxString& prefix) + { + CSLock lock(m_csRestart); + + // We simply store the prefix here and will really update during the + // next call to our Next() method as we want to call Start() from the + // worker thread to prevent the main UI thread from blocking while the + // completions are generated. + m_prefix = prefix; + m_restart = TRUE; + } virtual HRESULT STDMETHODCALLTYPE Next(ULONG celt, LPOLESTR *rgelt, @@ -102,12 +178,22 @@ public: *pceltFetched = 0; - for ( const unsigned count = m_strings.size(); celt--; ++m_index ) + CSLock lock(m_csCompleter); + + if ( !RestartIfNeeded() ) + return S_FALSE; + + while ( celt-- ) { - if ( m_index == count ) + // Stop iterating if we need to update completions anyhow. + if ( m_restart ) + return S_FALSE; + + const wxString s = m_completer->GetNext(); + if ( s.empty() ) return S_FALSE; - const wxWX2WCbuf wcbuf = m_strings[m_index].wc_str(); + const wxWX2WCbuf wcbuf = s.wc_str(); const size_t size = (wcslen(wcbuf) + 1)*sizeof(wchar_t); void *olestr = CoTaskMemAlloc(size); if ( !olestr ) @@ -125,11 +211,21 @@ public: virtual HRESULT STDMETHODCALLTYPE Skip(ULONG celt) { - m_index += celt; - if ( m_index > m_strings.size() ) - { - m_index = m_strings.size(); + if ( !celt ) + return E_INVALIDARG; + + CSLock lock(m_csCompleter); + + if ( !RestartIfNeeded() ) return S_FALSE; + + while ( celt-- ) + { + if ( m_restart ) + return S_FALSE; + + if ( m_completer->GetNext().empty() ) + return S_FALSE; } return S_OK; @@ -137,7 +233,9 @@ public: virtual HRESULT STDMETHODCALLTYPE Reset() { - m_index = 0; + CSLock lock(m_csRestart); + + m_restart = TRUE; return S_OK; } @@ -147,28 +245,100 @@ public: if ( !ppEnum ) return E_POINTER; - wxIEnumString *e = new wxIEnumString(*this); + CSLock lock(m_csCompleter); + wxIEnumString * const e = new wxIEnumString; e->AddRef(); + + e->ChangeCompleter(m_completer); + *ppEnum = e; return S_OK; } + DECLARE_IUNKNOWN_METHODS; + private: // dtor doesn't have to be virtual as we're only ever deleted from our own // Release() and are not meant to be derived form anyhow, but making it // virtual silences gcc warnings; making it private makes it impossible to // (mistakenly) delete us directly instead of calling Release() - virtual ~wxIEnumString() { } + virtual ~wxIEnumString() + { + ::DeleteCriticalSection(&m_csRestart); + ::DeleteCriticalSection(&m_csCompleter); + } + + // Common part of all ctors. + void Init() + { + ::InitializeCriticalSection(&m_csCompleter); + ::InitializeCriticalSection(&m_csRestart); + + m_completer = NULL; + m_restart = FALSE; + } + + // Restart completions generation if needed. Should be only called from + // inside m_csCompleter. + // + // If false is returned, it means that there are no completions and that + // wxTextCompleter::GetNext() shouldn't be called at all. + bool RestartIfNeeded() + { + bool rc = true; + for ( ;; ) + { + wxString prefix; + LONG restart; + { + CSLock lock(m_csRestart); + + prefix = m_prefix; + restart = m_restart; + + m_restart = FALSE; + } // Release m_csRestart before calling Start() to avoid blocking + // the main thread in UpdatePrefix() during its execution. + + if ( !restart ) + break; + + rc = m_completer->Start(prefix); + } + + return rc; + } + + + // Critical section protecting m_completer itself. It must be entered when + // using the pointer to ensure that we don't continue using a dangling one + // after it is destroyed. + CRITICAL_SECTION m_csCompleter; + + // The completer we delegate to for the completions generation. It is never + // NULL after the initial ChangeCompleter() call. + wxTextCompleter *m_completer; + + + // Critical section m_prefix and m_restart. It should be only entered for + // short periods of time, i.e. we shouldn't call any wxTextCompleter + // methods from inside, to prevent the main thread from blocking on it in + // UpdatePrefix(). + CRITICAL_SECTION m_csRestart; + // If m_restart is true, we need to call wxTextCompleter::Start() with the + // given prefix to restart generating the completions. + wxString m_prefix; - wxArrayString m_strings; - unsigned m_index; + // Notice that we use LONG and not bool here to ensure that reading this + // value is atomic (32 bit reads are atomic operations under all Windows + // versions but reading bool isn't necessarily). + LONG m_restart; - friend class wxTextAutoCompleteData; - wxDECLARE_NO_ASSIGN_CLASS(wxIEnumString); + wxDECLARE_NO_COPY_CLASS(wxIEnumString); }; BEGIN_IID_TABLE(wxIEnumString) @@ -179,8 +349,8 @@ END_IID_TABLE; IMPLEMENT_IUNKNOWN_METHODS(wxIEnumString) -// This class gathers the auto-complete-related we use. It is allocated on -// demand by wxTextEntry when AutoComplete() is called. +// This class gathers the all auto-complete-related stuff we use. It is +// allocated on demand by wxTextEntry when AutoComplete() is called. class wxTextAutoCompleteData wxBIND_OR_CONNECT_HACK_ONLY_BASE_CLASS { public: @@ -192,16 +362,18 @@ public: m_autoComplete = NULL; m_autoCompleteDropDown = NULL; m_enumStrings = NULL; + + m_fixedCompleter = NULL; m_customCompleter = NULL; - m_connectedTextChangedEvent = false; + m_connectedCharEvent = false; // Create an object exposing IAutoComplete interface which we'll later // use to get IAutoComplete2 as the latter can't be created directly, // apparently. HRESULT hr = CoCreateInstance ( - CLSID_AutoComplete, + wxCLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, IID_IAutoComplete, @@ -236,7 +408,7 @@ public: // provided IAutoComplete always implements IAutoCompleteDropDown too. hr = m_autoComplete->QueryInterface ( - IID_IAutoCompleteDropDown, + wxIID_IAutoCompleteDropDown, reinterpret_cast(&m_autoCompleteDropDown) ); if ( FAILED(hr) ) @@ -255,6 +427,7 @@ public: if ( SUCCEEDED(hr) ) { pAutoComplete2->SetOptions(ACO_AUTOSUGGEST | + ACO_AUTOAPPEND | ACO_UPDOWNKEYDROPSLIST); pAutoComplete2->Release(); } @@ -263,6 +436,7 @@ public: ~wxTextAutoCompleteData() { delete m_customCompleter; + delete m_fixedCompleter; if ( m_enumStrings ) m_enumStrings->Release(); @@ -281,7 +455,12 @@ public: void ChangeStrings(const wxArrayString& strings) { - m_enumStrings->m_strings = strings; + if ( !m_fixedCompleter ) + m_fixedCompleter = new wxTextCompleterFixed; + + m_fixedCompleter->SetCompletions(strings); + + m_enumStrings->ChangeCompleter(m_fixedCompleter); DoRefresh(); } @@ -289,6 +468,10 @@ public: // Takes ownership of the pointer if it is non-NULL. bool ChangeCustomCompleter(wxTextCompleter *completer) { + // Ensure that the old completer is not used any more before deleting + // it. + m_enumStrings->ChangeCompleter(completer); + delete m_customCompleter; m_customCompleter = completer; @@ -297,13 +480,25 @@ public: // We postpone connecting to this event until we really need to do // it (however we don't disconnect from it when we don't need it // any more because we don't have wxUNBIND_OR_DISCONNECT_HACK...). - if ( !m_connectedTextChangedEvent ) + if ( !m_connectedCharEvent ) { - m_connectedTextChangedEvent = true; - - wxBIND_OR_CONNECT_HACK(m_win, wxEVT_COMMAND_TEXT_UPDATED, - wxCommandEventHandler, - wxTextAutoCompleteData::OnTextChanged, + m_connectedCharEvent = true; + + // Use the special wxEVT_AFTER_CHAR and not the usual + // wxEVT_CHAR here because we need to have the updated value of + // the text control in this handler in order to provide + // completions for the correct prefix and unfortunately we + // don't have any way to let DefWindowProc() run from our + // wxEVT_CHAR handler (as we must also let the other handlers + // defined at wx level run first). + // + // Notice that we can't use wxEVT_COMMAND_TEXT_UPDATED here + // neither as, due to our use of ACO_AUTOAPPEND, we get + // EN_CHANGE notifications from the control every time + // IAutoComplete auto-appends something to it. + wxBIND_OR_CONNECT_HACK(m_win, wxEVT_AFTER_CHAR, + wxKeyEventHandler, + wxTextAutoCompleteData::OnAfterChar, this); } @@ -319,16 +514,12 @@ public: // to effectively disable auto-completion just fine. We could (and // probably should) use IAutoComplete::Enable(FALSE) for this too but // then we'd need to call Enable(TRUE) to turn it on back again later. - - m_enumStrings->m_strings.clear(); - m_enumStrings->Reset(); - - ChangeCustomCompleter(NULL); + ChangeStrings(wxArrayString()); } private: - // Must be called after changing wxIEnumString::m_strings to really make - // the changes stick. + // Must be called after changing the values to be returned by wxIEnumString + // to really make the changes stick. void DoRefresh() { m_enumStrings->Reset(); @@ -348,19 +539,28 @@ private: // the currently valid choices according to the custom completer. void UpdateStringsFromCustomCompleter() { - // For efficiency we access m_strings directly instead of creating - // another wxArrayString, normally this should save us an unnecessary - // memory allocation on the subsequent calls. - m_enumStrings->m_strings.clear(); - m_customCompleter->GetCompletions(m_entry->GetValue(), - m_enumStrings->m_strings); + // As we use ACO_AUTOAPPEND, the selected part of the text is usually + // the one appended by us so don't consider it as part of the + // user-entered prefix. + long from, to; + m_entry->GetSelection(&from, &to); + + if ( to == from ) + from = m_entry->GetLastPosition(); // Take all if no selection. + + const wxString prefix = m_entry->GetRange(0, from); + + m_enumStrings->UpdatePrefix(prefix); DoRefresh(); } - void OnTextChanged(wxCommandEvent& event) + void OnAfterChar(wxKeyEvent& event) { - if ( m_customCompleter ) + // Notice that we must not refresh the completions when the user + // presses Backspace as this would result in adding back the just + // erased character(s) because of ACO_AUTOAPPEND option we use. + if ( m_customCompleter && event.GetKeyCode() != WXK_BACK ) UpdateStringsFromCustomCompleter(); event.Skip(); @@ -382,11 +582,14 @@ private: // Enumerator for strings currently used for auto-completion. wxIEnumString *m_enumStrings; + // Fixed string completer or NULL if none. + wxTextCompleterFixed *m_fixedCompleter; + // Custom completer or NULL if none. wxTextCompleter *m_customCompleter; // Initially false, set to true after connecting OnTextChanged() handler. - bool m_connectedTextChangedEvent; + bool m_connectedCharEvent; wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteData); @@ -422,7 +625,7 @@ wxTextEntry::~wxTextEntry() void wxTextEntry::WriteText(const wxString& text) { - ::SendMessage(GetEditHwnd(), EM_REPLACESEL, 0, (LPARAM)text.wx_str()); + ::SendMessage(GetEditHwnd(), EM_REPLACESEL, 0, wxMSW_CONV_LPARAM(text)); } wxString wxTextEntry::DoGetValue() const @@ -541,7 +744,9 @@ void wxTextEntry::GetSelection(long *from, long *to) const #ifdef HAS_AUTOCOMPLETE -bool wxTextEntry::DoAutoCompleteFileNames() +#if wxUSE_DYNLIB_CLASS + +bool wxTextEntry::DoAutoCompleteFileNames(int flags) { typedef HRESULT (WINAPI *SHAutoComplete_t)(HWND, DWORD); static SHAutoComplete_t s_pfnSHAutoComplete = (SHAutoComplete_t)-1; @@ -561,7 +766,18 @@ bool wxTextEntry::DoAutoCompleteFileNames() if ( !s_pfnSHAutoComplete ) return false; - HRESULT hr = (*s_pfnSHAutoComplete)(GetEditHwnd(), SHACF_FILESYS_ONLY); + DWORD dwFlags = 0; + if ( flags & wxFILE ) + dwFlags |= SHACF_FILESYS_ONLY; + else if ( flags & wxDIR ) + dwFlags |= SHACF_FILESYS_DIRS; + else + { + wxFAIL_MSG(wxS("No flags for file name auto completion?")); + return false; + } + + HRESULT hr = (*s_pfnSHAutoComplete)(GetEditHwnd(), dwFlags); if ( FAILED(hr) ) { wxLogApiError(wxT("SHAutoComplete()"), hr); @@ -577,6 +793,8 @@ bool wxTextEntry::DoAutoCompleteFileNames() return true; } +#endif // wxUSE_DYNLIB_CLASS + wxTextAutoCompleteData *wxTextEntry::GetOrCreateCompleter() { if ( !m_autoCompleteData ) @@ -634,9 +852,9 @@ bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter *completer) // We still need to define stubs as we declared these overrides in the header. -bool wxTextEntry::DoAutoCompleteFileNames() +bool wxTextEntry::DoAutoCompleteFileNames(int flags) { - return wxTextEntryBase::DoAutoCompleteFileNames(); + return wxTextEntryBase::DoAutoCompleteFileNames(flags); } bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices) @@ -644,6 +862,11 @@ bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices) return wxTextEntryBase::DoAutoCompleteStrings(choices); } +bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter *completer) +{ + return wxTextEntryBase::DoAutoCompleteCustom(completer); +} + #endif // HAS_AUTOCOMPLETE/!HAS_AUTOCOMPLETE #endif // wxUSE_OLE @@ -733,9 +956,12 @@ bool wxTextEntry::DoSetMargins(const wxPoint& margins) if ( margins.x != -1 ) { - // left margin + // Set both horizontal margins to the given value, we don't distinguish + // between left and right margin at wx API level and it seems to be + // better to change both of them than only left one. ::SendMessage(GetEditHwnd(), EM_SETMARGINS, - EC_LEFTMARGIN, MAKELONG(margins.x, 0)); + EC_LEFTMARGIN | EC_RIGHTMARGIN, + MAKELONG(margins.x, margins.x)); } if ( margins.y != -1 )