Positioning autocompletion text after QLineEdit

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

Positioning autocompletion text after QLineEdit

jfsturtz
Hi again all.

I have a QTableView with QLineEdit editor delegate.  I'm trying to implement autocompletion -- basically, the user types some input into the editor widget, and the auto-completed text shows up to the right of what the user has typed.

([hidden email], 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.)

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.

Screen shots of the behavior I'm getting are attached.  Code is also attached (note that you have to have model.py in the same directory as test.py, to provide the data model for the view).  To see the behavior, run test.py and type abcdefghijkl in any of the cells.

Wondering if anyone has an idea why this isn't working the way I expect?  I thought horizontalAdvance() was going to be just the right tool for this job ...

Thanks again.

/John

_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt

test.py (3K) Download Attachment
model.py (1K) Download Attachment
Image 2.png (3K) Download Attachment
Image 1.png (4K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Positioning autocompletion text after QLineEdit

Maurizio Berti
Il giorno sab 9 mar 2019 alle ore 23:22 John F Sturtz <[hidden email]> ha scritto:
([hidden email], 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

_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt