X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/574479e8db6ac77d1f94183cddecd688e012504c..74a8f67d96591cec101def2a7d47c64072aff7fd:/src/msw/textentry.cpp diff --git a/src/msw/textentry.cpp b/src/msw/textentry.cpp index 654a82007b..77bc4cd1bc 100644 --- a/src/msw/textentry.cpp +++ b/src/msw/textentry.cpp @@ -31,8 +31,11 @@ #if wxUSE_TEXTCTRL || wxUSE_COMBOBOX #include "wx/textentry.h" +#include "wx/textcompleter.h" #include "wx/dynlib.h" +#include + #include "wx/msw/private.h" #if wxUSE_UXTHEME @@ -42,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 @@ -58,6 +61,10 @@ #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 @@ -68,24 +75,91 @@ #define SHACF_FILESYS_ONLY 0x00000010 #endif -DEFINE_GUID(CLSID_AutoComplete, +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(const wxArrayString& strings) : m_strings(strings) + wxIEnumString() { - m_index = 0; + Init(); } - void ChangeStrings(const wxArrayString& strings) + void ChangeCompleter(wxTextCompleter *completer) { - m_strings = strings; - Reset(); + // 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, @@ -100,12 +174,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 ) @@ -123,11 +207,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; @@ -135,7 +229,9 @@ public: virtual HRESULT STDMETHODCALLTYPE Reset() { - m_index = 0; + CSLock lock(m_csRestart); + + m_restart = TRUE; return S_OK; } @@ -145,25 +241,98 @@ public: if ( !ppEnum ) return E_POINTER; - wxIEnumString *e = new wxIEnumString(m_strings); - e->m_index = m_index; + 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; + + // 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; - wxArrayString m_strings; - unsigned m_index; wxDECLARE_NO_COPY_CLASS(wxIEnumString); }; @@ -175,12 +344,277 @@ END_IID_TABLE; IMPLEMENT_IUNKNOWN_METHODS(wxIEnumString) + +// 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: + // The constructor associates us with the given text entry. + wxTextAutoCompleteData(wxTextEntry *entry) + : m_entry(entry), + m_win(entry->GetEditableWindow()) + { + m_autoComplete = NULL; + m_autoCompleteDropDown = NULL; + m_enumStrings = NULL; + + m_fixedCompleter = NULL; + m_customCompleter = NULL; + + 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 + ( + wxCLSID_AutoComplete, + NULL, + CLSCTX_INPROC_SERVER, + IID_IAutoComplete, + reinterpret_cast(&m_autoComplete) + ); + if ( FAILED(hr) ) + { + wxLogApiError(wxT("CoCreateInstance(CLSID_AutoComplete)"), hr); + return; + } + + // Create a string enumerator and initialize the completer with it. + m_enumStrings = new wxIEnumString; + m_enumStrings->AddRef(); + hr = m_autoComplete->Init(m_entry->GetEditHWND(), m_enumStrings, + NULL, NULL); + if ( FAILED(hr) ) + { + wxLogApiError(wxT("IAutoComplete::Init"), hr); + + m_enumStrings->Release(); + m_enumStrings = NULL; + + return; + } + + // As explained in DoRefresh(), we need to call IAutoCompleteDropDown:: + // ResetEnumerator() if we want to be able to change the completions on + // the fly. In principle we could live without it, i.e. return true + // from IsOk() even if this QueryInterface() fails, but it doesn't look + // like this is ever going to have in practice anyhow as the shell- + // provided IAutoComplete always implements IAutoCompleteDropDown too. + hr = m_autoComplete->QueryInterface + ( + wxIID_IAutoCompleteDropDown, + reinterpret_cast(&m_autoCompleteDropDown) + ); + if ( FAILED(hr) ) + { + wxLogApiError(wxT("IAutoComplete::QI(IAutoCompleteDropDown)"), hr); + return; + } + + // Finally set the completion options using IAutoComplete2. + IAutoComplete2 *pAutoComplete2 = NULL; + hr = m_autoComplete->QueryInterface + ( + IID_IAutoComplete2, + reinterpret_cast(&pAutoComplete2) + ); + if ( SUCCEEDED(hr) ) + { + pAutoComplete2->SetOptions(ACO_AUTOSUGGEST | + ACO_AUTOAPPEND | + ACO_UPDOWNKEYDROPSLIST); + pAutoComplete2->Release(); + } + } + + ~wxTextAutoCompleteData() + { + delete m_customCompleter; + delete m_fixedCompleter; + + if ( m_enumStrings ) + m_enumStrings->Release(); + if ( m_autoCompleteDropDown ) + m_autoCompleteDropDown->Release(); + if ( m_autoComplete ) + m_autoComplete->Release(); + } + + // Must be called after creating this object to verify if initializing it + // succeeded. + bool IsOk() const + { + return m_autoComplete && m_autoCompleteDropDown && m_enumStrings; + } + + void ChangeStrings(const wxArrayString& strings) + { + if ( !m_fixedCompleter ) + m_fixedCompleter = new wxTextCompleterFixed; + + m_fixedCompleter->SetCompletions(strings); + + m_enumStrings->ChangeCompleter(m_fixedCompleter); + + DoRefresh(); + } + + // 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; + + if ( m_customCompleter ) + { + // 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_connectedCharEvent ) + { + 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); + } + + UpdateStringsFromCustomCompleter(); + } + + return true; + } + + void DisableCompletion() + { + // We currently simply reset the list of possible strings as this seems + // 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. + ChangeStrings(wxArrayString()); + } + +private: + // Must be called after changing the values to be returned by wxIEnumString + // to really make the changes stick. + void DoRefresh() + { + m_enumStrings->Reset(); + + // This is completely and utterly not documented and in fact the + // current MSDN seems to try to discourage us from using it by saying + // that "there is no reason to use this method unless the drop-down + // list is currently visible" but actually we absolutely must call it + // to force the auto-completer (and not just its drop-down!) to refresh + // the list of completions which could have changed now. Without this + // call the new choices returned by GetCompletions() that hadn't been + // returned by it before are simply silently ignored. + m_autoCompleteDropDown->ResetEnumerator(); + } + + // Update the strings returned by our string enumerator to correspond to + // the currently valid choices according to the custom completer. + void UpdateStringsFromCustomCompleter() + { + // 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 OnAfterChar(wxKeyEvent& event) + { + // 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(); + } + + + // The text entry we're associated with. + wxTextEntry * const m_entry; + + // The window of this text entry. + wxWindow * const m_win; + + // The auto-completer object itself. + IAutoComplete *m_autoComplete; + + // Its IAutoCompleteDropDown interface needed for ResetEnumerator() call. + IAutoCompleteDropDown *m_autoCompleteDropDown; + + // 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_connectedCharEvent; + + + wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteData); +}; + #endif // HAS_AUTOCOMPLETE // ============================================================================ // wxTextEntry implementation // ============================================================================ +// ---------------------------------------------------------------------------- +// initialization and destruction +// ---------------------------------------------------------------------------- + +wxTextEntry::wxTextEntry() +{ +#ifdef HAS_AUTOCOMPLETE + m_autoCompleteData = NULL; +#endif // HAS_AUTOCOMPLETE +} + +wxTextEntry::~wxTextEntry() +{ +#ifdef HAS_AUTOCOMPLETE + delete m_autoCompleteData; +#endif // HAS_AUTOCOMPLETE +} + // ---------------------------------------------------------------------------- // operations on text // ---------------------------------------------------------------------------- @@ -303,9 +737,11 @@ void wxTextEntry::GetSelection(long *from, long *to) const // ---------------------------------------------------------------------------- #if wxUSE_OLE + +#ifdef HAS_AUTOCOMPLETE + bool wxTextEntry::DoAutoCompleteFileNames() { -#ifdef HAS_AUTOCOMPLETE typedef HRESULT (WINAPI *SHAutoComplete_t)(HWND, DWORD); static SHAutoComplete_t s_pfnSHAutoComplete = (SHAutoComplete_t)-1; static wxDynamicLibrary s_dllShlwapi; @@ -331,77 +767,89 @@ bool wxTextEntry::DoAutoCompleteFileNames() return false; } + + // Disable the other kinds of completion now that we use the built-in file + // names completion. + if ( m_autoCompleteData ) + m_autoCompleteData->DisableCompletion(); + return true; -#else // !HAS_AUTOCOMPLETE - return false; -#endif // HAS_AUTOCOMPLETE/!HAS_AUTOCOMPLETE } -bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices) +wxTextAutoCompleteData *wxTextEntry::GetOrCreateCompleter() { -#ifdef HAS_AUTOCOMPLETE - // if we had an old enumerator we must reuse it as IAutoComplete doesn't - // free it if we call Init() again (see #10968) -- and it's also simpler - if ( m_enumStrings ) + if ( !m_autoCompleteData ) { - m_enumStrings->ChangeStrings(choices); - return true; + wxTextAutoCompleteData * const ac = new wxTextAutoCompleteData(this); + if ( ac->IsOk() ) + m_autoCompleteData = ac; + else + delete ac; } - // create an object exposing IAutoComplete interface (don't go for - // IAutoComplete2 immediately as, presumably, it might be not available on - // older systems as otherwise why do we have both -- although in practice I - // don't know when can this happen) - IAutoComplete *pAutoComplete = NULL; - HRESULT hr = CoCreateInstance - ( - CLSID_AutoComplete, - NULL, - CLSCTX_INPROC_SERVER, - IID_IAutoComplete, - reinterpret_cast(&pAutoComplete) - ); - if ( FAILED(hr) ) - { - wxLogApiError(wxT("CoCreateInstance(CLSID_AutoComplete)"), hr); + return m_autoCompleteData; +} + +bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices) +{ + wxTextAutoCompleteData * const ac = GetOrCreateCompleter(); + if ( !ac ) return false; - } - // associate it with our strings - m_enumStrings = new wxIEnumString(choices); - m_enumStrings->AddRef(); - hr = pAutoComplete->Init(GetEditHwnd(), m_enumStrings, NULL, NULL); - m_enumStrings->Release(); - if ( FAILED(hr) ) + ac->ChangeStrings(choices); + + return true; +} + +bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter *completer) +{ + // First deal with the case when we just want to disable auto-completion. + if ( !completer ) { - wxLogApiError(wxT("IAutoComplete::Init"), hr); - return false; + if ( m_autoCompleteData ) + m_autoCompleteData->DisableCompletion(); + //else: Nothing to do, we hadn't used auto-completion even before. } - - // if IAutoComplete2 is available, set more user-friendly options - IAutoComplete2 *pAutoComplete2 = NULL; - hr = pAutoComplete->QueryInterface - ( - IID_IAutoComplete2, - reinterpret_cast(&pAutoComplete2) - ); - if ( SUCCEEDED(hr) ) + else // Have a valid completer. { - pAutoComplete2->SetOptions(ACO_AUTOSUGGEST | ACO_UPDOWNKEYDROPSLIST); - pAutoComplete2->Release(); + wxTextAutoCompleteData * const ac = GetOrCreateCompleter(); + if ( !ac ) + { + // Delete the custom completer for consistency with the case when + // we succeed to avoid memory leaks in user code. + delete completer; + return false; + } + + // This gives ownership of the custom completer to m_autoCompleteData. + if ( !ac->ChangeCustomCompleter(completer) ) + return false; } - // the docs are unclear about when can we release it but it seems safe to - // do it immediately, presumably the edit control itself keeps a reference - // to the auto completer object - pAutoComplete->Release(); return true; +} + #else // !HAS_AUTOCOMPLETE - wxUnusedVar(choices); - return false; -#endif // HAS_AUTOCOMPLETE/!HAS_AUTOCOMPLETE +// We still need to define stubs as we declared these overrides in the header. + +bool wxTextEntry::DoAutoCompleteFileNames() +{ + return wxTextEntryBase::DoAutoCompleteFileNames(); +} + +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 // ----------------------------------------------------------------------------