2

I have a QLineEdit with a QAction at leading position. I want to know the start position of the text but I don't find how to do:

QLineEdit *le = new QLineEdit(parent);
le->addAction(QIcon(":/myicon"), QLineEdit::LeadingPosition);
// Now I want to get the text start position
// but both return "QMargins(0, 0, 0, 0) QMargins(0, 0, 0, 0)"
qDebug() << le->textMargins() << le->contentsMargins();

I searched in the qt's github sources to find if the addAction() method does something on the contents or text margins but without success.

thibsc
  • 3,098
  • 1
  • 17
  • 29
  • Your question raised my attention. I investigated a little bit and am about to prepare answer. Before, I would like to ask: Why do you need to know the start position of text? – Scheff's Cat Jan 16 '19 at 12:26
  • AFAIK that's not possible. Perhaps you could describe what you *want* to do? – Martin Hennings Jan 16 '19 at 12:35
  • @MartinHennings and @Scheff I want to know the start to begin draw at the right x position for my [LineEditTag](https://github.com/thibDev/qtCustomPlugins) when I have QAction. I think to a possible solution, use the [`cursorRect()`](http://doc.qt.io/qt-5/qlineedit.html#cursorRect) and manage something with position of the rect. – thibsc Jan 16 '19 at 12:46
  • If you use a "one line high" `QTextEdit` - embedding of images would become much easier. Another option could be to use a specific character for this e.g. `✕` (U+2715). Finally, I believe it's easier to derive a custom widget for this where you make painting and mouse interaction on your own than trying to tweak the `QLineEdit`. The [Fridge Magnets Example](http://doc.qt.io/qt-5/qtwidgets-draganddrop-fridgemagnets-example.html) could be a good start. – Scheff's Cat Jan 16 '19 at 13:15
  • @Scheff it's seems to be work with the `cursorRect()` method, the x position of the returned rect looks to be the offset – thibsc Jan 16 '19 at 13:31
  • I sent my answer though I believe it leads rather into a dead end. Please, take this as another argument for my previous recommendations. – Scheff's Cat Jan 16 '19 at 13:36

1 Answers1

3

I must admit that (before reading OPs question) I was not aware about QLineEdit::addAction(). Thus, I wrote a little sample testQLineEditAction.cc:

#include <QtWidgets>

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // init GUI
  QLineEdit qEdit;
  qEdit.addAction(QIcon("./document-properties.svg"), QLineEdit::LeadingPosition);
  qEdit.addAction(QIcon("./document-save.svg"), QLineEdit::TrailingPosition);
  qEdit.show();
  // runtime loop
  return app.exec();
}

and this is how it looks (compiled in cygwin64):

Snapshot of testQLineEditAction

Afterwards, I digged a bit through woboq.org to find out how it is implemented.

I started in QLineEdit::paintEvent():

void QLineEdit::paintEvent(QPaintEvent *)
{

...

  QStyleOptionFrame panel;
  initStyleOption(&panel);

...

  QRect r = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this);
  r.setX(r.x() + d->effectiveLeftTextMargin());
  r.setY(r.y() + d->topTextMargin);
  r.setRight(r.right() - d->effectiveRightTextMargin());
  r.setBottom(r.bottom() - d->bottomTextMargin);

This is interesting: The rectangle for contents is retrieved and then corrected by inner offsets.

  QFontMetrics fm = fontMetrics();

...

  QRect lineRect(r.x() + d->horizontalMargin, d->vscroll, r.width() - 2*d->horizontalMargin, fm.height());

About the d->horizontalMargin, I'm not quite sure but I ignored it for now and followed instead d->effectiveLeftTextMargin():

int QLineEditPrivate::effectiveLeftTextMargin() const
{
    return effectiveTextMargin(leftTextMargin, leftSideWidgetList(), sideWidgetParameters());
}

...

static int effectiveTextMargin(int defaultMargin, const QLineEditPrivate::SideWidgetEntryList &widgets,
                               const QLineEditPrivate::SideWidgetParameters &parameters)
{
    if (widgets.empty())
        return defaultMargin;
    return defaultMargin + (parameters.margin + parameters.widgetWidth) *
           int(std::count_if(widgets.begin(), widgets.end(),
                             [](const QLineEditPrivate::SideWidgetEntry &e) {
                                 return e.widget->isVisibleTo(e.widget->parentWidget()); }));
}

So, I came to the conclusion that QLineEditPrivate::effectiveLeftTextMargin() considers the space for action icons when effective size of text rectangle is determined.

It's a pity that all these functions are private and thus not accessable from outside. After thinking a while how to get access to these from outside and looking into doc. whether I haven't overseen something, I got the idea to use the QActions directly for this:

#include <QtWidgets>

void inspect(const QString &cmd, QAction &qCmd)
{
  qDebug() << (cmd + "->associatedWidgets().size():")
    << qCmd.associatedWidgets().size();
  int i = 0;
  for (QWidget *const pQWidget : qCmd.associatedWidgets()) {
    qDebug() << '[' << i++ << "]:"
      << typeid(*pQWidget).name()
      << "geometry:" << pQWidget->geometry();
  }
}

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // init GUI
  QLineEdit qEdit;
  qEdit.setText("012345678901234567890123456789");
  QAction *const pQCmd1
    = qEdit.addAction(QIcon("./document-properties.svg"), QLineEdit::LeadingPosition);
  QAction *const pQCmd2
    = qEdit.addAction(QIcon("./document-save.svg"), QLineEdit::TrailingPosition);
  qEdit.show();
  qDebug() << "qEdit.geometry():" << qEdit.geometry();
  inspect("pQCmd1", *pQCmd1);
  inspect("pQCmd2", *pQCmd2);
  // runtime loop
  return app.exec();
}

Console output:

Qt Version: 5.9.4
qEdit.geometry(): QRect(0,0 200x23)
"pQCmd1->associatedWidgets().size():" 2
[ 0 ]: 9QLineEdit geometry: QRect(0,0 200x23)
[ 1 ]: 19QLineEditIconButton geometry: QRect(4,2 22x18)
"pQCmd2->associatedWidgets().size():" 2
[ 0 ]: 9QLineEdit geometry: QRect(0,0 200x23)
[ 1 ]: 19QLineEditIconButton geometry: QRect(174,2 22x18)

To compare the values, another snapshot with modified icons (frame drawn in SVGs to show icon size) which has been magnified (factor 5):

Magnified Snapshot of testQLineEditAction (factor 5)

Left QLineEditIconButton reported position (4, 2) but the left frame of icon is 8 pixels away from left border of QLineEdit. There is surely a frame around the QLineEditIconButton which has to be considered as well (and I didn't investigate how to retrieve it). The width of frame might be subject of style engine and thus vary between platforms. To make such attempt robust and portable, the respective values should be retrieved from the widgets or from style. This starts to become a tedious fiddling with more or less chance for success.

I ended up once in a similar situation when trying to answer SO: How to automatically increase/decrease text size in label in Qt.


Concerning QLineEdit::cursorRect():

I believe that using QLineEdit::cursorRect() is (as well) at best fragile.

I modified my above example to check this out:

#include <QtWidgets>

class LineEdit: public QLineEdit {

  public:
    QRect cursorRect() const { return QLineEdit::cursorRect(); }
};

int main(int argc, char **argv)
{
  qDebug() << "Qt Version:" << QT_VERSION_STR;
  QApplication app(argc, argv);
  // init GUI
  LineEdit qEdit;
  qEdit.setText("012345678901234567890123456789");
  qEdit.addAction(QIcon("./document-properties.svg"), QLineEdit::LeadingPosition);
  qEdit.addAction(QIcon("./document-save.svg"), QLineEdit::TrailingPosition);
  qEdit.show();
  qDebug() << "qEdit.cursorRect():" << qEdit.cursorRect();
  // runtime loop
  return app.exec();
}

Console output:

Qt Version: 5.9.4
qEdit.geometry(): QRect(0,0 200x23)
qEdit.cursorRect(): QRect(253,0 9x16)

Funny, that the cursor x-position is not only quite high – it's even higher than the width of qEdit. How comes? The initial text "012345678901234567890123456789" I put into qEdit causes the cursor to be close to the right whereby horizontal scrolling happens. The cursor position seems to be related to the virtual text width (including the clipped range on the left side).

Scheff's Cat
  • 16,517
  • 5
  • 25
  • 45
  • Pretty explanation and investigation. From my side, the cursorRect() method seems to be a viable solution. You idea to use the geometry method of the QAction is an interesting way to get the offset, `actions.size()*actions.first().geometry.width()`. Thank you very much – thibsc Jan 16 '19 at 13:46
  • @ThibautB. It's a bit fragile. - Please, see my update. – Scheff's Cat Jan 16 '19 at 13:49
  • Effectively, It can't work in every case, I continue my investigation to a better solution ;) – thibsc Jan 16 '19 at 13:55