X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/e71e4c932a4e9e77207f968b150b51d5009408b5..b404a8f3b072129c107c6d9a5e0f6f53cd34807b:/src/msw/textentry.cpp diff --git a/src/msw/textentry.cpp b/src/msw/textentry.cpp index d0bde2fad6..77bc4cd1bc 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 @@ -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,12 +75,27 @@ #define SHACF_FILESYS_ONLY 0x00000010 #endif -DEFINE_GUID(CLSID_AutoComplete, - 0x00bb2763, 0x6a77, 0x11d0, 0xa5, 0x35, 0x00, 0xc0, 0x4f, 0xd7, 0xd0, 0x62); - 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 @@ -100,8 +120,7 @@ private: } // anonymity namespace // Implementation of string enumerator used by wxTextAutoCompleteData. This -// class simply iterates over its strings except that it also can be told to -// update them from the custom completer if one is used. +// 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. @@ -111,36 +130,35 @@ public: wxIEnumString() { Init(); - - m_index = 0; } - wxIEnumString(const wxIEnumString& other) - : m_strings(other.m_strings), - m_index(other.m_index) + void ChangeCompleter(wxTextCompleter *completer) { - Init(); - } + // Indicate to Next() that it should bail out as soon as possible. + { + CSLock lock(m_csRestart); - void ChangeStrings(const wxArrayString& strings) - { - CSLock lock(m_csStrings); + m_restart = TRUE; + } - m_strings = strings; - m_index = 0; + // 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; } - void UpdateStringsFromCompleter(wxTextCompleter *completer, - const wxString& prefix) + void UpdatePrefix(const wxString& prefix) { - CSLock lock(m_csUpdate); + CSLock lock(m_csRestart); - // We simply store the pointer here and will really update during the - // next call to our Next() method as we want to call GetCompletions() - // from the worker thread to prevent the main UI thread from blocking - // while the completions are generated. - m_completer = completer; + // 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, @@ -156,16 +174,22 @@ public: *pceltFetched = 0; - CSLock lock(m_csStrings); + CSLock lock(m_csCompleter); - DoUpdateIfNeeded(); + if ( !RestartIfNeeded() ) + return S_FALSE; - for ( const unsigned count = m_strings.size(); celt--; ++m_index ) + 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 ) @@ -183,13 +207,21 @@ public: virtual HRESULT STDMETHODCALLTYPE Skip(ULONG celt) { - CSLock lock(m_csStrings); + if ( !celt ) + return E_INVALIDARG; - m_index += celt; - if ( m_index > m_strings.size() ) - { - m_index = m_strings.size(); + 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; @@ -197,22 +229,9 @@ public: virtual HRESULT STDMETHODCALLTYPE Reset() { - { - CSLock lock(m_csUpdate); - - if ( m_completer ) - { - // We will update the string from completer soon (or maybe are - // already in process of doing it) so don't do anything now, it - // is useless at best and could also result in a deadlock if - // m_csStrings is already locked by Next(). - return S_OK; - } - } - - CSLock lock(m_csStrings); + CSLock lock(m_csRestart); - m_index = 0; + m_restart = TRUE; return S_OK; } @@ -222,11 +241,13 @@ public: if ( !ppEnum ) return E_POINTER; - CSLock lock(m_csStrings); - - 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; @@ -241,79 +262,79 @@ private: // (mistakenly) delete us directly instead of calling Release() virtual ~wxIEnumString() { - ::DeleteCriticalSection(&m_csStrings); - ::DeleteCriticalSection(&m_csUpdate); + ::DeleteCriticalSection(&m_csRestart); + ::DeleteCriticalSection(&m_csCompleter); } // Common part of all ctors. void Init() { - ::InitializeCriticalSection(&m_csUpdate); - ::InitializeCriticalSection(&m_csStrings); + ::InitializeCriticalSection(&m_csCompleter); + ::InitializeCriticalSection(&m_csRestart); m_completer = NULL; + m_restart = FALSE; } - void DoUpdateIfNeeded() + // 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() { - wxTextCompleter *completer; - wxString prefix; + bool rc = true; + for ( ;; ) { - CSLock lock(m_csUpdate); - - completer = m_completer; - if ( !completer ) - return; + wxString prefix; + LONG restart; + { + CSLock lock(m_csRestart); - prefix = m_prefix; - } // Unlock m_csUpdate to allow the main thread to call our - // UpdateStringsFromCompleter() without blocking while we are - // generating completions. + prefix = m_prefix; + restart = m_restart; - // Notice that m_csStrings is locked by our caller already so no need - // to enter it again. - m_index = 0; - m_strings.clear(); - completer->GetCompletions(prefix, m_strings); + m_restart = FALSE; + } // Release m_csRestart before calling Start() to avoid blocking + // the main thread in UpdatePrefix() during its execution. - { - CSLock lock(m_csUpdate); + if ( !restart ) + break; - if ( m_completer == completer && m_prefix == prefix ) - { - // There were no calls to UpdateStringsFromCompleter() while we - // generated the completions, so our completions are still - // pertinent. - m_completer = NULL; - return; - } - //else: Our completions are already out of date, regenerate them - // once again. + rc = m_completer->Start(prefix); } + + return rc; } - // Critical section protecting m_strings and m_index. - CRITICAL_SECTION m_csStrings; + // 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 strings we iterate over and the current iteration index. - wxArrayString m_strings; - unsigned m_index; + // The completer we delegate to for the completions generation. It is never + // NULL after the initial ChangeCompleter() call. + wxTextCompleter *m_completer; - // This one protects just m_completer, it is different from m_csStrings - // because the main thread should be able to update our completer prefix - // without blocking (as this would freeze the UI) even while we're inside a - // possibly long process of updating m_strings. - CRITICAL_SECTION m_csUpdate; - // If completer pointer is non-NULL, we must use it by calling its - // GetCompletions() method with the specified prefix to update the list of - // strings we iterate over when our Next() is called the next time. - 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; + // 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; + - wxDECLARE_NO_ASSIGN_CLASS(wxIEnumString); + wxDECLARE_NO_COPY_CLASS(wxIEnumString); }; BEGIN_IID_TABLE(wxIEnumString) @@ -324,8 +345,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: @@ -337,6 +358,8 @@ public: m_autoComplete = NULL; m_autoCompleteDropDown = NULL; m_enumStrings = NULL; + + m_fixedCompleter = NULL; m_customCompleter = NULL; m_connectedCharEvent = false; @@ -346,7 +369,7 @@ public: // apparently. HRESULT hr = CoCreateInstance ( - CLSID_AutoComplete, + wxCLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER, IID_IAutoComplete, @@ -381,7 +404,7 @@ public: // provided IAutoComplete always implements IAutoCompleteDropDown too. hr = m_autoComplete->QueryInterface ( - IID_IAutoCompleteDropDown, + wxIID_IAutoCompleteDropDown, reinterpret_cast(&m_autoCompleteDropDown) ); if ( FAILED(hr) ) @@ -409,6 +432,7 @@ public: ~wxTextAutoCompleteData() { delete m_customCompleter; + delete m_fixedCompleter; if ( m_enumStrings ) m_enumStrings->Release(); @@ -427,7 +451,12 @@ public: void ChangeStrings(const wxArrayString& strings) { - m_enumStrings->ChangeStrings(strings); + if ( !m_fixedCompleter ) + m_fixedCompleter = new wxTextCompleterFixed; + + m_fixedCompleter->SetCompletions(strings); + + m_enumStrings->ChangeCompleter(m_fixedCompleter); DoRefresh(); } @@ -435,6 +464,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; @@ -477,15 +510,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->ChangeStrings(wxArrayString()); - - 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(); @@ -516,7 +546,7 @@ private: const wxString prefix = m_entry->GetRange(0, from); - m_enumStrings->UpdateStringsFromCompleter(m_customCompleter, prefix); + m_enumStrings->UpdatePrefix(prefix); DoRefresh(); } @@ -548,6 +578,9 @@ 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; @@ -810,6 +843,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