diff --git a/edbee-lib/edbee/views/components/texteditorautocompletecomponent.cpp b/edbee-lib/edbee/views/components/texteditorautocompletecomponent.cpp index 3c23332..1134ded 100644 --- a/edbee-lib/edbee/views/components/texteditorautocompletecomponent.cpp +++ b/edbee-lib/edbee/views/components/texteditorautocompletecomponent.cpp @@ -50,10 +50,15 @@ TextEditorAutoCompleteComponent::TextEditorAutoCompleteComponent(TextEditorContr this->setAttribute(Qt::WA_ShowWithoutActivating); menuRef_ = new QMenu(this); + menuRef_->setFocusPolicy(Qt::NoFocus); + menuRef_->setAttribute(Qt::WA_ShowWithoutActivating); menuRef_->setAccessibleName("Autocomplete"); listWidgetRef_ = new QListWidget(menuRef_); + listWidgetRef_->setFocusPolicy(Qt::NoFocus); + listWidgetRef_->setAttribute(Qt::WA_ShowWithoutActivating); + editorComponentRef_->installEventFilter(this); listWidgetRef_->installEventFilter(this); menuRef_->installEventFilter(this); @@ -326,24 +331,42 @@ void TextEditorAutoCompleteComponent::hideEvent(QHideEvent* event) event->isAccepted(); } +bool TextEditorAutoCompleteComponent::sendKeyEventTo(QWidget* target, QKeyEvent* sourceEvent) +{ + QKeyEvent event(sourceEvent->type(), sourceEvent->key(), sourceEvent->modifiers(), sourceEvent->text(), sourceEvent->isAutoRepeat(), sourceEvent->count()); + + eventBeingFiltered_ = true; + const bool wasHandled = QApplication::sendEvent(target, &event); + eventBeingFiltered_ = false; + + return wasHandled; +} /// we need to intercept keypresses if the widget is visible bool TextEditorAutoCompleteComponent::eventFilter(QObject *obj, QEvent *event) { + if (eventBeingFiltered_) { + return QObject::eventFilter(obj, event); + } + if (event->type() == QEvent::Close && obj == menuRef_) { hide(); hideInfoTip(); return QObject::eventFilter(obj, event); } - if(obj == listWidgetRef_ && event->type() == QEvent::KeyPress) { + if ((obj == editorComponentRef_ || obj == listWidgetRef_ || obj == menuRef_) && event->type() == QEvent::KeyPress && menuRef_->isVisible()) { QKeyEvent* key = static_cast(event); + const bool editorHasEvent = obj == editorComponentRef_; // text keys are allowed if (!key->text().isEmpty()) { QChar nextChar = key->text().at(0); if (nextChar.isLetterOrNumber()) { - QApplication::sendEvent(editorComponentRef_, event); + if (editorHasEvent) { + return false; + } + sendKeyEventTo(editorComponentRef_, key); return true; } } @@ -360,7 +383,10 @@ bool TextEditorAutoCompleteComponent::eventFilter(QObject *obj, QEvent *event) case Qt::Key_Tab: if (listWidgetRef_->currentItem() && currentWord_ == listWidgetRef_->currentItem()->text()) { // sends normal enter/return/tab if you've typed a full word menuRef_->close(); - QApplication::sendEvent(editorComponentRef_, event); + if (editorHasEvent) { + return false; + } + sendKeyEventTo(editorComponentRef_, key); return true; } else if (listWidgetRef_->currentItem()) { insertCurrentSelectedListItem(); @@ -371,11 +397,17 @@ bool TextEditorAutoCompleteComponent::eventFilter(QObject *obj, QEvent *event) break; case Qt::Key_Backspace: - QApplication::sendEvent(editorComponentRef_, event); + if (editorHasEvent) { + return false; + } + sendKeyEventTo(editorComponentRef_, key); return true; case Qt::Key_Shift: //ignore shift, don't hide - QApplication::sendEvent(editorComponentRef_, event); + if (editorHasEvent) { + return false; + } + sendKeyEventTo(editorComponentRef_, key); return true; // forward special keys to list @@ -383,12 +415,19 @@ bool TextEditorAutoCompleteComponent::eventFilter(QObject *obj, QEvent *event) case Qt::Key_Down: case Qt::Key_PageDown: case Qt::Key_PageUp: + if (editorHasEvent || obj == menuRef_) { + sendKeyEventTo(listWidgetRef_, key); + return true; + } return false; } // default operation is to hide and continue the event menuRef_->close(); - QApplication::sendEvent(editorComponentRef_, event); + if (editorHasEvent) { + return false; + } + sendKeyEventTo(editorComponentRef_, key); return true; } @@ -428,7 +467,6 @@ void TextEditorAutoCompleteComponent::updateList() // fills the autocomplete list with the curent word if (fillAutoCompleteList(doc, range, currentWord_)) { menuRef_->popup(menuRef_->pos()); - listWidgetRef_->setFocus(); // position the widget showInfoTip(); diff --git a/edbee-lib/edbee/views/components/texteditorautocompletecomponent.h b/edbee-lib/edbee/views/components/texteditorautocompletecomponent.h index c735f2e..4d1f75e 100644 --- a/edbee-lib/edbee/views/components/texteditorautocompletecomponent.h +++ b/edbee-lib/edbee/views/components/texteditorautocompletecomponent.h @@ -18,6 +18,7 @@ class QListWidget; class QListWidgetItem; +class QKeyEvent; namespace edbee { @@ -76,6 +77,7 @@ class EDBEE_EXPORT TextEditorAutoCompleteComponent : public QWidget //void moveEvent(QMoveEvent *event); void insertCurrentSelectedListItem(); + bool sendKeyEventTo(QWidget* target, QKeyEvent* sourceEvent); signals: public slots: diff --git a/edbee-test/CMakeLists.txt b/edbee-test/CMakeLists.txt index 536b63c..b13bc6f 100644 --- a/edbee-test/CMakeLists.txt +++ b/edbee-test/CMakeLists.txt @@ -37,6 +37,7 @@ SET(SOURCES edbee/util/rangesetlineiteratortest.cpp edbee/models/dynamicvariablestest.cpp edbee/util/rangelineiteratortest.cpp + edbee/views/texteditorautocompletecomponenttest.cpp edbee/views/textthememanagertest.cpp ) @@ -67,6 +68,7 @@ SET(HEADERS edbee/util/rangesetlineiteratortest.h edbee/models/dynamicvariablestest.h edbee/util/rangelineiteratortest.h + edbee/views/texteditorautocompletecomponenttest.h edbee/views/textthememanagertest.h ) @@ -85,4 +87,3 @@ ADD_EXECUTABLE(edbee-test TARGET_LINK_LIBRARIES(edbee-test edbee-lib ${QT_LIBS}) set_target_properties(edbee-test PROPERTIES AUTOMOC ON CXX_STANDARD 11) - diff --git a/edbee-test/edbee/views/texteditorautocompletecomponenttest.cpp b/edbee-test/edbee/views/texteditorautocompletecomponenttest.cpp new file mode 100644 index 0000000..754016f --- /dev/null +++ b/edbee-test/edbee/views/texteditorautocompletecomponenttest.cpp @@ -0,0 +1,52 @@ +// edbee - Copyright (c) 2012-2025 by Rick Blommers and contributors +// SPDX-License-Identifier: MIT + +#include "texteditorautocompletecomponenttest.h" + +#include +#include +#include + +#include "edbee/models/textautocompleteprovider.h" +#include "edbee/models/textdocument.h" +#include "edbee/texteditorcontroller.h" +#include "edbee/texteditorwidget.h" +#include "edbee/views/components/texteditorautocompletecomponent.h" +#include "edbee/views/components/texteditorcomponent.h" + +namespace edbee { + +void TextEditorAutoCompleteComponentTest::typingKeepsEditorFocusedWhenAutocompleteIsVisible() +{ + TextEditorWidget widget; + widget.resize(640, 320); + widget.show(); + QApplication::processEvents(); + + StringTextAutoCompleteProvider* provider = new StringTextAutoCompleteProvider(); + provider->add("compare"); + widget.textDocument()->autoCompleteProviderList()->giveProvider(provider); + + TextEditorComponent* editor = widget.textEditorComponent(); + QListWidget* list = widget.autoCompleteComponent()->listWidget(); + + editor->setFocus(); + widget.controller()->replaceSelection("com"); + widget.autoCompleteComponent()->updateList(); + QApplication::processEvents(); + + testEqual(widget.textDocument()->text(), "com"); + testTrue(editor->hasFocus()); + testFalse(list->hasFocus()); + testEqual(list->count(), 1); + + QKeyEvent textKey(QEvent::KeyPress, Qt::Key_P, Qt::NoModifier, "p"); + QApplication::sendEvent(editor, &textKey); + QApplication::processEvents(); + + testEqual(widget.textDocument()->text(), "comp"); + testTrue(editor->hasFocus()); + testFalse(list->hasFocus()); +} + +} // edbee diff --git a/edbee-test/edbee/views/texteditorautocompletecomponenttest.h b/edbee-test/edbee/views/texteditorautocompletecomponenttest.h new file mode 100644 index 0000000..78b468d --- /dev/null +++ b/edbee-test/edbee/views/texteditorautocompletecomponenttest.h @@ -0,0 +1,20 @@ +// edbee - Copyright (c) 2012-2025 by Rick Blommers and contributors +// SPDX-License-Identifier: MIT + +#pragma once + +#include "edbee/util/test.h" + +namespace edbee { + +class TextEditorAutoCompleteComponentTest : public edbee::test::TestCase +{ + Q_OBJECT + +private slots: + void typingKeepsEditorFocusedWhenAutocompleteIsVisible(); +}; + +DECLARE_TEST(edbee::TextEditorAutoCompleteComponentTest); + +} // edbee