]> git.saurik.com Git - wxWidgets.git/commitdiff
Add support for dynamic auto-completion in wxTextEntry.
authorVadim Zeitlin <vadim@wxwidgets.org>
Sat, 16 Apr 2011 17:27:16 +0000 (17:27 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Sat, 16 Apr 2011 17:27:16 +0000 (17:27 +0000)
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

15 files changed:
Makefile.in
build/bakefiles/files.bkl
build/msw/wx_core.dsp
build/msw/wx_vc7_core.vcproj
build/msw/wx_vc8_core.vcproj
build/msw/wx_vc9_core.vcproj
docs/changes.txt
include/wx/msw/textentry.h
include/wx/textcompleter.h [new file with mode: 0644]
include/wx/textentry.h
interface/wx/textcompleter.h [new file with mode: 0644]
interface/wx/textentry.h
samples/widgets/widgets.cpp
src/common/textentrycmn.cpp
src/msw/textentry.cpp

index 0b3fa1f3f3af7c812c2c1a68de47024b809beb38..091d58b580ee75fb79e4761d370fbc6776ae086a 100644 (file)
@@ -3929,6 +3929,7 @@ COND_USE_GUI_1_ALL_GUI_HEADERS =  \
        wx/statbox.h \
        wx/stattext.h \
        wx/statusbr.h \
        wx/statbox.h \
        wx/stattext.h \
        wx/statusbr.h \
+       wx/textcompleter.h \
        wx/textctrl.h \
        wx/textdlg.h \
        wx/textentry.h \
        wx/textctrl.h \
        wx/textdlg.h \
        wx/textentry.h \
index 7f7e571009e91dec50ddd915e66ee33eb4998858..fca4e9bb376d93932563e8c842948c38c0980ea1 100644 (file)
@@ -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/statbox.h
     wx/stattext.h
     wx/statusbr.h
+    wx/textcompleter.h
     wx/textctrl.h
     wx/textdlg.h
     wx/textentry.h
     wx/textctrl.h
     wx/textdlg.h
     wx/textentry.h
index e5032676c5a44bbd99059af9255b99cb97126934..d66a99bcc052b5ec4f9ecc970d96f83a92eda24e 100644 (file)
@@ -6741,6 +6741,10 @@ SOURCE=..\..\include\wx\tbarbase.h
 # End Source File\r
 # Begin Source File\r
 \r
 # End Source File\r
 # Begin Source File\r
 \r
+SOURCE=..\..\include\wx\textcompleter.h
+# End Source File
+# Begin Source File
+
 SOURCE=..\..\include\wx\textctrl.h\r
 # End Source File\r
 # Begin Source File\r
 SOURCE=..\..\include\wx\textctrl.h\r
 # End Source File\r
 # Begin Source File\r
index 01639cb3b1189f7208546752204ca3eaed644e39..511e2a8640e22b80981f580b369e917168f0c3ea 100644 (file)
                                RelativePath="..\..\include\wx\tbarbase.h">\r
                        </File>\r
                        <File\r
                                RelativePath="..\..\include\wx\tbarbase.h">\r
                        </File>\r
                        <File\r
+                               RelativePath="..\..\include\wx\textcompleter.h">
+                       </File>
+                       <File
                                RelativePath="..\..\include\wx\textctrl.h">\r
                        </File>\r
                        <File\r
                                RelativePath="..\..\include\wx\textctrl.h">\r
                        </File>\r
                        <File\r
index 5321e2436818fd7fd27fcab70a2020e1b2b56c6a..04728f4f7b33f9b8e855b3031a530579d172d2c4 100644 (file)
                                >\r
                        </File>\r
                        <File\r
                                >\r
                        </File>\r
                        <File\r
+                               RelativePath="..\..\include\wx\textcompleter.h"
+                               >
+                       </File>
+                       <File
                                RelativePath="..\..\include\wx\textctrl.h"\r
                                >\r
                        </File>\r
                                RelativePath="..\..\include\wx\textctrl.h"\r
                                >\r
                        </File>\r
index 4713e649fc765d27c60aaa6a63baf82b3d4ba82e..c4ed51456b80114f3644cfa6675e25396e18db30 100644 (file)
                                >\r
                        </File>\r
                        <File\r
                                >\r
                        </File>\r
                        <File\r
+                               RelativePath="..\..\include\wx\textcompleter.h"
+                               >
+                       </File>
+                       <File
                                RelativePath="..\..\include\wx\textctrl.h"\r
                                >\r
                        </File>\r
                                RelativePath="..\..\include\wx\textctrl.h"\r
                                >\r
                        </File>\r
index e542d014da3e7f4b0fd2cda646918bbbba8a1ad3..63441b056debf1981aff86657f5ae13d514e5815 100644 (file)
@@ -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 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().
 - wxAUI: support auto-orientable toolbars (wsu).
 - wxAUI: add support for icons in pane title bars (triton).
 - Added wxPanel::SetBackgroundBitmap().
index aa23df118cda02e55802531bde699fbc5b00f971..a2f5f95be5a0c5bef191e254705aa5d81702ba48 100644 (file)
@@ -11,6 +11,8 @@
 #ifndef _WX_MSW_TEXTENTRY_H_
 #define _WX_MSW_TEXTENTRY_H_
 
 #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
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
 // wxTextEntry: common part of wxComboBox and (single line) wxTextCtrl
 // ----------------------------------------------------------------------------
 class WXDLLIMPEXP_CORE wxTextEntry : public wxTextEntryBase
 {
 public:
 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);
 
     // 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();
 #if wxUSE_OLE
     virtual bool DoAutoCompleteStrings(const wxArrayString& choices);
     virtual bool DoAutoCompleteFileNames();
+    virtual bool DoAutoCompleteCustom(wxTextCompleter *completer);
 #endif // wxUSE_OLE
 
 private:
 #endif // wxUSE_OLE
 
 private:
@@ -85,8 +84,16 @@ private:
     virtual WXHWND GetEditHWND() const = 0;
 
 #if wxUSE_OLE
     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
 };
 
 #endif // wxUSE_OLE
 };
 
diff --git a/include/wx/textcompleter.h b/include/wx/textcompleter.h
new file mode 100644 (file)
index 0000000..76aae00
--- /dev/null
@@ -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_
index f72828925158674875df75ccdb59f621d2b396f7..7601444ca84d89360c0b2cbfcd2af2850ab5e52d 100644 (file)
@@ -16,6 +16,7 @@
 typedef long wxTextPos;
 
 class WXDLLIMPEXP_FWD_BASE wxArrayString;
 typedef long wxTextPos;
 
 class WXDLLIMPEXP_FWD_BASE wxArrayString;
+class WXDLLIMPEXP_FWD_CORE wxTextCompleter;
 class WXDLLIMPEXP_FWD_CORE wxTextEntryHintData;
 class WXDLLIMPEXP_FWD_CORE wxWindow;
 
 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
 
     // 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
     //
     // 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(); }
 
     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
     // ------
 
     // status
     // ------
@@ -224,6 +231,7 @@ protected:
     virtual bool DoAutoCompleteStrings(const wxArrayString& WXUNUSED(choices))
         { return false; }
     virtual bool DoAutoCompleteFileNames() { return false; }
     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
 
 
     // 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 (file)
index 0000000..c900821
--- /dev/null
@@ -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;
+};
index f29cc17239b335414ef234166794f47c84238245..565bbaf5971e4f925b36df677d27b56802a86a1e 100644 (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
         single-line text control using all valid file system paths.
     /**
         Call this function to enable auto-completion of the text typed in a
         single-line text control using all valid file system paths.
index 1ec72612b9b12efa3d5a7dc23c2e714b38a46126..c5f470047868c40fb62b02e65e212cfb1f71acad 100644 (file)
@@ -50,6 +50,7 @@
 #include "wx/textdlg.h"
 #include "wx/imaglist.h"
 #include "wx/wupdlock.h"
 #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"
 
 #include "wx/persist/toplevel.h"
 #include "wx/persist/treebook.h"
@@ -98,6 +99,7 @@ enum
     TextEntry_DisableAutoComplete = TextEntry_Begin,
     TextEntry_AutoCompleteFixed,
     TextEntry_AutoCompleteFilenames,
     TextEntry_DisableAutoComplete = TextEntry_Begin,
     TextEntry_AutoCompleteFixed,
     TextEntry_AutoCompleteFilenames,
+    TextEntry_AutoCompleteCustom,
 
     TextEntry_SetHint,
     TextEntry_End
 
     TextEntry_SetHint,
     TextEntry_End
@@ -172,6 +174,7 @@ protected:
     void OnDisableAutoComplete(wxCommandEvent& event);
     void OnAutoCompleteFixed(wxCommandEvent& event);
     void OnAutoCompleteFilenames(wxCommandEvent& event);
     void OnDisableAutoComplete(wxCommandEvent& event);
     void OnAutoCompleteFixed(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_AutoCompleteFixed,     WidgetsFrame::OnAutoCompleteFixed)
     EVT_MENU(TextEntry_AutoCompleteFilenames, WidgetsFrame::OnAutoCompleteFilenames)
     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)
 
 
     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"));
                                    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");
 
     menuTextEntry->AppendSeparator();
     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))
 {
     wxTextEntryBase *entry = CurrentPage()->GetTextEntry();
 void WidgetsFrame::OnSetHint(wxCommandEvent& WXUNUSED(event))
 {
     wxTextEntryBase *entry = CurrentPage()->GetTextEntry();
index 51020ddcb40cf7e130645c488545d2a8100b074a..66a0a6452e2ba1bc01fb373410c69c24dd7527ac 100644 (file)
@@ -31,6 +31,7 @@
 #endif //WX_PRECOMP
 
 #include "wx/textentry.h"
 #endif //WX_PRECOMP
 
 #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
index d66b9c6bf3f60e64bafbbfd98ad6b3662e060e1a..161035b59ff5131cc0fd40c019be685ad3fb3f20 100644 (file)
@@ -31,6 +31,7 @@
 #if wxUSE_TEXTCTRL || wxUSE_COMBOBOX
 
 #include "wx/textentry.h"
 #if wxUSE_TEXTCTRL || wxUSE_COMBOBOX
 
 #include "wx/textentry.h"
+#include "wx/textcompleter.h"
 #include "wx/dynlib.h"
 
 #include "wx/msw/private.h"
 #include "wx/dynlib.h"
 
 #include "wx/msw/private.h"
@@ -54,6 +55,7 @@
 
 #include "wx/msw/ole/oleutils.h"
 #include <shldisp.h>
 
 #include "wx/msw/ole/oleutils.h"
 #include <shldisp.h>
+#include <shobjidl.h>
 
 #if defined(__MINGW32__) || defined (__WATCOMC__) || defined(__CYGWIN__)
     // needed for IID_IAutoComplete, IID_IAutoComplete2 and ACO_AUTOSUGGEST
 
 #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:
 class wxIEnumString : public IEnumString
 {
 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 )
             return E_POINTER;
 
         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;
 
         e->AddRef();
         *ppEnum = e;
@@ -165,7 +166,9 @@ private:
     wxArrayString m_strings;
     unsigned m_index;
 
     wxArrayString m_strings;
     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
 
 // ============================================================================
 // wxTextEntry implementation
 // ============================================================================
 
 #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
 // ----------------------------------------------------------------------------
 // ----------------------------------------------------------------------------
 // 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;
 }
 
-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<void **>(&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;
         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<void **>(&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;
 }
 
     return true;
 }