wxQt: Add auto-completion support to wxTextEntry

Closes #25133.
This commit is contained in:
AliKet
2025-01-31 20:16:21 +01:00
committed by Vadim Zeitlin
parent b3629f8859
commit 602f4f3334
2 changed files with 285 additions and 0 deletions

View File

@@ -8,6 +8,8 @@
#ifndef _WX_QT_TEXTENTRY_H_
#define _WX_QT_TEXTENTRY_H_
class wxTextAutoCompleteData; // private class used only by wxTextEntry itself
class WXDLLIMPEXP_CORE wxTextEntry : public wxTextEntryBase
{
public:
@@ -40,10 +42,21 @@ protected:
virtual wxString DoGetValue() const override;
virtual void DoSetValue(const wxString& value, int flags=0) override;
virtual bool DoAutoCompleteStrings(const wxArrayString& choices) override;
virtual bool DoAutoCompleteFileNames(int flags) override;
virtual bool DoAutoCompleteCustom(wxTextCompleter* completer) override;
virtual wxWindow *GetEditableWindow() override;
// Block/unblock the corresponding Qt signal.
virtual void EnableTextChangedEvents(bool enable) override;
// Various auto-completion-related stuff, only used if any of AutoComplete()
// methods are called. Use the function above to access it.
wxTextAutoCompleteData* m_autoCompleteData = nullptr;
// It needs to call our GetEditableWindow() method.
friend class wxTextAutoCompleteData;
};
#endif // _WX_QT_TEXTENTRY_H_

View File

@@ -9,8 +9,16 @@
#include "wx/wxprec.h"
#include "wx/textentry.h"
#include "wx/textcompleter.h"
#include "wx/window.h"
#include "wx/qt/private/converter.h"
#include <QtCore/QStringListModel>
#include <QtWidgets/QCompleter>
#include <QtWidgets/QFileSystemModel>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QWidget>
wxTextEntry::wxTextEntry()
@@ -122,3 +130,267 @@ void wxTextEntry::EnableTextChangedEvents(bool enable)
if ( win )
win->GetHandle()->blockSignals(!enable);
}
// ----------------------------------------------------------------------------
// auto-completion
// ----------------------------------------------------------------------------
namespace
{
// This class is taken from Qt documentation "as is" to see "C:\Program Files"
// instead of just "Program Files" as QFileSystemModel does by default.
class FileSystemModel : public QFileSystemModel
{
public:
explicit FileSystemModel(QObject* parent = nullptr) : QFileSystemModel(parent)
{
}
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override
{
if ( role == Qt::DisplayRole && index.column() == 0 )
{
QString path = QDir::toNativeSeparators(filePath(index));
if ( path.endsWith(QDir::separator()) )
path.chop(1);
return path;
}
return QFileSystemModel::data(index, role);
}
};
}
// This class gathers the all auto-complete-related stuff we use. It is
// allocated on demand by wxTextEntry when AutoComplete() is called.
class wxTextAutoCompleteData
{
public:
enum class CompleterType
{
StringCompleter,
FileSystemCompleter
};
// The constructor associates us with the given text entry.
explicit wxTextAutoCompleteData(wxTextEntry* entry,
CompleterType type = CompleterType::StringCompleter)
: m_entry(entry),
m_win(entry->GetEditableWindow())
{
if ( m_win )
{
auto qtWidget = m_win->GetHandle();
m_qtCompleter = new QCompleter(qtWidget->parentWidget());
if ( type == CompleterType::FileSystemCompleter )
{
auto fsModel = new FileSystemModel(m_qtCompleter);
fsModel->setRootPath(QDir::currentPath());
m_qtCompleter->setModel(fsModel);
}
else
{
m_qtCompleter->setModel( new QStringListModel(m_qtCompleter) );
m_qtCompleter->setCaseSensitivity(Qt::CaseInsensitive);
}
if ( auto lineEdit = qobject_cast<QLineEdit*>(qtWidget) )
{
lineEdit->setCompleter(m_qtCompleter);
}
else if ( auto comboBox = qobject_cast<QComboBox*>(qtWidget) )
{
comboBox->setCompleter(m_qtCompleter);
}
else
{
wxDELETE(m_qtCompleter);
}
}
}
~wxTextAutoCompleteData()
{
ChangeCustomCompleter(nullptr); // disable custom completer if any.
}
bool IsOk() const { return m_qtCompleter != nullptr; }
void ChangeStrings(const wxArrayString& strings)
{
ChangeCustomCompleter(nullptr); // disable custom completer if any.
auto listModel = qobject_cast<QStringListModel*>(m_qtCompleter->model());
wxASSERT_MSG(listModel, "A QStringListModel object is expected here");
QStringList list;
for ( auto str : strings )
{
list << wxQtConvertString( str );
}
listModel->setStringList(list);
}
// Takes ownership of the pointer if it is non-null.
bool ChangeCustomCompleter(wxTextCompleter* completer)
{
if ( m_completer )
m_win->Unbind(wxEVT_TEXT, &wxTextAutoCompleteData::OnEntryChanged, this);
delete m_completer;
m_completer = completer;
if ( m_completer )
m_win->Bind(wxEVT_TEXT, &wxTextAutoCompleteData::OnEntryChanged, this);
return true;
}
void DisableCompletion()
{
if ( m_qtCompleter )
{
auto qtWidget = m_qtCompleter->widget();
if ( auto lineEdit = qobject_cast<QLineEdit*>(qtWidget) )
{
lineEdit->setCompleter(nullptr);
}
else if ( auto comboBox = qobject_cast<QComboBox*>(qtWidget) )
{
comboBox->setCompleter(nullptr);
}
m_qtCompleter = nullptr;
}
}
private:
// Update the strings returned by QCompleter to correspond to
// the currently valid choices according to the custom completer.
void UpdateStringsFromCustomCompleter()
{
if ( !m_completer )
return;
auto listModel = qobject_cast<QStringListModel*>(m_qtCompleter->model());
wxASSERT_MSG(listModel, "A QStringListModel object is expected here");
const wxString prefix = wxQtConvertString(m_qtCompleter->completionPrefix());
if ( m_prefix == prefix )
return;
QStringList list;
if ( m_completer->Start(prefix) )
{
m_prefix = prefix;
for (;;)
{
const wxString s = m_completer->GetNext();
if ( s.empty() )
break;
list << wxQtConvertString(s);
}
}
listModel->setStringList(list);
}
void OnEntryChanged(wxCommandEvent& event)
{
UpdateStringsFromCustomCompleter();
event.Skip();
}
// The text entry we're associated with.
wxTextEntry* const m_entry;
// The window of this text entry.
wxWindow* const m_win;
// Custom completer or nullptr if none.
wxTextCompleter* m_completer = nullptr;
// This pointer is owned by the underlying QWidget (QLineEdit, QComboBox, ...)
QCompleter* m_qtCompleter = nullptr;
wxString m_prefix;
wxDECLARE_NO_COPY_CLASS(wxTextAutoCompleteData);
};
bool wxTextEntry::DoAutoCompleteFileNames(int WXUNUSED(flags))
{
if ( m_autoCompleteData )
{
m_autoCompleteData->DisableCompletion();
wxDELETE(m_autoCompleteData);
}
auto fsCompleter = wxTextAutoCompleteData::CompleterType::FileSystemCompleter;
std::unique_ptr<wxTextAutoCompleteData>
autoCompleteData( new wxTextAutoCompleteData(this, fsCompleter) );
if ( autoCompleteData->IsOk() )
{
m_autoCompleteData = autoCompleteData.release();
}
return m_autoCompleteData != nullptr;
}
bool wxTextEntry::DoAutoCompleteStrings(const wxArrayString& choices)
{
if ( m_autoCompleteData )
{
m_autoCompleteData->DisableCompletion();
wxDELETE(m_autoCompleteData);
}
std::unique_ptr<wxTextAutoCompleteData>
autoCompleteData( new wxTextAutoCompleteData(this) );
if ( autoCompleteData->IsOk() )
{
m_autoCompleteData = autoCompleteData.release();
m_autoCompleteData->ChangeStrings(choices);
}
return m_autoCompleteData != nullptr;
}
bool wxTextEntry::DoAutoCompleteCustom(wxTextCompleter* completer)
{
if ( m_autoCompleteData )
{
m_autoCompleteData->DisableCompletion();
wxDELETE(m_autoCompleteData);
}
std::unique_ptr<wxTextAutoCompleteData>
autoCompleteData( new wxTextAutoCompleteData(this) );
if ( autoCompleteData->IsOk() )
{
m_autoCompleteData = autoCompleteData.release();
m_autoCompleteData->ChangeCustomCompleter(completer);
}
return m_autoCompleteData != nullptr;
}