Gui: Differentiate completion activation modes in Expression Editor

Currently if user tries to press TAB during Expression Editor, it
inserts both the entry and its first subentry. Also, if user browses the
dropdown with arrows keys, it inserts it's values.

Root cause of that is the regression made in latest changes to chaining
completion logic which is triggered for all completion modes including
TAB, which already has its own refresh mechanism. Also,
ExpressionTextEdit connected both activated (Enter/click) and highlight
signals to the same slot, resulting in arrow key navigation inserting
completions.

So, this adds separate slots for ExpressionTextEdit to differentiate
completion modes. And also updates tab handling to pass
ActivationMode::Highlighted to prevent double chaining.
This commit is contained in:
tetektoza
2025-11-12 00:14:02 +01:00
parent f0ce08268d
commit efbbfdbfb5
2 changed files with 36 additions and 16 deletions

View File

@@ -1013,7 +1013,7 @@ void ExpressionLineEdit::slotTextChanged(const QString& text)
}
}
void ExpressionLineEdit::slotCompleteText(const QString& completionPrefix, bool isActivated)
void ExpressionLineEdit::slotCompleteText(const QString& completionPrefix, ActivationMode mode)
{
int start, end;
completer->getPrefixRange(start, end);
@@ -1030,7 +1030,7 @@ void ExpressionLineEdit::slotCompleteText(const QString& completionPrefix, bool
// chain completions if we select an entry from the completer drop down
// and that entry ends with '.' or '#'
if (isActivated) {
if (mode == ActivationMode::Activated) {
std::string textToComplete = completionPrefix.toUtf8().constData();
if (textToComplete.size()
&& (*textToComplete.crbegin() == '.' || *textToComplete.crbegin() == '#')) {
@@ -1042,12 +1042,12 @@ void ExpressionLineEdit::slotCompleteText(const QString& completionPrefix, bool
void ExpressionLineEdit::slotCompleteTextHighlighted(const QString& completionPrefix)
{
slotCompleteText(completionPrefix, false);
slotCompleteText(completionPrefix, ActivationMode::Highlighted);
}
void ExpressionLineEdit::slotCompleteTextSelected(const QString& completionPrefix)
{
slotCompleteText(completionPrefix, true);
slotCompleteText(completionPrefix, ActivationMode::Activated);
}
@@ -1116,13 +1116,13 @@ void ExpressionTextEdit::setDocumentObject(const App::DocumentObject* currentDoc
completer,
qOverload<const QString&>(&QCompleter::activated),
this,
&ExpressionTextEdit::slotCompleteText
&ExpressionTextEdit::slotCompleteTextSelected
);
connect(
completer,
qOverload<const QString&>(&QCompleter::highlighted),
this,
&ExpressionTextEdit::slotCompleteText
&ExpressionTextEdit::slotCompleteTextHighlighted
);
connect(this, &ExpressionTextEdit::textChanged2, completer, &ExpressionCompleter::slotUpdate);
connect(
@@ -1155,7 +1155,7 @@ void ExpressionTextEdit::slotTextChanged()
}
}
void ExpressionTextEdit::slotCompleteText(const QString& completionPrefix)
void ExpressionTextEdit::slotCompleteText(const QString& completionPrefix, ActivationMode mode)
{
QTextCursor cursor = textCursor();
int start, end;
@@ -1165,17 +1165,31 @@ void ExpressionTextEdit::slotCompleteText(const QString& completionPrefix)
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, end - pos);
}
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, end - start);
Base::FlagToggler<bool> flag(block, false);
cursor.insertText(completionPrefix);
completer->updatePrefixEnd(cursor.positionInBlock());
std::string textToComplete = completionPrefix.toUtf8().constData();
if (textToComplete.size()
&& (*textToComplete.crbegin() == '.' || *textToComplete.crbegin() == '#')) {
completer->slotUpdate(cursor.block().text(), cursor.positionInBlock());
// chain completions only when activated (Enter/Click), not when highlighted (arrow keys)
if (mode == ActivationMode::Activated) {
std::string textToComplete = completionPrefix.toUtf8().constData();
if (!textToComplete.empty()
&& (*textToComplete.crbegin() == '.' || *textToComplete.crbegin() == '#')) {
completer->slotUpdate(cursor.block().text(), cursor.positionInBlock());
}
}
}
void ExpressionTextEdit::slotCompleteTextHighlighted(const QString& completionPrefix)
{
slotCompleteText(completionPrefix, ActivationMode::Highlighted);
}
void ExpressionTextEdit::slotCompleteTextSelected(const QString& completionPrefix)
{
slotCompleteText(completionPrefix, ActivationMode::Activated);
}
void ExpressionTextEdit::keyPressEvent(QKeyEvent* e)
{
Base::FlagToggler<bool> flag(block, true);
@@ -1208,9 +1222,8 @@ void ExpressionTextEdit::keyPressEvent(QKeyEvent* e)
if (!completer->popup()->currentIndex().isValid()) {
completer->popup()->setCurrentIndex(completer->popup()->model()->index(0, 0));
}
// insert completion
completer->setCurrentRow(completer->popup()->currentIndex().row());
slotCompleteText(completer->currentCompletion());
slotCompleteText(completer->currentCompletion(), ActivationMode::Highlighted);
// refresh completion list
completer->setCompletionPrefix(completer->currentCompletion());

View File

@@ -44,6 +44,12 @@ class ObjectIdentifier;
namespace Gui
{
enum class ActivationMode
{
Highlighted, // browsing with arrow keys
Activated // confirmed selection (Enter/Click)
};
class GuiExport ExpressionValidator: public QValidator
{
Q_OBJECT
@@ -123,8 +129,7 @@ Q_SIGNALS:
void textChanged2(QString text, int pos);
public Q_SLOTS:
void slotTextChanged(const QString& text);
// activated == pressed enter on the completion item
void slotCompleteText(const QString& completionPrefix, bool isActivated);
void slotCompleteText(const QString& completionPrefix, ActivationMode mode);
void slotCompleteTextHighlighted(const QString& completionPrefix);
void slotCompleteTextSelected(const QString& completionPrefix);
@@ -159,7 +164,9 @@ Q_SIGNALS:
void textChanged2(QString text, int pos);
public Q_SLOTS:
void slotTextChanged();
void slotCompleteText(const QString& completionPrefix);
void slotCompleteText(const QString& completionPrefix, ActivationMode mode);
void slotCompleteTextHighlighted(const QString& completionPrefix);
void slotCompleteTextSelected(const QString& completionPrefix);
void adjustCompleterToCursor();
private: