Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<QKeyEvent*>(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;
}
}
Expand All @@ -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();
Expand All @@ -371,24 +397,37 @@ 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
case Qt::Key_Up:
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;

}
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

class QListWidget;
class QListWidgetItem;
class QKeyEvent;

namespace edbee {

Expand Down Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion edbee-test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down Expand Up @@ -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
)

Expand All @@ -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)

52 changes: 52 additions & 0 deletions edbee-test/edbee/views/texteditorautocompletecomponenttest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// edbee - Copyright (c) 2012-2025 by Rick Blommers and contributors
// SPDX-License-Identifier: MIT

#include "texteditorautocompletecomponenttest.h"

#include <QApplication>
#include <QKeyEvent>
#include <QListWidget>

#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
20 changes: 20 additions & 0 deletions edbee-test/edbee/views/texteditorautocompletecomponenttest.h
Original file line number Diff line number Diff line change
@@ -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