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
This commit is contained in:
Vadim Zeitlin 2011-04-16 17:27:16 +00:00
parent 6b30ffedb1
commit ea98f11c2f
15 changed files with 613 additions and 65 deletions

View File

@ -3929,6 +3929,7 @@ COND_USE_GUI_1_ALL_GUI_HEADERS = \
wx/statbox.h \ wx/statbox.h \
wx/stattext.h \ wx/stattext.h \
wx/statusbr.h \ wx/statusbr.h \
wx/textcompleter.h \
wx/textctrl.h \ wx/textctrl.h \
wx/textdlg.h \ wx/textdlg.h \
wx/textentry.h \ wx/textentry.h \

View File

@ -905,6 +905,7 @@ IMPORTANT: please read docs/tech/tn0016.txt before modifying this file!
wx/statbox.h wx/statbox.h
wx/stattext.h wx/stattext.h
wx/statusbr.h wx/statusbr.h
wx/textcompleter.h
wx/textctrl.h wx/textctrl.h
wx/textdlg.h wx/textdlg.h
wx/textentry.h wx/textentry.h

View File

@ -6741,6 +6741,10 @@ SOURCE=..\..\include\wx\tbarbase.h
# End Source File # End Source File
# Begin Source File # Begin Source File
SOURCE=..\..\include\wx\textcompleter.h
# End Source File
# Begin Source File
SOURCE=..\..\include\wx\textctrl.h SOURCE=..\..\include\wx\textctrl.h
# End Source File # End Source File
# Begin Source File # Begin Source File

View File

@ -5643,6 +5643,9 @@
RelativePath="..\..\include\wx\tbarbase.h"> RelativePath="..\..\include\wx\tbarbase.h">
</File> </File>
<File <File
RelativePath="..\..\include\wx\textcompleter.h">
</File>
<File
RelativePath="..\..\include\wx\textctrl.h"> RelativePath="..\..\include\wx\textctrl.h">
</File> </File>
<File <File

View File

@ -7544,6 +7544,10 @@
> >
</File> </File>
<File <File
RelativePath="..\..\include\wx\textcompleter.h"
>
</File>
<File
RelativePath="..\..\include\wx\textctrl.h" RelativePath="..\..\include\wx\textctrl.h"
> >
</File> </File>

View File

@ -7540,6 +7540,10 @@
> >
</File> </File>
<File <File
RelativePath="..\..\include\wx\textcompleter.h"
>
</File>
<File
RelativePath="..\..\include\wx\textctrl.h" RelativePath="..\..\include\wx\textctrl.h"
> >
</File> </File>

View File

@ -466,6 +466,7 @@ All (GUI):
- Added wxRichMessageDialog (Rickard Westerlund, GSoC 2010 project). - Added wxRichMessageDialog (Rickard Westerlund, GSoC 2010 project).
- Added wxCommandLinkButton (Rickard Westerlund, GSoC 2010 project). - Added wxCommandLinkButton (Rickard Westerlund, GSoC 2010 project).
- Added wxUIActionSimulator (Steven Lamerton, 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: support auto-orientable toolbars (wsu).
- wxAUI: add support for icons in pane title bars (triton). - wxAUI: add support for icons in pane title bars (triton).
- Added wxPanel::SetBackgroundBitmap(). - Added wxPanel::SetBackgroundBitmap().

View File

@ -11,6 +11,8 @@
#ifndef _WX_MSW_TEXTENTRY_H_ #ifndef _WX_MSW_TEXTENTRY_H_
#define _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 // wxTextEntry: common part of wxComboBox and (single line) wxTextCtrl
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -18,12 +20,8 @@
class WXDLLIMPEXP_CORE wxTextEntry : public wxTextEntryBase class WXDLLIMPEXP_CORE wxTextEntry : public wxTextEntryBase
{ {
public: public:
wxTextEntry() wxTextEntry();
{ virtual ~wxTextEntry();
#if wxUSE_OLE
m_enumStrings = NULL;
#endif // wxUSE_OLE
}
// implement wxTextEntryBase pure virtual methods // implement wxTextEntryBase pure virtual methods
virtual void WriteText(const wxString& text); virtual void WriteText(const wxString& text);
@ -78,6 +76,7 @@ protected:
#if wxUSE_OLE #if wxUSE_OLE
virtual bool DoAutoCompleteStrings(const wxArrayString& choices); virtual bool DoAutoCompleteStrings(const wxArrayString& choices);
virtual bool DoAutoCompleteFileNames(); virtual bool DoAutoCompleteFileNames();
virtual bool DoAutoCompleteCustom(wxTextCompleter *completer);
#endif // wxUSE_OLE #endif // wxUSE_OLE
private: private:
@ -85,8 +84,16 @@ private:
virtual WXHWND GetEditHWND() const = 0; virtual WXHWND GetEditHWND() const = 0;
#if wxUSE_OLE #if wxUSE_OLE
// enumerator for strings currently used for auto-completion or NULL // Get the auto-complete object creating it if necessary. Returns NULL if
class wxIEnumString *m_enumStrings; // 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 #endif // wxUSE_OLE
}; };

View File

@ -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 <vadim@wxwidgets.org>
// 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_

View File

@ -16,6 +16,7 @@
typedef long wxTextPos; typedef long wxTextPos;
class WXDLLIMPEXP_FWD_BASE wxArrayString; class WXDLLIMPEXP_FWD_BASE wxArrayString;
class WXDLLIMPEXP_FWD_CORE wxTextCompleter;
class WXDLLIMPEXP_FWD_CORE wxTextEntryHintData; class WXDLLIMPEXP_FWD_CORE wxTextEntryHintData;
class WXDLLIMPEXP_FWD_CORE wxWindow; class WXDLLIMPEXP_FWD_CORE wxWindow;
@ -106,7 +107,7 @@ public:
// these functions allow to auto-complete the text already entered into the // 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 // 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 // they all return true if completion was enabled or false on error (most
// commonly meaning that this functionality is not available under the // commonly meaning that this functionality is not available under the
@ -118,6 +119,12 @@ public:
bool AutoCompleteFileNames() bool AutoCompleteFileNames()
{ return DoAutoCompleteFileNames(); } { 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 // status
// ------ // ------
@ -224,6 +231,7 @@ protected:
virtual bool DoAutoCompleteStrings(const wxArrayString& WXUNUSED(choices)) virtual bool DoAutoCompleteStrings(const wxArrayString& WXUNUSED(choices))
{ return false; } { return false; }
virtual bool DoAutoCompleteFileNames() { return false; } virtual bool DoAutoCompleteFileNames() { return false; }
virtual bool DoAutoCompleteCustom(wxTextCompleter *completer);
// class which should be used to temporarily disable text change events // class which should be used to temporarily disable text change events

View File

@ -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 <vadim@wxwindows.org>
// 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;
};

View File

@ -60,6 +60,40 @@ public:
*/ */
bool AutoComplete(const wxArrayString& choices); 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 Call this function to enable auto-completion of the text typed in a
single-line text control using all valid file system paths. single-line text control using all valid file system paths.

View File

@ -50,6 +50,7 @@
#include "wx/textdlg.h" #include "wx/textdlg.h"
#include "wx/imaglist.h" #include "wx/imaglist.h"
#include "wx/wupdlock.h" #include "wx/wupdlock.h"
#include "wx/textcompleter.h"
#include "wx/persist/toplevel.h" #include "wx/persist/toplevel.h"
#include "wx/persist/treebook.h" #include "wx/persist/treebook.h"
@ -98,6 +99,7 @@ enum
TextEntry_DisableAutoComplete = TextEntry_Begin, TextEntry_DisableAutoComplete = TextEntry_Begin,
TextEntry_AutoCompleteFixed, TextEntry_AutoCompleteFixed,
TextEntry_AutoCompleteFilenames, TextEntry_AutoCompleteFilenames,
TextEntry_AutoCompleteCustom,
TextEntry_SetHint, TextEntry_SetHint,
TextEntry_End TextEntry_End
@ -172,6 +174,7 @@ protected:
void OnDisableAutoComplete(wxCommandEvent& event); void OnDisableAutoComplete(wxCommandEvent& event);
void OnAutoCompleteFixed(wxCommandEvent& event); void OnAutoCompleteFixed(wxCommandEvent& event);
void OnAutoCompleteFilenames(wxCommandEvent& event); void OnAutoCompleteFilenames(wxCommandEvent& event);
void OnAutoCompleteCustom(wxCommandEvent& event);
void OnSetHint(wxCommandEvent& event); void OnSetHint(wxCommandEvent& event);
@ -300,6 +303,7 @@ BEGIN_EVENT_TABLE(WidgetsFrame, wxFrame)
EVT_MENU(TextEntry_DisableAutoComplete, WidgetsFrame::OnDisableAutoComplete) EVT_MENU(TextEntry_DisableAutoComplete, WidgetsFrame::OnDisableAutoComplete)
EVT_MENU(TextEntry_AutoCompleteFixed, WidgetsFrame::OnAutoCompleteFixed) EVT_MENU(TextEntry_AutoCompleteFixed, WidgetsFrame::OnAutoCompleteFixed)
EVT_MENU(TextEntry_AutoCompleteFilenames, WidgetsFrame::OnAutoCompleteFilenames) EVT_MENU(TextEntry_AutoCompleteFilenames, WidgetsFrame::OnAutoCompleteFilenames)
EVT_MENU(TextEntry_AutoCompleteCustom, WidgetsFrame::OnAutoCompleteCustom)
EVT_MENU(TextEntry_SetHint, WidgetsFrame::OnSetHint) EVT_MENU(TextEntry_SetHint, WidgetsFrame::OnSetHint)
@ -414,6 +418,8 @@ WidgetsFrame::WidgetsFrame(const wxString& title)
wxT("Fixed-&list auto-completion")); wxT("Fixed-&list auto-completion"));
menuTextEntry->AppendRadioItem(TextEntry_AutoCompleteFilenames, menuTextEntry->AppendRadioItem(TextEntry_AutoCompleteFilenames,
wxT("&Files names auto-completion")); wxT("&Files names auto-completion"));
menuTextEntry->AppendRadioItem(TextEntry_AutoCompleteCustom,
wxT("&Custom auto-completion"));
menuTextEntry->AppendSeparator(); menuTextEntry->AppendSeparator();
menuTextEntry->Append(TextEntry_SetHint, "Set help &hint"); menuTextEntry->Append(TextEntry_SetHint, "Set help &hint");
@ -981,7 +987,7 @@ void WidgetsFrame::OnAutoCompleteFilenames(wxCommandEvent& WXUNUSED(event))
if ( entry->AutoCompleteFileNames() ) if ( entry->AutoCompleteFileNames() )
{ {
wxLogMessage("Enable auto completion of file names."); wxLogMessage("Enabled auto completion of file names.");
} }
else 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)) void WidgetsFrame::OnSetHint(wxCommandEvent& WXUNUSED(event))
{ {
wxTextEntryBase *entry = CurrentPage()->GetTextEntry(); wxTextEntryBase *entry = CurrentPage()->GetTextEntry();

View File

@ -31,6 +31,7 @@
#endif //WX_PRECOMP #endif //WX_PRECOMP
#include "wx/textentry.h" #include "wx/textentry.h"
#include "wx/textcompleter.h"
#include "wx/clipbrd.h" #include "wx/clipbrd.h"
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -368,4 +369,22 @@ bool wxTextEntryBase::SendTextUpdatedEvent(wxWindow *win)
return win->HandleWindowEvent(event); 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 #endif // wxUSE_TEXTCTRL || wxUSE_COMBOBOX

View File

@ -31,6 +31,7 @@
#if wxUSE_TEXTCTRL || wxUSE_COMBOBOX #if wxUSE_TEXTCTRL || wxUSE_COMBOBOX
#include "wx/textentry.h" #include "wx/textentry.h"
#include "wx/textcompleter.h"
#include "wx/dynlib.h" #include "wx/dynlib.h"
#include "wx/msw/private.h" #include "wx/msw/private.h"
@ -54,6 +55,7 @@
#include "wx/msw/ole/oleutils.h" #include "wx/msw/ole/oleutils.h"
#include <shldisp.h> #include <shldisp.h>
#include <shobjidl.h>
#if defined(__MINGW32__) || defined (__WATCOMC__) || defined(__CYGWIN__) #if defined(__MINGW32__) || defined (__WATCOMC__) || defined(__CYGWIN__)
// needed for IID_IAutoComplete, IID_IAutoComplete2 and ACO_AUTOSUGGEST // needed for IID_IAutoComplete, IID_IAutoComplete2 and ACO_AUTOSUGGEST
@ -74,15 +76,15 @@ DEFINE_GUID(CLSID_AutoComplete,
class wxIEnumString : public IEnumString class wxIEnumString : public IEnumString
{ {
public: public:
wxIEnumString(const wxArrayString& strings) : m_strings(strings) wxIEnumString()
{ {
m_index = 0; 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; DECLARE_IUNKNOWN_METHODS;
@ -145,8 +147,7 @@ public:
if ( !ppEnum ) if ( !ppEnum )
return E_POINTER; return E_POINTER;
wxIEnumString *e = new wxIEnumString(m_strings); wxIEnumString *e = new wxIEnumString(*this);
e->m_index = m_index;
e->AddRef(); e->AddRef();
*ppEnum = e; *ppEnum = e;
@ -165,7 +166,9 @@ private:
wxArrayString m_strings; wxArrayString m_strings;
unsigned m_index; unsigned m_index;
wxDECLARE_NO_COPY_CLASS(wxIEnumString); friend class wxTextAutoCompleteData;
wxDECLARE_NO_ASSIGN_CLASS(wxIEnumString);
}; };
BEGIN_IID_TABLE(wxIEnumString) BEGIN_IID_TABLE(wxIEnumString)
@ -175,12 +178,244 @@ END_IID_TABLE;
IMPLEMENT_IUNKNOWN_METHODS(wxIEnumString) 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<void **>(&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<void **>(&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<void **>(&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 #endif // HAS_AUTOCOMPLETE
// ============================================================================ // ============================================================================
// wxTextEntry implementation // 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 // operations on text
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -333,66 +568,65 @@ bool wxTextEntry::DoAutoCompleteFileNames()
return false; 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; return true;
} }
wxTextAutoCompleteData *wxTextEntry::GetOrCreateCompleter()
{
if ( !m_autoCompleteData )
{
wxTextAutoCompleteData * const ac = new wxTextAutoCompleteData(this);
if ( ac->IsOk() )
m_autoCompleteData = ac;
else
delete ac;
}
return m_autoCompleteData;
}
bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices) bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices)
{ {
// if we had an old enumerator we must reuse it as IAutoComplete doesn't wxTextAutoCompleteData * const ac = GetOrCreateCompleter();
// free it if we call Init() again (see #10968) -- and it's also simpler if ( !ac )
if ( m_enumStrings )
{
m_enumStrings->ChangeStrings(choices);
return true;
}
// 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<void **>(&pAutoComplete)
);
if ( FAILED(hr) )
{
wxLogApiError(wxT("CoCreateInstance(CLSID_AutoComplete)"), hr);
return false; return false;
}
// associate it with our strings ac->ChangeStrings(choices);
m_enumStrings = new wxIEnumString(choices);
m_enumStrings->AddRef(); return true;
hr = pAutoComplete->Init(GetEditHwnd(), m_enumStrings, NULL, NULL); }
m_enumStrings->Release();
if ( FAILED(hr) ) 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); if ( m_autoCompleteData )
return false; m_autoCompleteData->DisableCompletion();
//else: Nothing to do, we hadn't used auto-completion even before.
} }
else // Have a valid completer.
// if IAutoComplete2 is available, set more user-friendly options
IAutoComplete2 *pAutoComplete2 = NULL;
hr = pAutoComplete->QueryInterface
(
IID_IAutoComplete2,
reinterpret_cast<void **>(&pAutoComplete2)
);
if ( SUCCEEDED(hr) )
{ {
pAutoComplete2->SetOptions(ACO_AUTOSUGGEST | ACO_UPDOWNKEYDROPSLIST); wxTextAutoCompleteData * const ac = GetOrCreateCompleter();
pAutoComplete2->Release(); 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; return true;
} }