Merge branch 'gtk-dyn-autocomplete'
Implement dynamic wxTextEntry autocompletion for wxGTK. See https://github.com/wxWidgets/wxWidgets/pull/690 Closes #18061.
This commit is contained in:
@@ -182,6 +182,7 @@ wxGTK:
|
||||
- Make wxUIActionSimulator work with GTK+ 3 (Scott Talbert).
|
||||
- Make wxBORDER_NONE work for wxTextCtrl with GTK+ 3 (Adrien Tétar).
|
||||
- Apply wxTextCtrl::SetDefaultStyle() to user-entered text (Andreas Falkenhahn).
|
||||
- Implement dynamic auto-completion in wxTextEntry (AliKet).
|
||||
- Fix wxTextCtrl::GetStyle() with GTK+ 3.
|
||||
- Fix wxButton::SetBitmapPosition() with GTK+ 3 (Jake Nelson).
|
||||
- Support background colour in wxDataViewCtrl attributes.
|
||||
|
@@ -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,13 @@ private:
|
||||
// implement this to return the associated GtkEntry
|
||||
virtual GtkEntry *GetEntry() const = 0;
|
||||
|
||||
// Various auto-completion-related stuff, only used if any of AutoComplete()
|
||||
// methods are called.
|
||||
wxTextAutoCompleteData *m_autoCompleteData;
|
||||
|
||||
// It needs to call our GetEntry() method.
|
||||
friend class wxTextAutoCompleteData;
|
||||
|
||||
bool m_isUpperCase;
|
||||
};
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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,9 @@ protected:
|
||||
void OnAutoCompleteFilenames(wxCommandEvent& event);
|
||||
void OnAutoCompleteDirectories(wxCommandEvent& event);
|
||||
void OnAutoCompleteCustom(wxCommandEvent& event);
|
||||
void OnAutoCompleteKeyLength(wxCommandEvent& event);
|
||||
|
||||
void DoUseCustomAutoComplete(size_t minLength = 1);
|
||||
|
||||
void OnSetHint(wxCommandEvent& event);
|
||||
|
||||
@@ -317,6 +322,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)
|
||||
|
||||
@@ -443,6 +449,8 @@ 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_SetHint, "Set help &hint");
|
||||
|
||||
@@ -1025,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" );
|
||||
@@ -1038,6 +1051,11 @@ void WidgetsFrame::OnAutoCompleteCustom(wxCommandEvent& WXUNUSED(event))
|
||||
class CustomTextCompleter : public wxTextCompleterSimple
|
||||
{
|
||||
public:
|
||||
explicit CustomTextCompleter(size_t minLength)
|
||||
: m_minLength(minLength)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void GetCompletions(const wxString& prefix, wxArrayString& res) wxOVERRIDE
|
||||
{
|
||||
// This is used for illustrative purposes only and shows how many
|
||||
@@ -1064,9 +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.
|
||||
if ( prefix.empty() )
|
||||
// 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
|
||||
@@ -1117,9 +1136,11 @@ void WidgetsFrame::OnAutoCompleteCustom(wxCommandEvent& WXUNUSED(event))
|
||||
res.push_back(prefix + c);
|
||||
}
|
||||
}
|
||||
|
||||
size_t m_minLength;
|
||||
};
|
||||
|
||||
if ( entry->AutoComplete(new CustomTextCompleter) )
|
||||
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).");
|
||||
@@ -1130,6 +1151,23 @@ 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: 0 disables the length check completely.";
|
||||
const wxString prompt = "Enter the minimum key length:";
|
||||
const wxString caption = "Minimum key length";
|
||||
|
||||
int res = wxGetNumberFromUser(message, prompt, caption, 1, 0, 100, this);
|
||||
if ( res == -1 )
|
||||
return;
|
||||
|
||||
wxLogMessage("The minimum key length for autocomplete is %d.", res);
|
||||
|
||||
DoUseCustomAutoComplete(static_cast<size_t>(res));
|
||||
}
|
||||
|
||||
void WidgetsFrame::OnSetHint(wxCommandEvent& WXUNUSED(event))
|
||||
{
|
||||
wxTextEntryBase *entry = CurrentPage()->GetTextEntry();
|
||||
|
@@ -25,14 +25,18 @@
|
||||
#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/window.h"
|
||||
#endif //WX_PRECOMP
|
||||
|
||||
#include "wx/textcompleter.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include "wx/gtk/private.h"
|
||||
#include "wx/gtk/private/gtk2-compat.h"
|
||||
#include "wx/gtk/private/object.h"
|
||||
#include "wx/gtk/private/string.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -193,10 +197,239 @@ wx_gtk_paste_clipboard_callback( GtkWidget *widget, wxWindow *win )
|
||||
|
||||
} // extern "C"
|
||||
|
||||
// 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 wxTextAutoCompleteFixed* New(wxTextEntry *entry)
|
||||
{
|
||||
if ( !CanComplete(entry) )
|
||||
return NULL;
|
||||
|
||||
return new wxTextAutoCompleteFixed(entry);
|
||||
}
|
||||
|
||||
virtual bool ChangeStrings(const wxArrayString& strings) wxOVERRIDE
|
||||
{
|
||||
wxGtkObject<GtkListStore> store(gtk_list_store_new (1, G_TYPE_STRING));
|
||||
|
||||
for ( wxArrayString::const_iterator i = strings.begin();
|
||||
i != strings.end();
|
||||
++i )
|
||||
{
|
||||
AppendToStore(store, *i);
|
||||
}
|
||||
|
||||
UseModel(store);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool ChangeCompleter(wxTextCompleter*) wxOVERRIDE
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
// Ctor is private, use New() to create objects of this type.
|
||||
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;
|
||||
|
||||
win->Bind(wxEVT_TEXT, &wxTextAutoCompleteDynamic::OnEntryChanged, this);
|
||||
}
|
||||
|
||||
void OnEntryChanged(wxCommandEvent& event)
|
||||
{
|
||||
DoUpdateCompletionModel();
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
// Recreate the model to contain all completions for the current prefix.
|
||||
void DoUpdateCompletionModel()
|
||||
{
|
||||
const wxString& prefix = m_entry->GetValue();
|
||||
|
||||
if ( m_completer->Start(prefix) )
|
||||
{
|
||||
wxGtkObject<GtkListStore> store(gtk_list_store_new (1, G_TYPE_STRING));
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const wxString s = m_completer->GetNext();
|
||||
if ( s.empty() )
|
||||
break;
|
||||
|
||||
AppendToStore(store, s);
|
||||
}
|
||||
|
||||
UseModel(store);
|
||||
}
|
||||
else
|
||||
{
|
||||
UseModel(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Custom completer.
|
||||
wxTextCompleter *m_completer;
|
||||
|
||||
// The associated window, we need to store it to unbind our event handler.
|
||||
wxWindow* const m_win;
|
||||
|
||||
wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteDynamic);
|
||||
};
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// wxTextEntry implementation
|
||||
// ============================================================================
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// initialization and destruction
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
wxTextEntry::wxTextEntry()
|
||||
{
|
||||
m_autoCompleteData = NULL;
|
||||
m_isUpperCase = false;
|
||||
}
|
||||
|
||||
wxTextEntry::~wxTextEntry()
|
||||
{
|
||||
delete m_autoCompleteData;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// text operations
|
||||
// ----------------------------------------------------------------------------
|
||||
@@ -411,30 +644,62 @@ void wxTextEntry::GetSelection(long *from, long *to) const
|
||||
|
||||
bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices)
|
||||
{
|
||||
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 )
|
||||
// Try to update the existing data first.
|
||||
if ( !m_autoCompleteData || !m_autoCompleteData->ChangeStrings(choices) )
|
||||
{
|
||||
gtk_list_store_append(store, &iter);
|
||||
gtk_list_store_set(store, &iter,
|
||||
0, (const gchar *)i->utf8_str(),
|
||||
-1);
|
||||
// If it failed, try creating a new object for fixed completion.
|
||||
wxTextAutoCompleteFixed* const ac = wxTextAutoCompleteFixed::New(this);
|
||||
if ( !ac )
|
||||
return false;
|
||||
|
||||
ac->ChangeStrings(choices);
|
||||
|
||||
delete m_autoCompleteData;
|
||||
m_autoCompleteData = 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 true;
|
||||
}
|
||||
|
||||
bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter *completer)
|
||||
{
|
||||
// First deal with the case when we just want to disable auto-completion.
|
||||
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.
|
||||
{
|
||||
// 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) )
|
||||
{
|
||||
wxTextAutoCompleteDynamic* const
|
||||
ac = wxTextAutoCompleteDynamic::New(this);
|
||||
if ( !ac )
|
||||
return false;
|
||||
|
||||
ac->ChangeCompleter(completer);
|
||||
|
||||
delete m_autoCompleteData;
|
||||
m_autoCompleteData = ac;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// ----------------------------------------------------------------------------
|
||||
// editable status
|
||||
// ----------------------------------------------------------------------------
|
||||
|
Reference in New Issue
Block a user