From ea98f11c2fbcd33ffc9b9771b8046c7353f51fcf Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 16 Apr 2011 17:27:16 +0000 Subject: [PATCH] Add support for dynamic auto-completion in wxTextEntry. Add wxTextCompleter class which allows to return the possible completions dynamically and wxTextCompleter::AutoComplete() overload using it. So far this is only implemented for wxMSW. Also fix calling wxTextEntry::AutoComplete(wxArrayString) multiple times under MSW, this didn't correctly update the list of shown completions before. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@67511 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- Makefile.in | 1 + build/bakefiles/files.bkl | 1 + build/msw/wx_core.dsp | 4 + build/msw/wx_vc7_core.vcproj | 3 + build/msw/wx_vc8_core.vcproj | 4 + build/msw/wx_vc9_core.vcproj | 4 + docs/changes.txt | 1 + include/wx/msw/textentry.h | 23 ++- include/wx/textcompleter.h | 31 ++++ include/wx/textentry.h | 10 +- interface/wx/textcompleter.h | 85 +++++++++ interface/wx/textentry.h | 34 ++++ samples/widgets/widgets.cpp | 114 +++++++++++- src/common/textentrycmn.cpp | 19 ++ src/msw/textentry.cpp | 340 +++++++++++++++++++++++++++++------ 15 files changed, 611 insertions(+), 63 deletions(-) create mode 100644 include/wx/textcompleter.h create mode 100644 interface/wx/textcompleter.h diff --git a/Makefile.in b/Makefile.in index 0b3fa1f3f3..091d58b580 100644 --- a/Makefile.in +++ b/Makefile.in @@ -3929,6 +3929,7 @@ COND_USE_GUI_1_ALL_GUI_HEADERS = \ wx/statbox.h \ wx/stattext.h \ wx/statusbr.h \ + wx/textcompleter.h \ wx/textctrl.h \ wx/textdlg.h \ wx/textentry.h \ diff --git a/build/bakefiles/files.bkl b/build/bakefiles/files.bkl index 7f7e571009..fca4e9bb37 100644 --- a/build/bakefiles/files.bkl +++ b/build/bakefiles/files.bkl @@ -905,6 +905,7 @@ IMPORTANT: please read docs/tech/tn0016.txt before modifying this file! wx/statbox.h wx/stattext.h wx/statusbr.h + wx/textcompleter.h wx/textctrl.h wx/textdlg.h wx/textentry.h diff --git a/build/msw/wx_core.dsp b/build/msw/wx_core.dsp index e5032676c5..d66a99bcc0 100644 --- a/build/msw/wx_core.dsp +++ b/build/msw/wx_core.dsp @@ -6741,6 +6741,10 @@ SOURCE=..\..\include\wx\tbarbase.h # End Source File # Begin Source File +SOURCE=..\..\include\wx\textcompleter.h +# End Source File +# Begin Source File + SOURCE=..\..\include\wx\textctrl.h # End Source File # Begin Source File diff --git a/build/msw/wx_vc7_core.vcproj b/build/msw/wx_vc7_core.vcproj index 01639cb3b1..511e2a8640 100644 --- a/build/msw/wx_vc7_core.vcproj +++ b/build/msw/wx_vc7_core.vcproj @@ -5643,6 +5643,9 @@ RelativePath="..\..\include\wx\tbarbase.h"> + + + + diff --git a/build/msw/wx_vc9_core.vcproj b/build/msw/wx_vc9_core.vcproj index 4713e649fc..c4ed51456b 100644 --- a/build/msw/wx_vc9_core.vcproj +++ b/build/msw/wx_vc9_core.vcproj @@ -7540,6 +7540,10 @@ > + + diff --git a/docs/changes.txt b/docs/changes.txt index e542d014da..63441b056d 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -466,6 +466,7 @@ All (GUI): - Added wxRichMessageDialog (Rickard Westerlund, GSoC 2010 project). - Added wxCommandLinkButton (Rickard Westerlund, GSoC 2010 project). - Added wxUIActionSimulator (Steven Lamerton, GSoC 2010 project). +- Added support for dynamic auto-completion in wxTextEntry. - wxAUI: support auto-orientable toolbars (wsu). - wxAUI: add support for icons in pane title bars (triton). - Added wxPanel::SetBackgroundBitmap(). diff --git a/include/wx/msw/textentry.h b/include/wx/msw/textentry.h index aa23df118c..a2f5f95be5 100644 --- a/include/wx/msw/textentry.h +++ b/include/wx/msw/textentry.h @@ -11,6 +11,8 @@ #ifndef _WX_MSW_TEXTENTRY_H_ #define _WX_MSW_TEXTENTRY_H_ +class wxTextAutoCompleteData; // private class used only by wxTextEntry itself + // ---------------------------------------------------------------------------- // wxTextEntry: common part of wxComboBox and (single line) wxTextCtrl // ---------------------------------------------------------------------------- @@ -18,12 +20,8 @@ class WXDLLIMPEXP_CORE wxTextEntry : public wxTextEntryBase { public: - wxTextEntry() - { -#if wxUSE_OLE - m_enumStrings = NULL; -#endif // wxUSE_OLE - } + wxTextEntry(); + virtual ~wxTextEntry(); // implement wxTextEntryBase pure virtual methods virtual void WriteText(const wxString& text); @@ -78,6 +76,7 @@ protected: #if wxUSE_OLE virtual bool DoAutoCompleteStrings(const wxArrayString& choices); virtual bool DoAutoCompleteFileNames(); + virtual bool DoAutoCompleteCustom(wxTextCompleter *completer); #endif // wxUSE_OLE private: @@ -85,8 +84,16 @@ private: virtual WXHWND GetEditHWND() const = 0; #if wxUSE_OLE - // enumerator for strings currently used for auto-completion or NULL - class wxIEnumString *m_enumStrings; + // Get the auto-complete object creating it if necessary. Returns NULL if + // creating it failed. + wxTextAutoCompleteData *GetOrCreateCompleter(); + + // Various auto-completion-related stuff, only used if any of AutoComplete() + // methods are called. Use the function above to access it. + wxTextAutoCompleteData *m_autoCompleteData; + + // It needs to call our GetEditableWindow() and GetEditHWND() methods. + friend class wxTextAutoCompleteData; #endif // wxUSE_OLE }; diff --git a/include/wx/textcompleter.h b/include/wx/textcompleter.h new file mode 100644 index 0000000000..76aae00a0d --- /dev/null +++ b/include/wx/textcompleter.h @@ -0,0 +1,31 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/textcompleter.h +// Purpose: Declaration of wxTextCompleter class. +// Author: Vadim Zeitlin +// Created: 2011-04-13 +// RCS-ID: $Id: wxhead.h,v 1.12 2010-04-22 12:44:51 zeitlin Exp $ +// Copyright: (c) 2011 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_TEXTCOMPLETER_H_ +#define _WX_TEXTCOMPLETER_H_ + +// ---------------------------------------------------------------------------- +// wxTextCompleter: used by wxTextEnter::AutoComplete() +// ---------------------------------------------------------------------------- + +class WXDLLIMPEXP_CORE wxTextCompleter +{ +public: + wxTextCompleter() { } + + virtual void GetCompletions(const wxString& prefix, wxArrayString& res) = 0; + + virtual ~wxTextCompleter(); + +private: + wxDECLARE_NO_COPY_CLASS(wxTextCompleter); +}; + +#endif // _WX_TEXTCOMPLETER_H_ diff --git a/include/wx/textentry.h b/include/wx/textentry.h index f728289251..7601444ca8 100644 --- a/include/wx/textentry.h +++ b/include/wx/textentry.h @@ -16,6 +16,7 @@ typedef long wxTextPos; class WXDLLIMPEXP_FWD_BASE wxArrayString; +class WXDLLIMPEXP_FWD_CORE wxTextCompleter; class WXDLLIMPEXP_FWD_CORE wxTextEntryHintData; class WXDLLIMPEXP_FWD_CORE wxWindow; @@ -106,7 +107,7 @@ public: // these functions allow to auto-complete the text already entered into the // control using either the given fixed list of strings, the paths from the - // file system or, in the future, an arbitrary user-defined completer + // file system or an arbitrary user-defined completer // // they all return true if completion was enabled or false on error (most // commonly meaning that this functionality is not available under the @@ -118,6 +119,12 @@ public: bool AutoCompleteFileNames() { return DoAutoCompleteFileNames(); } + // notice that we take ownership of the pointer and will delete it + // + // if the pointer is NULL auto-completion is disabled + bool AutoComplete(wxTextCompleter *completer) + { return DoAutoCompleteCustom(completer); } + // status // ------ @@ -224,6 +231,7 @@ protected: virtual bool DoAutoCompleteStrings(const wxArrayString& WXUNUSED(choices)) { return false; } virtual bool DoAutoCompleteFileNames() { return false; } + virtual bool DoAutoCompleteCustom(wxTextCompleter *completer); // class which should be used to temporarily disable text change events diff --git a/interface/wx/textcompleter.h b/interface/wx/textcompleter.h new file mode 100644 index 0000000000..c900821379 --- /dev/null +++ b/interface/wx/textcompleter.h @@ -0,0 +1,85 @@ +///////////////////////////////////////////////////////////////////////////// +// Name: wx/textcompleter.h +// Purpose: interface of wxTextCompleter +// Author: Vadim Zeitlin +// Created: 2011-04-13 +// RCS-ID: $Id$ +// Copyright: (c) 2011 Vadim Zeitlin +// Licence: wxWindows licence +///////////////////////////////////////////////////////////////////////////// + +/** + @class wxTextCompleter + + Base class for custom text completer objects. + + Custom completer objects used with wxTextEntry::AutoComplete() must derive + from this class and implement its pure virtual method returning the + completions. You would typically use a custom completer when the total + number of completions is too big for performance to be acceptable if all of + them need to be returned at once but if they can be generated + hierarchically, i.e. only the first component initially, then the second + one after the user finished entering the first one and so on. + + Here is a simple example of a custom completer that completes the names of + some chess pieces. Of course, as the total list here has only four items it + would have been much simpler to just specify the array containing all the + completions in this example but the same approach could be used when the + total number of completions is much higher provided the number of + possibilities for each word is still relatively small: + @code + class MyTextCompleter : public wxTextCompleter + { + public: + virtual void GetCompletions(const wxString& prefix, wxArrayString& res) + { + const wxString firstWord = prefix.BeforeFirst(' '); + if ( firstWord == "white" ) + { + res.push_back("white pawn"); + res.push_back("white rook"); + } + else if ( firstWord == "black" ) + { + res.push_back("black king"); + res.push_back("black queen"); + } + else + { + res.push_back("white"); + res.push_back("black"); + } + } + }; + ... + wxTextCtrl *text = ...; + text->AutoComplete(new MyTextCompleter); + @endcode + + @library{wxcore} + + @since 2.9.2 +*/ +class wxTextCompleter +{ +public: + /** + Pure virtual method returning all possible completions for the given + prefix. + + The custom completer should examine the provided prefix and return all + the possible completions for it in the output array @a res. + + Please notice that the returned values should start with the prefix, + otherwise they will be simply ignored, making adding them to the array + in the first place useless. + + @param prefix + The possibly empty prefix that the user had already entered. + @param res + Initially empty array that should be filled with all possible + completions (possibly none if there are no valid possibilities + starting with the given prefix). + */ + virtual void GetCompletions(const wxString& prefix, wxArrayString& res) = 0; +}; diff --git a/interface/wx/textentry.h b/interface/wx/textentry.h index f29cc17239..565bbaf597 100644 --- a/interface/wx/textentry.h +++ b/interface/wx/textentry.h @@ -60,6 +60,40 @@ public: */ bool AutoComplete(const wxArrayString& choices); + /** + Enable auto-completion using the provided completer object. + + This method should be used instead of AutoComplete() overload taking + the array of possible completions if the total number of strings is too + big as it allows to return the completions dynamically, depending on + the text already entered by user and so is more efficient. + + The specified @a completer object will be used to retrieve the list of + possible completions for the already entered text and will be deleted + by wxTextEntry itself when it's not needed any longer. + + Notice that you need to include @c wx/textcompleter.h in order to + define your class inheriting from wxTextCompleter. + + Currently this method is only implemented in wxMSW port. + + @since 2.9.2 + + @param completer + The object to be used for generating completions if non-@NULL. If + it is @NULL, auto-completion is disabled. The wxTextEntry object + takes ownership of this pointer and will delete it in any case + (i.e. even if this method returns @false). + + @return + @true if the auto-completion was enabled or @false if the operation + failed, typically because auto-completion is not supported by the + current platform. + + @see wxTextCompleter + */ + bool AutoComplete(wxTextCompleter *completer); + /** Call this function to enable auto-completion of the text typed in a single-line text control using all valid file system paths. diff --git a/samples/widgets/widgets.cpp b/samples/widgets/widgets.cpp index 1ec72612b9..c5f4700478 100644 --- a/samples/widgets/widgets.cpp +++ b/samples/widgets/widgets.cpp @@ -50,6 +50,7 @@ #include "wx/textdlg.h" #include "wx/imaglist.h" #include "wx/wupdlock.h" +#include "wx/textcompleter.h" #include "wx/persist/toplevel.h" #include "wx/persist/treebook.h" @@ -98,6 +99,7 @@ enum TextEntry_DisableAutoComplete = TextEntry_Begin, TextEntry_AutoCompleteFixed, TextEntry_AutoCompleteFilenames, + TextEntry_AutoCompleteCustom, TextEntry_SetHint, TextEntry_End @@ -172,6 +174,7 @@ protected: void OnDisableAutoComplete(wxCommandEvent& event); void OnAutoCompleteFixed(wxCommandEvent& event); void OnAutoCompleteFilenames(wxCommandEvent& event); + void OnAutoCompleteCustom(wxCommandEvent& event); void OnSetHint(wxCommandEvent& event); @@ -300,6 +303,7 @@ BEGIN_EVENT_TABLE(WidgetsFrame, wxFrame) EVT_MENU(TextEntry_DisableAutoComplete, WidgetsFrame::OnDisableAutoComplete) EVT_MENU(TextEntry_AutoCompleteFixed, WidgetsFrame::OnAutoCompleteFixed) EVT_MENU(TextEntry_AutoCompleteFilenames, WidgetsFrame::OnAutoCompleteFilenames) + EVT_MENU(TextEntry_AutoCompleteCustom, WidgetsFrame::OnAutoCompleteCustom) EVT_MENU(TextEntry_SetHint, WidgetsFrame::OnSetHint) @@ -414,6 +418,8 @@ WidgetsFrame::WidgetsFrame(const wxString& title) wxT("Fixed-&list auto-completion")); menuTextEntry->AppendRadioItem(TextEntry_AutoCompleteFilenames, wxT("&Files names auto-completion")); + menuTextEntry->AppendRadioItem(TextEntry_AutoCompleteCustom, + wxT("&Custom auto-completion")); menuTextEntry->AppendSeparator(); menuTextEntry->Append(TextEntry_SetHint, "Set help &hint"); @@ -981,7 +987,7 @@ void WidgetsFrame::OnAutoCompleteFilenames(wxCommandEvent& WXUNUSED(event)) if ( entry->AutoCompleteFileNames() ) { - wxLogMessage("Enable auto completion of file names."); + wxLogMessage("Enabled auto completion of file names."); } else { @@ -989,6 +995,112 @@ void WidgetsFrame::OnAutoCompleteFilenames(wxCommandEvent& WXUNUSED(event)) } } +void WidgetsFrame::OnAutoCompleteCustom(wxCommandEvent& WXUNUSED(event)) +{ + wxTextEntryBase *entry = CurrentPage()->GetTextEntry(); + wxCHECK_RET( entry, "menu item should be disabled" ); + + // This is a simple (and hence rather useless) example of a custom + // completer class that completes the first word (only) initially and only + // build the list of the possible second words once the first word is + // known. This allows to avoid building the full 676000 item list of + // possible strings all at once as the we have 1000 possibilities for the + // first word (000..999) and 676 (aa..zz) for the second one. + class CustomTextCompleter : public wxTextCompleter + { + public: + virtual void GetCompletions(const wxString& prefix, wxArrayString& res) + { + // This is used for illustrative purposes only and shows how many + // completions we return every time when we're called. + class LogCompletions + { + public: + LogCompletions(const wxString& prefix, const wxArrayString& res) + : m_prefix(prefix), + m_res(res) + { + } + + ~LogCompletions() + { + wxLogMessage("Returning %lu possible completions for " + "prefix \"%s\"", + m_res.size(), m_prefix); + } + + private: + const wxString& m_prefix; + const wxArrayString& m_res; + } logCompletions(prefix, res); + + + // Normally it doesn't make sense to complete empty control, there + // are too many choices and listing them all wouldn't be helpful. + if ( prefix.empty() ) + return; + + // The only valid strings start with 3 digits so check for their + // presence proposing to complete the remaining ones. + if ( !wxIsdigit(prefix[0]) ) + return; + + if ( prefix.length() == 1 ) + { + for ( int i = 0; i < 10; i++ ) + for ( int j = 0; j < 10; j++ ) + res.push_back(wxString::Format("%s%02d", + prefix, 10*i + j)); + return; + } + else if ( !wxIsdigit(prefix[1]) ) + return; + + if ( prefix.length() == 2 ) + { + for ( int i = 0; i < 10; i++ ) + res.push_back(wxString::Format("%s%d", prefix, i)); + return; + } + else if ( !wxIsdigit(prefix[2]) ) + return; + + // Next we must have a space and two letters. + wxString prefix2(prefix); + if ( prefix.length() == 3 ) + prefix2 += ' '; + else if ( prefix[3] != ' ' ) + return; + + if ( prefix2.length() == 4 ) + { + for ( char c = 'a'; c <= 'z'; c++ ) + for ( char d = 'a'; d <= 'z'; d++ ) + res.push_back(wxString::Format("%s%c%c", prefix2, c, d)); + return; + } + else if ( !wxIslower(prefix[4]) ) + return; + + if ( prefix.length() == 5 ) + { + for ( char c = 'a'; c <= 'z'; c++ ) + res.push_back(prefix + c); + } + } + }; + + if ( entry->AutoComplete(new CustomTextCompleter) ) + { + wxLogMessage("Enabled custom auto completer for \"NNN XX\" items " + "(where N is a digit and X is a letter)."); + } + else + { + wxLogMessage("AutoComplete() failed."); + } +} + void WidgetsFrame::OnSetHint(wxCommandEvent& WXUNUSED(event)) { wxTextEntryBase *entry = CurrentPage()->GetTextEntry(); diff --git a/src/common/textentrycmn.cpp b/src/common/textentrycmn.cpp index 51020ddcb4..66a0a6452e 100644 --- a/src/common/textentrycmn.cpp +++ b/src/common/textentrycmn.cpp @@ -31,6 +31,7 @@ #endif //WX_PRECOMP #include "wx/textentry.h" +#include "wx/textcompleter.h" #include "wx/clipbrd.h" // ---------------------------------------------------------------------------- @@ -368,4 +369,22 @@ bool wxTextEntryBase::SendTextUpdatedEvent(wxWindow *win) return win->HandleWindowEvent(event); } +// ---------------------------------------------------------------------------- +// auto-completion stubs +// ---------------------------------------------------------------------------- + +wxTextCompleter::~wxTextCompleter() +{ +} + +bool wxTextEntryBase::DoAutoCompleteCustom(wxTextCompleter *completer) +{ + // We don't do anything here but we still need to delete the completer for + // consistency with the ports that do implement this method and take + // ownership of the pointer. + delete completer; + + return false; +} + #endif // wxUSE_TEXTCTRL || wxUSE_COMBOBOX diff --git a/src/msw/textentry.cpp b/src/msw/textentry.cpp index d66b9c6bf3..161035b59f 100644 --- a/src/msw/textentry.cpp +++ b/src/msw/textentry.cpp @@ -31,6 +31,7 @@ #if wxUSE_TEXTCTRL || wxUSE_COMBOBOX #include "wx/textentry.h" +#include "wx/textcompleter.h" #include "wx/dynlib.h" #include "wx/msw/private.h" @@ -54,6 +55,7 @@ #include "wx/msw/ole/oleutils.h" #include +#include #if defined(__MINGW32__) || defined (__WATCOMC__) || defined(__CYGWIN__) // needed for IID_IAutoComplete, IID_IAutoComplete2 and ACO_AUTOSUGGEST @@ -74,15 +76,15 @@ DEFINE_GUID(CLSID_AutoComplete, class wxIEnumString : public IEnumString { public: - wxIEnumString(const wxArrayString& strings) : m_strings(strings) + wxIEnumString() { m_index = 0; } - void ChangeStrings(const wxArrayString& strings) + wxIEnumString(const wxIEnumString& other) + : m_strings(other.m_strings), + m_index(other.m_index) { - m_strings = strings; - Reset(); } DECLARE_IUNKNOWN_METHODS; @@ -145,8 +147,7 @@ public: if ( !ppEnum ) return E_POINTER; - wxIEnumString *e = new wxIEnumString(m_strings); - e->m_index = m_index; + wxIEnumString *e = new wxIEnumString(*this); e->AddRef(); *ppEnum = e; @@ -165,7 +166,9 @@ private: wxArrayString m_strings; unsigned m_index; - wxDECLARE_NO_COPY_CLASS(wxIEnumString); + friend class wxTextAutoCompleteData; + + wxDECLARE_NO_ASSIGN_CLASS(wxIEnumString); }; BEGIN_IID_TABLE(wxIEnumString) @@ -175,12 +178,244 @@ 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. +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_customCompleter = NULL; + + m_connectedTextChangedEvent = 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, + 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 + ( + IID_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_UPDOWNKEYDROPSLIST); + pAutoComplete2->Release(); + } + } + + ~wxTextAutoCompleteData() + { + delete m_customCompleter; + + 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) + { + m_enumStrings->m_strings = strings; + + DoRefresh(); + } + + // Takes ownership of the pointer if it is non-NULL. + bool ChangeCustomCompleter(wxTextCompleter *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_connectedTextChangedEvent ) + { + m_connectedTextChangedEvent = true; + + wxBIND_OR_CONNECT_HACK(m_win, wxEVT_COMMAND_TEXT_UPDATED, + wxCommandEventHandler, + wxTextAutoCompleteData::OnTextChanged, + 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. + + m_enumStrings->m_strings.clear(); + m_enumStrings->Reset(); + + ChangeCustomCompleter(NULL); + } + +private: + // Must be called after changing wxIEnumString::m_strings 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() + { + // 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); + + DoRefresh(); + } + + void OnTextChanged(wxCommandEvent& event) + { + if ( m_customCompleter ) + 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; + + // Custom completer or NULL if none. + wxTextCompleter *m_customCompleter; + + // Initially false, set to true after connecting OnTextChanged() handler. + bool m_connectedTextChangedEvent; + + + 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 // ---------------------------------------------------------------------------- @@ -333,66 +568,65 @@ 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; } -bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices) +wxTextAutoCompleteData *wxTextEntry::GetOrCreateCompleter() { - // 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; } -- 2.45.2