From 47d191354437ae39e87950008870e718641b579f Mon Sep 17 00:00:00 2001 From: Krzysztof Date: Thu, 25 Dec 2025 09:11:00 +0100 Subject: [PATCH] Core: Extend completer popup list in 'Expression editor', fix size and position adjustment (#25242) * Core: Fix completer popup adjustment after 'Tab' pressing Completer popup in Expression Editor is now positioned and sized after pressing 'Tab'. This was missing when feature was added. * Core: Extend completer popup list to up 20 elements or up to screen edge Completer popup will now show up to 20 elements or will be limited to screen edge; if limited < 3 rows, popup will be displayed over cursor. * Core: Fix completer wrapping on secondary screens * Core: Fix completer positioning when includes long entries * Core: Fix linter complaints --- src/Gui/ExpressionCompleter.cpp | 77 +++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/src/Gui/ExpressionCompleter.cpp b/src/Gui/ExpressionCompleter.cpp index 36afce5566..1abac0c1fe 100644 --- a/src/Gui/ExpressionCompleter.cpp +++ b/src/Gui/ExpressionCompleter.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include @@ -34,6 +36,8 @@ #include #include #include +#include +#include #include #include @@ -1227,6 +1231,7 @@ void ExpressionTextEdit::keyPressEvent(QKeyEvent* e) // refresh completion list completer->setCompletionPrefix(completer->currentCompletion()); + adjustCompleterToCursor(); if (completer->completionCount() == 1) { completer->popup()->setVisible(false); } @@ -1278,9 +1283,14 @@ void ExpressionTextEdit::adjustCompleterToCursor() return; } + const int completionsCount = completer->completionModel()->rowCount(); + if (!completionsCount) { + return; + } + // get longest string width int maxCompletionWidth = 0; - for (int i = 0; i < completer->completionModel()->rowCount(); ++i) { + for (int i = 0; i < completionsCount; ++i) { const QModelIndex index = completer->completionModel()->index(i, 0); const QString element = completer->completionModel()->data(index).toString(); maxCompletionWidth = std::max( @@ -1296,28 +1306,69 @@ void ExpressionTextEdit::adjustCompleterToCursor() int posX = cursorPos.x(); int posY = cursorPos.y(); - completer->popup()->setMaximumWidth(this->viewport()->width() * 0.6); - completer->popup()->setMaximumHeight(this->viewport()->height() * 0.6); + constexpr double popupLengthRatio = 0.6; // popup shall not be longer than 0.6 of + // TextEdit length + const int widthLimit = static_cast(this->viewport()->width() * popupLengthRatio); + completer->popup()->setMaximumWidth(widthLimit); + maxCompletionWidth = std::min(maxCompletionWidth, widthLimit); - const QSize completerSize { - maxCompletionWidth + 40, - completer->popup()->size().height() - }; // 40 is margin for scrollbar - completer->popup()->resize(completerSize); + QScreen* screen = QGuiApplication::primaryScreen(); + // looking for screen on which popup appears + const int cursorGlobalY = mapToGlobal(cursorPos).y(); + for (QScreen* elem : QGuiApplication::screens()) { + const int screenTopY = elem->geometry().top(); + const int screenBottomY = elem->geometry().bottom(); - // vertical correction - if (posY + completerSize.height() > viewport()->height()) { - posY -= completerSize.height(); + if (cursorGlobalY >= screenTopY && cursorGlobalY < screenBottomY) { + screen = elem; + break; + } + } + + constexpr double marginToScreen = 0.05; // margin to screen as percent of screen + // height; keep 5% margin to screen edge + constexpr int rowsLimit = 20; // max. count of rows that shall be shown at once + + const int rowHeight = completer->popup()->fontMetrics().height(); + int rowsToEdge = static_cast( + (screen->geometry().bottom() * (1.0 - marginToScreen) + - (mapToGlobal(cursorPos).y() + rowHeight)) + / rowHeight + ); + const auto adjustHeight = [&rowHeight, &completionsCount](const int _rowsToEdge) -> int { + return std::min({ + rowHeight * rowsLimit, // up to 'rowsLimit' elements shall be shown at once + _rowsToEdge * rowHeight, // or less limited to screen edge + completionsCount * rowHeight + 5 // or all, if only there are only few; 5 is magic + // number, somehow last entry is partly hovered + }); + }; + + int adjustedPopupHeight = adjustHeight(rowsToEdge); + + // vertical correction to cursor + if (rowsToEdge < 4) { + // display above cursor + rowsToEdge = static_cast( + mapToGlobal(cursorPos).y() - screen->geometry().height() * marginToScreen + ); + adjustedPopupHeight = adjustHeight(rowsToEdge); + posY -= adjustedPopupHeight; } else { - posY += fontMetrics().height(); + // display under cursor + posY += rowHeight; } - // horizontal correction + const QSize completerSize {maxCompletionWidth + 40, adjustedPopupHeight}; // 40 is margin for + // scrollbar + + // horizontal correction to cursor if (posX + completerSize.width() > viewport()->width()) { posX = viewport()->width() - completerSize.width(); } + completer->popup()->resize(completerSize); completer->popup()->move(mapToGlobal(QPoint {posX, posY})); completer->popup()->setVisible(true); }