From 18983cf538275077b3e918b1a9a67a8ff0e7731b Mon Sep 17 00:00:00 2001 From: AliKet Date: Fri, 26 Jan 2018 23:43:33 +0100 Subject: [PATCH 01/13] Implement dynamic auto-completion for wxGTK Make completion using custom wxTextCompleter work in wxGTK too. Closes #18061. --- include/wx/gtk/textentry.h | 15 +- samples/widgets/widgets.cpp | 38 ++++- src/gtk/textentry.cpp | 326 +++++++++++++++++++++++++++++++++--- 3 files changed, 356 insertions(+), 23 deletions(-) diff --git a/include/wx/gtk/textentry.h b/include/wx/gtk/textentry.h index 9f573d0bb4..674eab2ebd 100644 --- a/include/wx/gtk/textentry.h +++ b/include/wx/gtk/textentry.h @@ -14,6 +14,8 @@ typedef struct _GdkEventKey GdkEventKey; typedef struct _GtkEditable GtkEditable; typedef struct _GtkEntry GtkEntry; +class wxTextAutoCompleteData; // private class used only by wxTextEntry itself + // ---------------------------------------------------------------------------- // wxTextEntry: roughly corresponds to GtkEditable // ---------------------------------------------------------------------------- @@ -21,7 +23,8 @@ typedef struct _GtkEntry GtkEntry; class WXDLLIMPEXP_CORE wxTextEntry : public wxTextEntryBase { public: - wxTextEntry() { m_isUpperCase = false; } + wxTextEntry(); + virtual ~wxTextEntry(); // implement wxTextEntryBase pure virtual methods virtual void WriteText(const wxString& text) wxOVERRIDE; @@ -76,6 +79,7 @@ protected: virtual wxPoint DoGetMargins() const wxOVERRIDE; virtual bool DoAutoCompleteStrings(const wxArrayString& choices) wxOVERRIDE; + virtual bool DoAutoCompleteCustom(wxTextCompleter *completer) wxOVERRIDE; // Override the base class method to use GtkEntry IM context. virtual int GTKIMFilterKeypress(GdkEventKey* event) const; @@ -90,6 +94,15 @@ private: // implement this to return the associated GtkEntry virtual GtkEntry *GetEntry() const = 0; + 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 GetEditable() method. + friend class wxTextAutoCompleteData; + bool m_isUpperCase; }; diff --git a/samples/widgets/widgets.cpp b/samples/widgets/widgets.cpp index 9cc7de5cec..ef66b725a6 100644 --- a/samples/widgets/widgets.cpp +++ b/samples/widgets/widgets.cpp @@ -46,6 +46,7 @@ #include "wx/sizer.h" #include "wx/colordlg.h" #include "wx/fontdlg.h" +#include "wx/numdlg.h" #include "wx/textdlg.h" #include "wx/imaglist.h" #include "wx/wupdlock.h" @@ -108,6 +109,7 @@ enum TextEntry_AutoCompleteFilenames, TextEntry_AutoCompleteDirectories, TextEntry_AutoCompleteCustom, + TextEntry_AutoCompleteKeyLength, TextEntry_SetHint, TextEntry_End @@ -188,6 +190,7 @@ protected: void OnAutoCompleteFilenames(wxCommandEvent& event); void OnAutoCompleteDirectories(wxCommandEvent& event); void OnAutoCompleteCustom(wxCommandEvent& event); + void OnAutoCompleteKeyLength(wxCommandEvent& event); void OnSetHint(wxCommandEvent& event); @@ -218,6 +221,9 @@ private: // the book containing the test pages WidgetsBookCtrl *m_book; + // + int m_prefixMinLength; + // any class wishing to process wxWidgets events must use this macro wxDECLARE_EVENT_TABLE(); }; @@ -317,6 +323,7 @@ wxBEGIN_EVENT_TABLE(WidgetsFrame, wxFrame) EVT_MENU(TextEntry_AutoCompleteFilenames, WidgetsFrame::OnAutoCompleteFilenames) EVT_MENU(TextEntry_AutoCompleteDirectories, WidgetsFrame::OnAutoCompleteDirectories) EVT_MENU(TextEntry_AutoCompleteCustom, WidgetsFrame::OnAutoCompleteCustom) + EVT_MENU(TextEntry_AutoCompleteKeyLength, WidgetsFrame::OnAutoCompleteKeyLength) EVT_MENU(TextEntry_SetHint, WidgetsFrame::OnSetHint) @@ -386,6 +393,8 @@ WidgetsFrame::WidgetsFrame(const wxString& title) #endif // USE_LOG m_book = NULL; + m_prefixMinLength = 1; + #if wxUSE_MENUS // create the menubar wxMenuBar *mbar = new wxMenuBar; @@ -444,6 +453,8 @@ WidgetsFrame::WidgetsFrame(const wxString& title) menuTextEntry->AppendRadioItem(TextEntry_AutoCompleteCustom, wxT("&Custom auto-completion")); menuTextEntry->AppendSeparator(); + menuTextEntry->Append(TextEntry_AutoCompleteKeyLength, + wxT("&Minimum key length for auto-completion")); menuTextEntry->Append(TextEntry_SetHint, "Set help &hint"); mbar->Append(menuTextEntry, wxT("&Text")); @@ -1038,6 +1049,8 @@ void WidgetsFrame::OnAutoCompleteCustom(wxCommandEvent& WXUNUSED(event)) class CustomTextCompleter : public wxTextCompleterSimple { public: + CustomTextCompleter( int length ) : m_minLength( length ) {} + virtual void GetCompletions(const wxString& prefix, wxArrayString& res) wxOVERRIDE { // This is used for illustrative purposes only and shows how many @@ -1066,7 +1079,10 @@ void WidgetsFrame::OnAutoCompleteCustom(wxCommandEvent& WXUNUSED(event)) // 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() ) + // Or if we know in advance that a prefix with one or two characters + // would still results in too many choices too. + if ( prefix.empty() || + prefix.length() < static_cast(m_minLength) ) return; // The only valid strings start with 3 digits so check for their @@ -1117,9 +1133,11 @@ void WidgetsFrame::OnAutoCompleteCustom(wxCommandEvent& WXUNUSED(event)) res.push_back(prefix + c); } } + + int m_minLength; }; - if ( entry->AutoComplete(new CustomTextCompleter) ) + if ( entry->AutoComplete( new CustomTextCompleter( m_prefixMinLength ) ) ) { wxLogMessage("Enabled custom auto completer for \"NNN XX\" items " "(where N is a digit and X is a letter)."); @@ -1130,6 +1148,22 @@ void WidgetsFrame::OnAutoCompleteCustom(wxCommandEvent& WXUNUSED(event)) } } +void WidgetsFrame::OnAutoCompleteKeyLength(wxCommandEvent& WXUNUSED(event)) +{ + const wxString message = "The auto-completion is triggered if and only if\n" + "the length of the search key (prefix) is at least [LENGTH].\n" + "Hint: negative values disable auto-completion."; + const wxString prompt = "Enter the minimum key length:"; + const wxString caption = "Minimum key length"; + + m_prefixMinLength = wxGetNumberFromUser(message, prompt, caption, 1, -1, 100, this); + + wxCommandEvent theEvent(wxEVT_MENU, TextEntry_AutoCompleteCustom); + ProcessEventLocally(theEvent); + + wxLogMessage("The minimum key length for autocomplete is : %d.", m_prefixMinLength); +} + void WidgetsFrame::OnSetHint(wxCommandEvent& WXUNUSED(event)) { wxTextEntryBase *entry = CurrentPage()->GetTextEntry(); diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index 966d9b9153..a25c312529 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -25,9 +25,11 @@ #if wxUSE_TEXTCTRL || wxUSE_COMBOBOX #ifndef WX_PRECOMP + #include "wx/event.h" #include "wx/textentry.h" - #include "wx/window.h" #include "wx/textctrl.h" + #include "wx/textcompleter.h" + #include "wx/window.h" #endif //WX_PRECOMP #include @@ -193,10 +195,268 @@ wx_gtk_paste_clipboard_callback( GtkWidget *widget, wxWindow *win ) } // extern "C" +// This class gathers the all auto-complete-related stuff we use. It is +// allocated on demand by wxTextEntry when AutoComplete() is called. +// +// GTK already has completion functionality support for a GtkEntry via +// GtkEntryCompletion. This class simply forwards to GtkListStore +// in case we used ChangeStrings() overload. or to wxTextCompleter +// associated with it otherwise. +class wxTextAutoCompleteData +{ +public: + // The constructor associates us with the given text entry. + wxEXPLICIT wxTextAutoCompleteData(wxTextEntry *entry) + : m_entry(entry) + { + m_completer = NULL; + + m_isDynamicCompleter = false; + + m_newCompletionsNeeded = m_entry->IsEmpty(); + + // this asserts if entry is multiline. + // because multiline is actually a GtkTextView not a GtkEntry. + // even GetEditable() will return NULL if entry is multiline. + + wxCHECK_RET( GTK_IS_ENTRY(GetGtkEntry()), + "auto completion doesn't work with this control" ); + } + + ~wxTextAutoCompleteData() + { + delete m_completer; + } + + // Must be called after creating this object to verify if initializing it + // succeeded. + bool IsOk() const + { + return GTK_IS_ENTRY( GetGtkEntry() ); + } + + void ChangeStrings(const wxArrayString& strings) + { + wxDELETE( m_completer ); + + DoUpdateCompleterType(); + + DoEnableCompletion(); + + GtkListStore * const store = gtk_list_store_new (1, G_TYPE_STRING); + GtkTreeIter iter; + + for ( wxArrayString::const_iterator i = strings.begin(); + i != strings.end(); + ++i ) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, (const gchar *)i->utf8_str(), -1); + } + + gtk_entry_completion_set_model (GetEntryCompletion(), GTK_TREE_MODEL(store)); + g_object_unref (store); + + DoRefresh(); + } + + // Takes ownership of the pointer if it is non-NULL. + bool ChangeCustomCompleter(wxTextCompleter *completer) + { + delete m_completer; + m_completer = completer; + + if ( m_completer ) + { + wxTextCompleterFixed* fixedCompl = + dynamic_cast(m_completer); + + if ( fixedCompl ) + { + wxArrayString completions; + fixedCompl->GetCompletions(wxEmptyString, completions); + + ChangeStrings(completions); + + wxDELETE(m_completer); + return true; + } + + DoEnableCompletion(); + + DoUpdateCompletionModel(); + } + else + { + DisableCompletion(); + } + + DoUpdateCompleterType(); + + return true; + } + + void DisableCompletion() + { + gtk_entry_set_completion (GetGtkEntry(), NULL); + + wxDELETE(m_completer); + DoUpdateCompleterType(); + } + +private: + + void DoEnableCompletion() + { + if ( !GetEntryCompletion() ) + { + GtkEntryCompletion * const completion = gtk_entry_completion_new(); + + gtk_entry_completion_set_text_column (completion, 0); + gtk_entry_set_completion (GetGtkEntry(), completion); + } + } + + // for a given prefix, if DoUpdateCompletionModel() succeeds, + // we won't do any further update of the model as long as we + // do not clear the textentry. but then we have to start over again. + + void OnEntryChanged( wxCommandEvent& event ) + { + if ( event.GetString().empty() ) + { + m_newCompletionsNeeded = true; + } + else + { + if ( m_newCompletionsNeeded ) + DoUpdateCompletionModel(); + } + + event.Skip(); + } + + void DoUpdateCompleterType() + { + const bool isDynamic = (m_completer != NULL); + + if ( m_isDynamicCompleter == isDynamic ) + // we already connected/disconnected to/from + // the event handler. + return; + + m_isDynamicCompleter = isDynamic; + + wxWindow * const win = m_entry->GetEditableWindow(); + + // Disconnect from the event handler if we request + // a non-dynamic behaviour of our completion methode + // (e.g. completions are supplied via + // ChangeStrings() or wxTextCompleterFixed ) + // Connect otherwise. + // + // The event handler role is to request from the m_completer + // to generate dynamically new completions (e.g from database) + // if a certain condition is met (e.g. textentry is cleared + // and/or typed in text length >= *MIN_PREFIX_LENGTH* ) + // + + if ( !m_completer ) + { + win->Unbind(wxEVT_TEXT, &wxTextAutoCompleteData::OnEntryChanged, this); + } + else + { + win->Bind(wxEVT_TEXT, &wxTextAutoCompleteData::OnEntryChanged, this); + } + } + + void DoRefresh() + { + gtk_entry_completion_complete (GetEntryCompletion()); + } + + void DoUpdateCompletionModel() + { + wxASSERT_MSG( m_completer, "m_completer should not be null." ); + + const wxString prefix = m_entry->GetValue(); + + if ( m_completer->Start(prefix) ) + { + GtkListStore * const store = gtk_list_store_new (1, G_TYPE_STRING); + GtkTreeIter iter; + + for (;;) + { + const wxString s = m_completer->GetNext(); + if ( s.empty() ) + break; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, (const gchar *)s.utf8_str(), -1); + } + + gtk_entry_completion_set_model (GetEntryCompletion(), GTK_TREE_MODEL(store)); + g_object_unref (store); + + m_newCompletionsNeeded = false; + } + else + { + gtk_entry_completion_set_model (GetEntryCompletion(), NULL); + } + + DoRefresh(); + } + + + GtkEntry* GetGtkEntry() const { return m_entry->GetEntry(); } + + GtkEntryCompletion* GetEntryCompletion() const + { + return gtk_entry_get_completion (GetGtkEntry()); + } + + // The text entry we're associated with. + wxTextEntry * const m_entry; + + // Custom completer or NULL if none. + wxTextCompleter *m_completer; + + // helps to decide if we should connect/disconnect + // to/from the event handler. + bool m_isDynamicCompleter; + + // Each time we entered a new prefix, GtkEntryCompletion needs to be fed + // with new completions. And this flag lets as try to DoUpdateCompletionModel() + // and if it succeeds, it'll set the flag to false and OnEntryChanged() + // will not try to call it again unless we entered a new prefix. + bool m_newCompletionsNeeded; + + wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteData); +}; + + // ============================================================================ // wxTextEntry implementation // ============================================================================ +// ---------------------------------------------------------------------------- +// initialization and destruction +// ---------------------------------------------------------------------------- + +wxTextEntry::wxTextEntry() +{ + m_autoCompleteData = NULL; + m_isUpperCase = false; +} + +wxTextEntry::~wxTextEntry() +{ + delete m_autoCompleteData; +} + // ---------------------------------------------------------------------------- // text operations // ---------------------------------------------------------------------------- @@ -409,32 +669,58 @@ void wxTextEntry::GetSelection(long *from, long *to) const // auto completion // ---------------------------------------------------------------------------- -bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices) +wxTextAutoCompleteData *wxTextEntry::GetOrCreateCompleter() { - GtkEntry* const entry = (GtkEntry*)GetEditable(); - wxCHECK_MSG(GTK_IS_ENTRY(entry), false, "auto completion doesn't work with this control"); - - GtkListStore * const store = gtk_list_store_new(1, G_TYPE_STRING); - GtkTreeIter iter; - - for ( wxArrayString::const_iterator i = choices.begin(); - i != choices.end(); - ++i ) + if ( !m_autoCompleteData ) { - gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, - 0, (const gchar *)i->utf8_str(), - -1); + wxTextAutoCompleteData * const ac = new wxTextAutoCompleteData(this); + if ( ac->IsOk() ) + m_autoCompleteData = ac; + else + delete ac; } - GtkEntryCompletion * const completion = gtk_entry_completion_new(); - gtk_entry_completion_set_model(completion, GTK_TREE_MODEL(store)); - gtk_entry_completion_set_text_column(completion, 0); - gtk_entry_set_completion(entry, completion); - g_object_unref(completion); + return m_autoCompleteData; +} + +bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices) +{ + wxTextAutoCompleteData * const ac = GetOrCreateCompleter(); + if ( !ac ) + return false; + + 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 ) + { + if ( m_autoCompleteData ) + m_autoCompleteData->DisableCompletion(); + //else: Nothing to do, we hadn't used auto-completion even before. + } + else // Have a valid completer. + { + 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; + } + + return true; +} // ---------------------------------------------------------------------------- // editable status // ---------------------------------------------------------------------------- From a8c19c7bd24db7939fbe6ea3409b62be67f84a99 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Jan 2018 00:15:24 +0100 Subject: [PATCH 02/13] Just improve some comments Fix typos/wording in GTK autocompletion code comments. No real changes. --- src/gtk/textentry.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index a25c312529..91b47840b6 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -200,8 +200,8 @@ wx_gtk_paste_clipboard_callback( GtkWidget *widget, wxWindow *win ) // // GTK already has completion functionality support for a GtkEntry via // GtkEntryCompletion. This class simply forwards to GtkListStore -// in case we used ChangeStrings() overload. or to wxTextCompleter -// associated with it otherwise. +// in case we used ChangeStrings() overload, or to wxTextCompleter +// associated with it if ChangeCustomCompleter() was called. class wxTextAutoCompleteData { public: @@ -350,7 +350,7 @@ private: wxWindow * const win = m_entry->GetEditableWindow(); // Disconnect from the event handler if we request - // a non-dynamic behaviour of our completion methode + // a non-dynamic behaviour of our completion methods // (e.g. completions are supplied via // ChangeStrings() or wxTextCompleterFixed ) // Connect otherwise. @@ -376,6 +376,9 @@ private: gtk_entry_completion_complete (GetEntryCompletion()); } + // Recreate the model to contain all completions for the current prefix. + // + // This should only be called when using a custom completer. void DoUpdateCompletionModel() { wxASSERT_MSG( m_completer, "m_completer should not be null." ); @@ -428,8 +431,8 @@ private: // to/from the event handler. bool m_isDynamicCompleter; - // Each time we entered a new prefix, GtkEntryCompletion needs to be fed - // with new completions. And this flag lets as try to DoUpdateCompletionModel() + // Each time we enter a new prefix, GtkEntryCompletion needs to be fed with + // new completions. And this flag lets us try to DoUpdateCompletionModel() // and if it succeeds, it'll set the flag to false and OnEntryChanged() // will not try to call it again unless we entered a new prefix. bool m_newCompletionsNeeded; From 8030bd727a79d0493e7a2870c661522a217ecb2d Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Jan 2018 00:16:53 +0100 Subject: [PATCH 03/13] Refactor wxTextAutoCompleteData creation Use factory function instead of ctor and IsOk() check, as this simplifies the code using this class: if factory function fails, it can just return NULL, which is what the caller used to do explicitly after freeing the new object before. Also don't assert if there is no associated GtkEntry, AutoComplete() is supposed to just return false if using it with the given control is not implemented under the current platform. --- src/gtk/textentry.cpp | 46 +++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index 91b47840b6..c6e80cccc6 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -205,22 +205,17 @@ wx_gtk_paste_clipboard_callback( GtkWidget *widget, wxWindow *win ) class wxTextAutoCompleteData { public: - // The constructor associates us with the given text entry. - wxEXPLICIT wxTextAutoCompleteData(wxTextEntry *entry) - : m_entry(entry) + // Factory function, may return NULL if entry is invalid. + static wxTextAutoCompleteData* New(wxTextEntry *entry) { - m_completer = NULL; + if ( !GTK_IS_ENTRY(entry->GetEntry()) ) + { + // This is probably a multiline wxTextCtrl which doesn't have any + // associated GtkEntry. + return NULL; + } - m_isDynamicCompleter = false; - - m_newCompletionsNeeded = m_entry->IsEmpty(); - - // this asserts if entry is multiline. - // because multiline is actually a GtkTextView not a GtkEntry. - // even GetEditable() will return NULL if entry is multiline. - - wxCHECK_RET( GTK_IS_ENTRY(GetGtkEntry()), - "auto completion doesn't work with this control" ); + return new wxTextAutoCompleteData(entry); } ~wxTextAutoCompleteData() @@ -228,13 +223,6 @@ public: delete m_completer; } - // Must be called after creating this object to verify if initializing it - // succeeded. - bool IsOk() const - { - return GTK_IS_ENTRY( GetGtkEntry() ); - } - void ChangeStrings(const wxArrayString& strings) { wxDELETE( m_completer ); @@ -305,6 +293,16 @@ public: } private: + // Ctor is private, use New() to create objects of this type. + explicit wxTextAutoCompleteData(wxTextEntry *entry) + : m_entry(entry) + { + m_completer = NULL; + + m_isDynamicCompleter = false; + + m_newCompletionsNeeded = m_entry->IsEmpty(); + } void DoEnableCompletion() { @@ -676,11 +674,7 @@ wxTextAutoCompleteData *wxTextEntry::GetOrCreateCompleter() { if ( !m_autoCompleteData ) { - wxTextAutoCompleteData * const ac = new wxTextAutoCompleteData(this); - if ( ac->IsOk() ) - m_autoCompleteData = ac; - else - delete ac; + m_autoCompleteData = wxTextAutoCompleteData::New(this); } return m_autoCompleteData; From 9b9d13807707cf87dca76b2fddecb446d83d7e99 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Jan 2018 00:20:33 +0100 Subject: [PATCH 04/13] Factor out helper AppendToStore() function Simple refactoring to avoid repeating the same not quite obvious code for appending an item to GtkListStore twice. --- src/gtk/textentry.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index c6e80cccc6..8757540204 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -232,14 +232,12 @@ public: DoEnableCompletion(); GtkListStore * const store = gtk_list_store_new (1, G_TYPE_STRING); - GtkTreeIter iter; for ( wxArrayString::const_iterator i = strings.begin(); i != strings.end(); ++i ) { - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, (const gchar *)i->utf8_str(), -1); + AppendToStore(store, *i); } gtk_entry_completion_set_model (GetEntryCompletion(), GTK_TREE_MODEL(store)); @@ -304,6 +302,14 @@ private: m_newCompletionsNeeded = m_entry->IsEmpty(); } + // Helper function for appending a string to GtkListStore. + void AppendToStore(GtkListStore* store, const wxString& s) + { + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, (const gchar *)s.utf8_str(), -1); + } + void DoEnableCompletion() { if ( !GetEntryCompletion() ) @@ -386,7 +392,6 @@ private: if ( m_completer->Start(prefix) ) { GtkListStore * const store = gtk_list_store_new (1, G_TYPE_STRING); - GtkTreeIter iter; for (;;) { @@ -394,8 +399,7 @@ private: if ( s.empty() ) break; - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, (const gchar *)s.utf8_str(), -1); + AppendToStore(store, s); } gtk_entry_completion_set_model (GetEntryCompletion(), GTK_TREE_MODEL(store)); From 6f229cdcdeed53d48128fded12d61461a4e00702 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Jan 2018 00:22:25 +0100 Subject: [PATCH 05/13] Replace DoRefresh() with UseModel() helper function The new function both sets the new model and calls gtk_entry_completion_complete() instead of always doing first the one and then the other: if both calls needs to always be done together, it makes sense to have a function doing both of them. --- src/gtk/textentry.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index 8757540204..137fbde9fc 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -240,10 +240,8 @@ public: AppendToStore(store, *i); } - gtk_entry_completion_set_model (GetEntryCompletion(), GTK_TREE_MODEL(store)); + UseModel(store); g_object_unref (store); - - DoRefresh(); } // Takes ownership of the pointer if it is non-NULL. @@ -375,9 +373,12 @@ private: } } - void DoRefresh() + // Really change the completion model (which may be NULL). + void UseModel(GtkListStore* store) { - gtk_entry_completion_complete (GetEntryCompletion()); + GtkEntryCompletion* const c = gtk_entry_get_completion (GetGtkEntry()); + gtk_entry_completion_set_model (c, GTK_TREE_MODEL(store)); + gtk_entry_completion_complete (c); } // Recreate the model to contain all completions for the current prefix. @@ -402,17 +403,15 @@ private: AppendToStore(store, s); } - gtk_entry_completion_set_model (GetEntryCompletion(), GTK_TREE_MODEL(store)); + UseModel(store); g_object_unref (store); m_newCompletionsNeeded = false; } else { - gtk_entry_completion_set_model (GetEntryCompletion(), NULL); + UseModel(NULL); } - - DoRefresh(); } From f354b0a1b5db7d6ab76990b54ce9baf19c701a49 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Jan 2018 00:23:59 +0100 Subject: [PATCH 06/13] Remove almost unused GetEntryCompletion() function This function was now used only once and it's not really useful to have a trivial wrapper around the corresponding GTK+ function, just use it directly. --- src/gtk/textentry.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index 137fbde9fc..1b7b003deb 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -310,7 +310,7 @@ private: void DoEnableCompletion() { - if ( !GetEntryCompletion() ) + if ( !gtk_entry_get_completion (GetGtkEntry()) ) { GtkEntryCompletion * const completion = gtk_entry_completion_new(); @@ -417,10 +417,6 @@ private: GtkEntry* GetGtkEntry() const { return m_entry->GetEntry(); } - GtkEntryCompletion* GetEntryCompletion() const - { - return gtk_entry_get_completion (GetGtkEntry()); - } // The text entry we're associated with. wxTextEntry * const m_entry; From 96308f0534294faeaad8fe915f743ca188f7714f Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Jan 2018 00:24:58 +0100 Subject: [PATCH 07/13] Use wxGtkObject RAII wrapper for GtkListStore Prefer RAII wrapper class to manual g_object_unref() calls. --- src/gtk/textentry.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index 1b7b003deb..66c9c99516 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -35,6 +35,7 @@ #include #include "wx/gtk/private.h" #include "wx/gtk/private/gtk2-compat.h" +#include "wx/gtk/private/object.h" #include "wx/gtk/private/string.h" //----------------------------------------------------------------------------- @@ -231,7 +232,7 @@ public: DoEnableCompletion(); - GtkListStore * const store = gtk_list_store_new (1, G_TYPE_STRING); + wxGtkObject store(gtk_list_store_new (1, G_TYPE_STRING)); for ( wxArrayString::const_iterator i = strings.begin(); i != strings.end(); @@ -241,7 +242,6 @@ public: } UseModel(store); - g_object_unref (store); } // Takes ownership of the pointer if it is non-NULL. @@ -392,7 +392,7 @@ private: if ( m_completer->Start(prefix) ) { - GtkListStore * const store = gtk_list_store_new (1, G_TYPE_STRING); + wxGtkObject store(gtk_list_store_new (1, G_TYPE_STRING)); for (;;) { @@ -404,7 +404,6 @@ private: } UseModel(store); - g_object_unref (store); m_newCompletionsNeeded = false; } From d4c84ce7453217268f9dd325523fc9c90d649f2b Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Jan 2018 00:27:21 +0100 Subject: [PATCH 08/13] Remove special check for wxTextCompleterFixed There should be no need to handle this class specially and it's supposed to be just an implementation detail, so don't add any dependencies on it (if it's really important to optimize for this case, the check should be done for wxTextCompleterSimple and use wxRTTI as wxWidgets still supports being built without standard C++ RTTI). --- src/gtk/textentry.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index 66c9c99516..efffa656d1 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -252,20 +252,6 @@ public: if ( m_completer ) { - wxTextCompleterFixed* fixedCompl = - dynamic_cast(m_completer); - - if ( fixedCompl ) - { - wxArrayString completions; - fixedCompl->GetCompletions(wxEmptyString, completions); - - ChangeStrings(completions); - - wxDELETE(m_completer); - return true; - } - DoEnableCompletion(); DoUpdateCompletionModel(); From 02f2159aeade4eccd9d7dfecb0854e030d19ea83 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Jan 2018 01:07:18 +0100 Subject: [PATCH 09/13] Split wxTextAutoCompleteData in two subclasses Doing two different things in the same class, using m_isDynamicCompleter to determine which kind of completion is used, was not very clear, so create two simple classes each of which does one and one thing only and create the one we need in wxTextEntry methods. Note that wxTextAutoCompleteDynamic can assume to always have a valid wxTextCompleter as otherwise no wxTextAutoCompleteData is needed at all, which results in more simplifications. There should be no changes in behaviour. --- include/wx/gtk/textentry.h | 6 +- src/gtk/textentry.cpp | 331 ++++++++++++++++++++----------------- 2 files changed, 177 insertions(+), 160 deletions(-) diff --git a/include/wx/gtk/textentry.h b/include/wx/gtk/textentry.h index 674eab2ebd..e55fad8d4c 100644 --- a/include/wx/gtk/textentry.h +++ b/include/wx/gtk/textentry.h @@ -94,13 +94,11 @@ private: // implement this to return the associated GtkEntry virtual GtkEntry *GetEntry() const = 0; - wxTextAutoCompleteData *GetOrCreateCompleter(); - // Various auto-completion-related stuff, only used if any of AutoComplete() - // methods are called. Use the function above to access it. + // methods are called. wxTextAutoCompleteData *m_autoCompleteData; - // It needs to call our GetEditable() method. + // It needs to call our GetEntry() method. friend class wxTextAutoCompleteData; bool m_isUpperCase; diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index efffa656d1..9f17072503 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -196,42 +196,97 @@ wx_gtk_paste_clipboard_callback( GtkWidget *widget, wxWindow *win ) } // extern "C" -// This class gathers the all auto-complete-related stuff we use. It is -// allocated on demand by wxTextEntry when AutoComplete() is called. -// -// GTK already has completion functionality support for a GtkEntry via -// GtkEntryCompletion. This class simply forwards to GtkListStore -// in case we used ChangeStrings() overload, or to wxTextCompleter -// associated with it if ChangeCustomCompleter() was called. +// Base class for wxTextAutoCompleteFixed and wxTextAutoCompleteDynamic below. class wxTextAutoCompleteData { +public: + // This method is only implemented by wxTextAutoCompleteFixed and will just + // return false for wxTextAutoCompleteDynamic. + virtual bool ChangeStrings(const wxArrayString& strings) = 0; + + // Conversely, this one is only implemented for wxTextAutoCompleteDynamic + // and will just return false (without taking ownership of the argument!) + // for wxTextAutoCompleteFixed. + virtual bool ChangeCompleter(wxTextCompleter* completer) = 0; + + void DisableCompletion() + { + gtk_entry_set_completion (GetGtkEntry(), NULL); + } + + virtual ~wxTextAutoCompleteData() + { + // Do not call DisableCompletion() here, it would result in problems + // when this object is destroyed from wxTextEntry dtor as the real + // control (e.g. wxTextCtrl) is already destroyed by then. + } + +protected: + // Check if completion can be used with this entry. + static bool CanComplete(wxTextEntry* entry) + { + // If this check fails, this is probably a multiline wxTextCtrl which + // doesn't have any associated GtkEntry. + return GTK_IS_ENTRY(entry->GetEntry()); + } + + explicit wxTextAutoCompleteData(wxTextEntry* entry) + : m_entry(entry) + { + GtkEntryCompletion* const completion = gtk_entry_completion_new(); + + gtk_entry_completion_set_text_column (completion, 0); + gtk_entry_set_completion (GetGtkEntry(), completion); + } + + // Provide access to wxTextEntry::GetEditableWindow() to the derived + // classes: we can call it because this class is a friend of wxTextEntry, + // but the derived classes can't do it directly. + static wxWindow* GetEditableWindow(wxTextEntry* entry) + { + return entry->GetEditableWindow(); + } + + GtkEntry* GetGtkEntry() const { return m_entry->GetEntry(); } + + // Helper function for appending a string to GtkListStore. + void AppendToStore(GtkListStore* store, const wxString& s) + { + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, (const gchar *)s.utf8_str(), -1); + } + + // Really change the completion model (which may be NULL). + void UseModel(GtkListStore* store) + { + GtkEntryCompletion* const c = gtk_entry_get_completion (GetGtkEntry()); + gtk_entry_completion_set_model (c, GTK_TREE_MODEL(store)); + gtk_entry_completion_complete (c); + } + + + // The text entry we're associated with. + wxTextEntry * const m_entry; + + wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteData); +}; + +// This class simply forwards to GtkListStore. +class wxTextAutoCompleteFixed : public wxTextAutoCompleteData +{ public: // Factory function, may return NULL if entry is invalid. - static wxTextAutoCompleteData* New(wxTextEntry *entry) + static wxTextAutoCompleteFixed* New(wxTextEntry *entry) { - if ( !GTK_IS_ENTRY(entry->GetEntry()) ) - { - // This is probably a multiline wxTextCtrl which doesn't have any - // associated GtkEntry. + if ( !CanComplete(entry) ) return NULL; - } - return new wxTextAutoCompleteData(entry); + return new wxTextAutoCompleteFixed(entry); } - ~wxTextAutoCompleteData() + virtual bool ChangeStrings(const wxArrayString& strings) wxOVERRIDE { - delete m_completer; - } - - void ChangeStrings(const wxArrayString& strings) - { - wxDELETE( m_completer ); - - DoUpdateCompleterType(); - - DoEnableCompletion(); - wxGtkObject store(gtk_list_store_new (1, G_TYPE_STRING)); for ( wxArrayString::const_iterator i = strings.begin(); @@ -242,67 +297,75 @@ public: } UseModel(store); - } - - // Takes ownership of the pointer if it is non-NULL. - bool ChangeCustomCompleter(wxTextCompleter *completer) - { - delete m_completer; - m_completer = completer; - - if ( m_completer ) - { - DoEnableCompletion(); - - DoUpdateCompletionModel(); - } - else - { - DisableCompletion(); - } - - DoUpdateCompleterType(); return true; } - void DisableCompletion() + virtual bool ChangeCompleter(wxTextCompleter*) wxOVERRIDE { - gtk_entry_set_completion (GetGtkEntry(), NULL); - - wxDELETE(m_completer); - DoUpdateCompleterType(); + return false; } private: // Ctor is private, use New() to create objects of this type. - explicit wxTextAutoCompleteData(wxTextEntry *entry) - : m_entry(entry) + explicit wxTextAutoCompleteFixed(wxTextEntry *entry) + : wxTextAutoCompleteData(entry) + { + } + + wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteFixed); +}; + +// Dynamic completion using the provided custom wxTextCompleter. +class wxTextAutoCompleteDynamic : public wxTextAutoCompleteData +{ +public: + static wxTextAutoCompleteDynamic* New(wxTextEntry *entry) + { + if ( !CanComplete(entry) ) + return NULL; + + wxWindow * const win = GetEditableWindow(entry); + if ( !win ) + return NULL; + + return new wxTextAutoCompleteDynamic(entry, win); + } + + virtual ~wxTextAutoCompleteDynamic() + { + delete m_completer; + + m_win->Unbind(wxEVT_TEXT, &wxTextAutoCompleteDynamic::OnEntryChanged, this); + } + + virtual bool ChangeStrings(const wxArrayString&) wxOVERRIDE + { + return false; + } + + // Takes ownership of the pointer which must be non-NULL. + virtual bool ChangeCompleter(wxTextCompleter *completer) wxOVERRIDE + { + delete m_completer; + m_completer = completer; + + DoUpdateCompletionModel(); + + return true; + } + +private: + // Ctor is private, use New() to create objects of this type. + explicit wxTextAutoCompleteDynamic(wxTextEntry *entry, wxWindow *win) + : wxTextAutoCompleteData(entry), + m_win(win) { m_completer = NULL; - m_isDynamicCompleter = false; - m_newCompletionsNeeded = m_entry->IsEmpty(); - } - // Helper function for appending a string to GtkListStore. - void AppendToStore(GtkListStore* store, const wxString& s) - { - GtkTreeIter iter; - gtk_list_store_append (store, &iter); - gtk_list_store_set (store, &iter, 0, (const gchar *)s.utf8_str(), -1); - } - - void DoEnableCompletion() - { - if ( !gtk_entry_get_completion (GetGtkEntry()) ) - { - GtkEntryCompletion * const completion = gtk_entry_completion_new(); - - gtk_entry_completion_set_text_column (completion, 0); - gtk_entry_set_completion (GetGtkEntry(), completion); - } + win->Bind(wxEVT_TEXT, &wxTextAutoCompleteDynamic::OnEntryChanged, this); } // for a given prefix, if DoUpdateCompletionModel() succeeds, @@ -324,57 +387,10 @@ private: event.Skip(); } - void DoUpdateCompleterType() - { - const bool isDynamic = (m_completer != NULL); - - if ( m_isDynamicCompleter == isDynamic ) - // we already connected/disconnected to/from - // the event handler. - return; - - m_isDynamicCompleter = isDynamic; - - wxWindow * const win = m_entry->GetEditableWindow(); - - // Disconnect from the event handler if we request - // a non-dynamic behaviour of our completion methods - // (e.g. completions are supplied via - // ChangeStrings() or wxTextCompleterFixed ) - // Connect otherwise. - // - // The event handler role is to request from the m_completer - // to generate dynamically new completions (e.g from database) - // if a certain condition is met (e.g. textentry is cleared - // and/or typed in text length >= *MIN_PREFIX_LENGTH* ) - // - - if ( !m_completer ) - { - win->Unbind(wxEVT_TEXT, &wxTextAutoCompleteData::OnEntryChanged, this); - } - else - { - win->Bind(wxEVT_TEXT, &wxTextAutoCompleteData::OnEntryChanged, this); - } - } - - // Really change the completion model (which may be NULL). - void UseModel(GtkListStore* store) - { - GtkEntryCompletion* const c = gtk_entry_get_completion (GetGtkEntry()); - gtk_entry_completion_set_model (c, GTK_TREE_MODEL(store)); - gtk_entry_completion_complete (c); - } - // Recreate the model to contain all completions for the current prefix. - // - // This should only be called when using a custom completer. void DoUpdateCompletionModel() { - wxASSERT_MSG( m_completer, "m_completer should not be null." ); - - const wxString prefix = m_entry->GetValue(); + const wxString& prefix = m_entry->GetValue(); if ( m_completer->Start(prefix) ) { @@ -400,18 +416,11 @@ private: } - GtkEntry* GetGtkEntry() const { return m_entry->GetEntry(); } - - - // The text entry we're associated with. - wxTextEntry * const m_entry; - - // Custom completer or NULL if none. + // Custom completer. wxTextCompleter *m_completer; - // helps to decide if we should connect/disconnect - // to/from the event handler. - bool m_isDynamicCompleter; + // The associated window, we need to store it to unbind our event handler. + wxWindow* const m_win; // Each time we enter a new prefix, GtkEntryCompletion needs to be fed with // new completions. And this flag lets us try to DoUpdateCompletionModel() @@ -419,7 +428,7 @@ private: // will not try to call it again unless we entered a new prefix. bool m_newCompletionsNeeded; - wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteData); + wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteDynamic); }; @@ -654,23 +663,21 @@ void wxTextEntry::GetSelection(long *from, long *to) const // auto completion // ---------------------------------------------------------------------------- -wxTextAutoCompleteData *wxTextEntry::GetOrCreateCompleter() -{ - if ( !m_autoCompleteData ) - { - m_autoCompleteData = wxTextAutoCompleteData::New(this); - } - - return m_autoCompleteData; -} - bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices) { - wxTextAutoCompleteData * const ac = GetOrCreateCompleter(); - if ( !ac ) - return false; + // Try to update the existing data first. + if ( !m_autoCompleteData || !m_autoCompleteData->ChangeStrings(choices) ) + { + // If it failed, try creating a new object for fixed completion. + wxTextAutoCompleteFixed* const ac = wxTextAutoCompleteFixed::New(this); + if ( !ac ) + return false; - ac->ChangeStrings(choices); + ac->ChangeStrings(choices); + + delete m_autoCompleteData; + m_autoCompleteData = ac; + } return true; } @@ -681,23 +688,35 @@ bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter *completer) if ( !completer ) { if ( m_autoCompleteData ) + { + // This is not done in dtor because it's unnecessary when replacing + // one completer with another one, and even dangerous, when the + // control is being destroyed anyhow, so we need to call it + // explicitly here to really disable completion. m_autoCompleteData->DisableCompletion(); + + delete m_autoCompleteData; + m_autoCompleteData = NULL; + } //else: Nothing to do, we hadn't used auto-completion even before. } else // Have a valid completer. { - wxTextAutoCompleteData * const ac = GetOrCreateCompleter(); - if ( !ac ) + // As above, try to update the completer of the existing object first + // and fall back on creating a new one. + if ( !m_autoCompleteData || + !m_autoCompleteData->ChangeCompleter(completer) ) { - // Delete the custom completer for consistency with the case when - // we succeed to avoid memory leaks in user code. - delete completer; - return false; - } + wxTextAutoCompleteDynamic* const + ac = wxTextAutoCompleteDynamic::New(this); + if ( !ac ) + return false; - // This gives ownership of the custom completer to m_autoCompleteData. - if ( !ac->ChangeCustomCompleter(completer) ) - return false; + ac->ChangeCompleter(completer); + + delete m_autoCompleteData; + m_autoCompleteData = ac; + } } return true; From 456fdd8f932dfa4e3bb61ab24678ae229d457eec Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Jan 2018 01:18:21 +0100 Subject: [PATCH 10/13] Always update dynamic completions when the text changes Don't do it only when the text entry is (or becomes again) empty, this breaks dynamic completers such as the one used in the widgets sample, which determines its completions depending on the already entered text (of course, the sample example is not particularly useful, as the completions are always the same, but it's supposed to show that they could dynamically depend on the already entered part of the string). --- src/gtk/textentry.cpp | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index 9f17072503..a65822ffea 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -363,26 +363,12 @@ private: { m_completer = NULL; - m_newCompletionsNeeded = m_entry->IsEmpty(); - win->Bind(wxEVT_TEXT, &wxTextAutoCompleteDynamic::OnEntryChanged, this); } - // for a given prefix, if DoUpdateCompletionModel() succeeds, - // we won't do any further update of the model as long as we - // do not clear the textentry. but then we have to start over again. - - void OnEntryChanged( wxCommandEvent& event ) + void OnEntryChanged(wxCommandEvent& event) { - if ( event.GetString().empty() ) - { - m_newCompletionsNeeded = true; - } - else - { - if ( m_newCompletionsNeeded ) - DoUpdateCompletionModel(); - } + DoUpdateCompletionModel(); event.Skip(); } @@ -406,8 +392,6 @@ private: } UseModel(store); - - m_newCompletionsNeeded = false; } else { @@ -422,12 +406,6 @@ private: // The associated window, we need to store it to unbind our event handler. wxWindow* const m_win; - // Each time we enter a new prefix, GtkEntryCompletion needs to be fed with - // new completions. And this flag lets us try to DoUpdateCompletionModel() - // and if it succeeds, it'll set the flag to false and OnEntryChanged() - // will not try to call it again unless we entered a new prefix. - bool m_newCompletionsNeeded; - wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteDynamic); }; From ec2bb385ef2dbaf311c309abe8fdc41f04ce5bc3 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Jan 2018 01:20:30 +0100 Subject: [PATCH 11/13] Remove platform limitations from AutoComplete() documentation This method is now implemented for all major platforms. --- interface/wx/textentry.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/interface/wx/textentry.h b/interface/wx/textentry.h index 618fde61d9..f580f86f1e 100644 --- a/interface/wx/textentry.h +++ b/interface/wx/textentry.h @@ -52,10 +52,6 @@ public: Call this function to enable auto-completion of the text typed in a single-line text control using the given @a choices. - Notice that currently this function is only implemented in wxGTK2, - wxMSW and wxOSX/Cocoa (for wxTextCtrl only, but not for wxComboBox) - ports and does nothing under the other platforms. - @since 2.9.0 @return @@ -82,9 +78,6 @@ public: 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 and wxOSX/Cocoa (for - wxTextCtrl only, but not for wxComboBox). - @since 2.9.2 @param completer From a14a3de5cebb07a52cc8ab3cd341826af630df47 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Jan 2018 01:35:47 +0100 Subject: [PATCH 12/13] Refactor min autocomplete length handling in widgets sample Call DoUseCustomAutoComplete() function instead of using an artificial event. Also move the menu item in the radio group with the other autocomplete-related commands as it's exclusive with them. --- samples/widgets/widgets.cpp | 46 ++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/samples/widgets/widgets.cpp b/samples/widgets/widgets.cpp index ef66b725a6..8fa5ebb5fb 100644 --- a/samples/widgets/widgets.cpp +++ b/samples/widgets/widgets.cpp @@ -192,6 +192,8 @@ protected: void OnAutoCompleteCustom(wxCommandEvent& event); void OnAutoCompleteKeyLength(wxCommandEvent& event); + void DoUseCustomAutoComplete(size_t minLength = 1); + void OnSetHint(wxCommandEvent& event); void OnUpdateTextUI(wxUpdateUIEvent& event) @@ -221,9 +223,6 @@ private: // the book containing the test pages WidgetsBookCtrl *m_book; - // - int m_prefixMinLength; - // any class wishing to process wxWidgets events must use this macro wxDECLARE_EVENT_TABLE(); }; @@ -393,8 +392,6 @@ WidgetsFrame::WidgetsFrame(const wxString& title) #endif // USE_LOG m_book = NULL; - m_prefixMinLength = 1; - #if wxUSE_MENUS // create the menubar wxMenuBar *mbar = new wxMenuBar; @@ -452,9 +449,9 @@ WidgetsFrame::WidgetsFrame(const wxString& title) wxT("&Directories names auto-completion")); menuTextEntry->AppendRadioItem(TextEntry_AutoCompleteCustom, wxT("&Custom auto-completion")); + menuTextEntry->AppendRadioItem(TextEntry_AutoCompleteKeyLength, + wxT("Custom with &min length")); menuTextEntry->AppendSeparator(); - menuTextEntry->Append(TextEntry_AutoCompleteKeyLength, - wxT("&Minimum key length for auto-completion")); menuTextEntry->Append(TextEntry_SetHint, "Set help &hint"); mbar->Append(menuTextEntry, wxT("&Text")); @@ -1036,6 +1033,11 @@ void WidgetsFrame::OnAutoCompleteDirectories(wxCommandEvent& WXUNUSED(event)) } void WidgetsFrame::OnAutoCompleteCustom(wxCommandEvent& WXUNUSED(event)) +{ + DoUseCustomAutoComplete(); +} + +void WidgetsFrame::DoUseCustomAutoComplete(size_t minLength) { wxTextEntryBase *entry = CurrentPage()->GetTextEntry(); wxCHECK_RET( entry, "menu item should be disabled" ); @@ -1049,7 +1051,10 @@ void WidgetsFrame::OnAutoCompleteCustom(wxCommandEvent& WXUNUSED(event)) class CustomTextCompleter : public wxTextCompleterSimple { public: - CustomTextCompleter( int length ) : m_minLength( length ) {} + explicit CustomTextCompleter(size_t minLength) + : m_minLength(minLength) + { + } virtual void GetCompletions(const wxString& prefix, wxArrayString& res) wxOVERRIDE { @@ -1077,12 +1082,10 @@ void WidgetsFrame::OnAutoCompleteCustom(wxCommandEvent& WXUNUSED(event)) } 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. - // Or if we know in advance that a prefix with one or two characters - // would still results in too many choices too. - if ( prefix.empty() || - prefix.length() < static_cast(m_minLength) ) + // Wait for enough text to be entered before proposing completions: + // this is done to avoid proposing too many of them when the + // control is empty, for example. + if ( prefix.length() < m_minLength ) return; // The only valid strings start with 3 digits so check for their @@ -1134,10 +1137,10 @@ void WidgetsFrame::OnAutoCompleteCustom(wxCommandEvent& WXUNUSED(event)) } } - int m_minLength; + size_t m_minLength; }; - if ( entry->AutoComplete( new CustomTextCompleter( m_prefixMinLength ) ) ) + if ( entry->AutoComplete(new CustomTextCompleter(minLength))) { wxLogMessage("Enabled custom auto completer for \"NNN XX\" items " "(where N is a digit and X is a letter)."); @@ -1152,16 +1155,17 @@ void WidgetsFrame::OnAutoCompleteKeyLength(wxCommandEvent& WXUNUSED(event)) { const wxString message = "The auto-completion is triggered if and only if\n" "the length of the search key (prefix) is at least [LENGTH].\n" - "Hint: negative values disable auto-completion."; + "Hint: 0 disables the length check completely."; const wxString prompt = "Enter the minimum key length:"; const wxString caption = "Minimum key length"; - m_prefixMinLength = wxGetNumberFromUser(message, prompt, caption, 1, -1, 100, this); + int res = wxGetNumberFromUser(message, prompt, caption, 1, 0, 100, this); + if ( res == -1 ) + return; - wxCommandEvent theEvent(wxEVT_MENU, TextEntry_AutoCompleteCustom); - ProcessEventLocally(theEvent); + wxLogMessage("The minimum key length for autocomplete is %d.", res); - wxLogMessage("The minimum key length for autocomplete is : %d.", m_prefixMinLength); + DoUseCustomAutoComplete(static_cast(res)); } void WidgetsFrame::OnSetHint(wxCommandEvent& WXUNUSED(event)) From a6eb4c8e646697fa6ff5c5445d2d45bb335f8983 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Jan 2018 11:55:37 +0100 Subject: [PATCH 13/13] Fix Unix build when using precompiled headers In a twist on the usual theme, the previous commits broke the Unix build when _using_ PCH because wx/textcompleter.h was only included from inside "#ifndef WX_PRECOMP" check, but it needs to be always included as wx/wx.h doesn't include it. --- src/gtk/textentry.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gtk/textentry.cpp b/src/gtk/textentry.cpp index a65822ffea..84144d2289 100644 --- a/src/gtk/textentry.cpp +++ b/src/gtk/textentry.cpp @@ -28,10 +28,11 @@ #include "wx/event.h" #include "wx/textentry.h" #include "wx/textctrl.h" - #include "wx/textcompleter.h" #include "wx/window.h" #endif //WX_PRECOMP +#include "wx/textcompleter.h" + #include #include "wx/gtk/private.h" #include "wx/gtk/private/gtk2-compat.h"