[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>
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -26,9 +26,12 @@
|
||||
#include <QApplication>
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QGraphicsProxyWidget>
|
||||
#endif
|
||||
|
||||
#include "LineEdit.h"
|
||||
#include <Gui/MainWindow.h>
|
||||
#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<QKeyEvent*>(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<Gui::MDIView> 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<ZoomableView> zv = active_view->findChild<ZoomableView*>();
|
||||
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<qreal>(zv->zoomLevel()) / 100.0;
|
||||
const qreal shift_x = static_cast<qreal>(shift.x()) / scale_factor,
|
||||
shift_y = static_cast<qreal>(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<QKeyEvent*>(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"
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#define LINEEDIT_H
|
||||
|
||||
#include <Gui/ExpressionCompleter.h>
|
||||
#include <QWidget>
|
||||
#include <QListView>
|
||||
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user