From b3008feaa1863bd77544c56acecda4f95f2e76b6 Mon Sep 17 00:00:00 2001 From: xtemp09 Date: Sat, 22 Mar 2025 23:25:17 +0700 Subject: [PATCH] [Spreadsheet] Fix input in expression editor (#19934) * [Spreadsheet] Fix input in expression editor Closes #19804 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/Gui/ExpressionCompleter.cpp | 5 + src/Gui/ExpressionCompleter.h | 2 + src/Mod/Spreadsheet/Gui/LineEdit.cpp | 143 +++++++++++++++++++++------ src/Mod/Spreadsheet/Gui/LineEdit.h | 30 ++++-- 4 files changed, 141 insertions(+), 39 deletions(-) diff --git a/src/Gui/ExpressionCompleter.cpp b/src/Gui/ExpressionCompleter.cpp index 290c7b9fc8..44c0e65983 100644 --- a/src/Gui/ExpressionCompleter.cpp +++ b/src/Gui/ExpressionCompleter.cpp @@ -956,6 +956,11 @@ void ExpressionLineEdit::setExactMatch(bool enabled) } } +ExpressionCompleter *ExpressionLineEdit::getCompleter(void) +{ + return this->completer; +} + bool ExpressionLineEdit::completerActive() const { return completer && completer->popup() && completer->popup()->isVisible(); diff --git a/src/Gui/ExpressionCompleter.h b/src/Gui/ExpressionCompleter.h index c6773c2a28..ce62ef9846 100644 --- a/src/Gui/ExpressionCompleter.h +++ b/src/Gui/ExpressionCompleter.h @@ -80,6 +80,7 @@ private: class GuiExport ExpressionLineEdit : public QLineEdit { Q_OBJECT + Q_PROPERTY(ExpressionCompleter* completer READ getCompleter) public: ExpressionLineEdit(QWidget *parent = nullptr, bool noProperty=false, char checkPrefix=0, bool checkInList=true); @@ -89,6 +90,7 @@ public: void hideCompleter(); void setNoProperty(bool enabled=true); void setExactMatch(bool enabled=true); + ExpressionCompleter *getCompleter(void); Q_SIGNALS: void textChanged2(QString text, int pos); public Q_SLOTS: diff --git a/src/Mod/Spreadsheet/Gui/LineEdit.cpp b/src/Mod/Spreadsheet/Gui/LineEdit.cpp index ea17ad024d..0d9d18d35e 100644 --- a/src/Mod/Spreadsheet/Gui/LineEdit.cpp +++ b/src/Mod/Spreadsheet/Gui/LineEdit.cpp @@ -26,9 +26,12 @@ #include #include #include +#include #endif #include "LineEdit.h" +#include +#include "ZoomableView.h" using namespace SpreadsheetGui; @@ -40,46 +43,122 @@ LineEdit::LineEdit(QWidget* parent) setFocusPolicy(Qt::FocusPolicy::ClickFocus); } -bool LineEdit::eventFilter(QObject* object, QEvent* event) +void LineEdit::setDocumentObject(const App::DocumentObject* currentDocObj, bool checkInList) { - Q_UNUSED(object); - if (event && event->type() == QEvent::KeyPress) { - QKeyEvent* keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Tab) { - // Special tab handling -- must be done via a QApplication event filter, otherwise the - // widget system will always grab the tab events - if (completerActive()) { - hideCompleter(); - event->accept(); - return true; // To make sure this tab press doesn't do anything else - } - else { - lastKeyPressed = keyEvent->key(); - lastModifiers = keyEvent->modifiers(); - } - } + ExpressionLineEdit::setDocumentObject(currentDocObj, checkInList); + + /* The code below is supposed to fix the input of an expression and to make the popup + * functional. The input seems to be broken because of installed event filters. My solution is + * to readd the widget into the scene. Only a parentless widget can be added to the scene. + * Making a widget parentless makes it lose its windowFlags, even if it is added to the scene. + * So, the algorithm is to obtain globalPos, then to make the widget parentless, + * to add it to the scene, setting the globalPos after. */ + + QPointer active_view = Gui::MainWindow::getInstance()->activeWindow(); + if (!active_view) { + Base::Console().DeveloperWarning("LineEdit::setDocumentObject", + "The active view is not Spreadsheet"); + return; } - return false; // We don't usually actually "handle" the tab event, we just keep track of it + QPointer zv = active_view->findChild(); + if (!zv) { + Base::Console().DeveloperWarning("LineEdit::setDocumentObject", "ZoomableView not found"); + return; + } + + auto getPos = [this]() { + return this->mapToGlobal(QPoint {0, 0}); + }; + const QPoint old_pos = getPos(); + + auto xpopup = new XListView(this); + getCompleter()->setPopup(xpopup); + setParent(nullptr); + QGraphicsProxyWidget* proxy_lineedit = zv->scene()->addWidget(this); + + const QPoint new_pos = getPos(); + const QPoint shift = old_pos - new_pos; + const qreal scale_factor = static_cast(zv->zoomLevel()) / 100.0; + const qreal shift_x = static_cast(shift.x()) / scale_factor, + shift_y = static_cast(shift.y()) / scale_factor; + + QTransform trans = proxy_lineedit->transform(); + proxy_lineedit->setTransform(trans.translate(shift_x, shift_y)); + + + auto getPopupPos = [proxy_lineedit, zv]() { + const QPointF scene_pos = + proxy_lineedit->mapToScene(proxy_lineedit->boundingRect().bottomLeft()); + const QPoint view_pos = zv->mapFromScene(scene_pos); + const QPoint global_pos = zv->viewport()->mapToGlobal(view_pos); + + return global_pos; + }; + + auto getPopupWidth = [this, zv]() { + const int zoom_level = zv->zoomLevel(), editors_width = this->width(); + + return qMax(editors_width * zoom_level / 100, editors_width); + }; + + auto updatePopupGeom = [getPopupPos, getPopupWidth, xpopup]() { + const QPoint new_pos = getPopupPos(); + xpopup->setGeometry(new_pos.x(), new_pos.y(), getPopupWidth(), xpopup->height()); + }; + + QObject::connect(xpopup, &XListView::geometryChanged, this, updatePopupGeom); } -bool LineEdit::event(QEvent* event) +void LineEdit::focusOutEvent(QFocusEvent* event) { - if (event && event->type() == QEvent::FocusIn) { - qApp->installEventFilter(this); + if (lastKeyPressed) { + Q_EMIT finishedWithKey(lastKeyPressed, lastModifiers); } - else if (event && event->type() == QEvent::FocusOut) { - qApp->removeEventFilter(this); - if (lastKeyPressed) { - Q_EMIT finishedWithKey(lastKeyPressed, lastModifiers); - } - lastKeyPressed = 0; + + Gui::ExpressionLineEdit::focusOutEvent(event); +} + +void LineEdit::keyPressEvent(QKeyEvent* event) +{ + if (event->key() == Qt::Key::Key_Tab && completerActive()) { + hideCompleter(); + Gui::ExpressionLineEdit::keyPressEvent(event); + return; } - else if (event && event->type() == QEvent::KeyPress && !completerActive()) { - QKeyEvent* kevent = static_cast(event); - lastKeyPressed = kevent->key(); - lastModifiers = kevent->modifiers(); + + lastKeyPressed = event->key(); + lastModifiers = event->modifiers(); + + if ((lastKeyPressed == Qt::Key::Key_Down || lastKeyPressed == Qt::Key::Key_Up) + && completerActive()) { + auto kevent = new QKeyEvent(QEvent::KeyPress, lastKeyPressed, Qt::NoModifier); + QCoreApplication::postEvent(getCompleter()->popup(), kevent); } - return Gui::ExpressionLineEdit::event(event); + + Gui::ExpressionLineEdit::keyPressEvent(event); +} + +XListView::XListView(LineEdit* parent) + : QListView(parent) +{ + setEditTriggers(QAbstractItemView::NoEditTriggers); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + + setAttribute(Qt::WidgetAttribute::WA_ShowWithoutActivating); +} + +void XListView::resizeEvent(QResizeEvent* event) +{ + Q_EMIT geometryChanged(); + QListView::resizeEvent(event); +} + +void XListView::updateGeometries() +{ + QListView::updateGeometries(); + Q_EMIT geometryChanged(); } #include "moc_LineEdit.cpp" diff --git a/src/Mod/Spreadsheet/Gui/LineEdit.h b/src/Mod/Spreadsheet/Gui/LineEdit.h index b81ef60ac0..9255b9334c 100644 --- a/src/Mod/Spreadsheet/Gui/LineEdit.h +++ b/src/Mod/Spreadsheet/Gui/LineEdit.h @@ -24,7 +24,7 @@ #define LINEEDIT_H #include -#include +#include namespace SpreadsheetGui @@ -35,19 +35,35 @@ class LineEdit: public Gui::ExpressionLineEdit Q_OBJECT public: explicit LineEdit(QWidget* parent = nullptr); - - bool event(QEvent* event) override; + void setDocumentObject(const App::DocumentObject* currentDocObj, bool checkInList = true); Q_SIGNALS: void finishedWithKey(int key, Qt::KeyboardModifiers modifiers); -private: - bool eventFilter(QObject* object, QEvent* event) override; - - private: int lastKeyPressed; Qt::KeyboardModifiers lastModifiers; + +protected: + void focusOutEvent(QFocusEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; +}; + +/* QCompleter uses a parentless QListView as a popup, whose geometry + * is corrected using its own algorithm, which does not take into account QGraphicsScene, + * therefore we have to use our own widget to adjust the geometry. */ +class XListView: public QListView +{ + Q_OBJECT +public: + explicit XListView(LineEdit* parent); + +Q_SIGNALS: + void geometryChanged(void); + +protected: + void resizeEvent(QResizeEvent* event) override; + void updateGeometries(void) override; }; } // namespace SpreadsheetGui