[PyQt] Positioning autocompletion text after QLineEdit

Maurizio Berti maurizio.berti at gmail.com
Sun Mar 10 03:58:36 GMT 2019


Il giorno sab 9 mar 2019 alle ore 23:22 John F Sturtz <john at sturtz.org> ha
scritto:

> (*maurizio.berti at gmail.com <maurizio.berti at gmail.com>*, if you're reading
> this, you'll probably recognize it.  I finally decided to implement this
> using separate widgets, QLineEdit for the user input and QLabel for the
> auto-completion, instead of using a QTextEdit for all of it per your
> suggestion.  I realize there is a QCompleter class that can be used with
> a QLineEdit, but I'm intent on implementing it myself.)
>

Hello again John :-)
I'm not a big fan of "reinventing the wheel", as I tried to do that a lot
of times, and those "lot of times" I realized that once you've deeper
knowledge and experience with a framework (in this case, Qt) using its
tools is usually better.
But. Sometimes having "control" over things *is* better, for various
reasons. In this particular scenario I may agree with you. I don't like the
QCompleter implementation a lot: even if it suits most user cases, its
customization is problematic, expecially for special cases where there is
the need for better UX response (like in your case, which involves some
things QLineEdit doesn't provide, as inline formatting).
Implementing the QTextEdit wouldn't be that much easier indeed, as it would
require *good* QSyntaxHighlighter programming and debugging, and probably
the same amount of coding (considering the effort of time and "mental
energy") you'll put in for font metrics issues and possible
character/cursor positioning.


The issue is that the QLabel doesn't position itself properly at the end of
> what the user has typed in.  I am using
> fontMetrics().horizontalAdvance(text) on the text the user has typed in,
> to determine where to position the Qlabel.  According to the
> documentation, that should be "the distance appropriate for drawing a
> subsequent character after *text*."  But it comes out too close to the
> preceding text.
>

Unfortunately I'm still on Qt 5.7 (my main project is a bit critical,
upgrading PyQt5 now would demand a lot of dependency issues that would put
it at risk, as it's PyQt4 based), this means that I horizontalAdvance()
isn't available for me as it was introduced in Qt 5.11.
Nonetheless, by using the "simpler" QFontMetrics.width() I got almost the
same result, even if not probably "pixel-perfect" (it could depend on the
font rendering hints used for the current font we're using, some updates in
the rendering engine, png compression, etc).

That said, the issue here is that you didn't take into account the margins
applied to the QLineEdit contents, which requires some QStyle work.
The first thing to look for is the subElementRect of
QStyle.SE_LineEditContents, which returns the rectangle inside of which the
text rendering happens: while QLineEdit does not inherit from QFrame as
QLabels do, it still uses the QStyleOptionFrame (as it actually is some
sort of "frame", with its borders and margins).
After that, QLineEdit adds its own margin to the text, which seems to be
hardcoded and set to 2 (according to
https://code.woboq.org/qt5/qtbase/src/widgets/widgets/qlineedit_p.cpp.html#QLineEditPrivate::horizontalMargin
).
Finally, while in most uses this could be enough, remember that you're
using two very different widgets that apply some margins to their contents
whenever they're drawn. Luckily, the QLabel doesn't seem to apply further
margins using default QStyle(s) on Linux, but it might be better to check
for the QLabel's option too. This may also be necessary in some cases where
the QLabel vertical positioning is different from QLineEdit, again
depending on the current QStyle (I think it might the case of the default
styles used on Windows and MacOS).

I've just added some lines to your code, and it seems to be all right also
while zooming in (even with QFontMetrics.width() instead of
horizontalAdvance):

        # Position display widget
        option = QtWidgets.QStyleOptionFrame()
        self.initStyleOption(option)
        rect =
self.style().subElementRect(QtWidgets.QStyle.SE_LineEditContents, option,
self)
        # Add the left position of the contents and the hardcoded
horizontalMargin of QLineEdit
        w.move(self.fontMetrics().width(user) + rect.left() + 2, 0)

As a side note, remember to be careful about variable and property naming:
you used self.style for the stylesheet, but style() is an important
property of QWidgets: I had to change it to have a direct reference to it,
otherwise I'd have to use super/QtWidgets.QLabel.style(self).


Cheers,
Maurizio

-- 
È difficile avere una convinzione precisa quando si parla delle ragioni del
cuore. - "Sostiene Pereira", Antonio Tabucchi
http://www.jidesk.net
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20190310/a07ab99b/attachment-0001.html>


More information about the PyQt mailing list