Show a Qt window that does not take focus

Viewed 620

How could I create a window with Qt that the user can interact with (i.e., clicking buttons in the window) but that does not otherwise change or take focus?

Use case: I have a QLineEdit and I would like to show an additional "manipulator" popup when the QLineEdit gains focus. This manipulator popup might include buttons for tasks like converting to uppercase/lowercase or such.

I can get it to work if I set the focus policy for all the manipulator's child widgets to Qt::NoFocus but this feels a little odd because I literally have to iterate over them.

I am wondering if there is a way to disable focus to a widget altogether, like setEnabled() would disable/enable all the child widgets.

Below are some findings so far.

QCompleter

The QCompleter does something very similar to a QLineEdit, showing a QListView popup. It does so by setting the QListView's parent to 0 so it becomes a window, and it sets the Qt::Popup window flags so it will be frameless. Finally it sets focusPolicy(Qt::NoFocus) to prevent it from taking over the focus from the associated QLineEdit.

This is trivial because there is only one single widget that needs its focus policy set to Qt::NoFocus.

QDateTimeEdit

In QDateTimeEdit something similar is done. The popup there, however, actually takes focus.

1 Answers

There is no such a widget out of the box. However, it can be done in this way:

Header file:

#include <QObject>
#include <QWidget>
#include <QLineEdit>
#include <QBoxLayout>
#include <QPushButton>
#include <QFocusEvent>

class LineEdit : public QLineEdit
{
    Q_OBJECT
public:
    LineEdit(QWidget *parent) : QLineEdit(parent)
    { }

    void focusInEvent(QFocusEvent *event) override
    {
        QLineEdit::focusInEvent(event);
        emit focusIn();
    }

    void focusOutEvent(QFocusEvent *event) override
    {
        QLineEdit::focusOutEvent(event);
        emit focusOut();
    }

signals:
    void focusIn();
    void focusOut();
};

class ExtendedLineEdit : public QWidget
{
    Q_OBJECT
public:
    explicit ExtendedLineEdit(QWidget *parent = nullptr);

protected:
    bool eventFilter(QObject *obj, QEvent *event) override;

private:
    void setupWidget();

    LineEdit *lineEdit;
    QWidget *showableWidget;
    QPushButton *toUppercaseButton;
    QPushButton *toLowercaseButton;

    QWidget *parent;

};

Source file:

#include "ExtendedLineEdit.h"
#include <QPoint>

ExtendedLineEdit::ExtendedLineEdit(QWidget *parent)
    : QWidget(parent)
    , parent(parent)
{
    setupWidget();
}

bool ExtendedLineEdit::eventFilter(QObject *obj, QEvent *event)
{
    Q_UNUSED(obj)
    if (event->type() == QEvent::MouseButtonRelease ||
        event->type() == QEvent::MouseButtonPress ||
        event->type() == QEvent::MouseButtonDblClick)
    {
        lineEdit->setFocus();
    }
    return false;
}

void ExtendedLineEdit::setupWidget()
{
    auto mainLayout = new QBoxLayout(QBoxLayout::Direction::TopToBottom, this);
    this->setLayout(mainLayout);

    lineEdit = new LineEdit(this);
    mainLayout->addWidget(lineEdit);

    showableWidget = new QWidget(parent);
    showableWidget->setAutoFillBackground(true);
    showableWidget->setPalette(QPalette(QPalette::Background, Qt::white));
    showableWidget->hide();

    auto bottomLayout = new QBoxLayout(QBoxLayout::Direction::LeftToRight, showableWidget);
    showableWidget->setLayout(bottomLayout);

    toUppercaseButton = new QPushButton("to uppercase", this);
    toUppercaseButton->installEventFilter(this);
    connect(toUppercaseButton, &QPushButton::clicked,
            this, [&]{
       lineEdit->setText(lineEdit->text().toUpper());
    });

    toLowercaseButton = new QPushButton("to lowercase", this);
    toLowercaseButton->installEventFilter(this);
    connect(toLowercaseButton, &QPushButton::clicked,
            this, [&]{
       lineEdit->setText(lineEdit->text().toLower());
    });

    bottomLayout->addWidget(toUppercaseButton);
    bottomLayout->addWidget(toLowercaseButton);

    connect(lineEdit, &LineEdit::focusIn,
            this, [&]{
            auto size = lineEdit->size();
            auto position = QWidget::mapTo(parent, lineEdit->pos());

            showableWidget->setGeometry(position.x(),
                                        position.y() + size.height(),
                                        size.width(),
                                        40);
            showableWidget->show();

    });

    connect(lineEdit, &LineEdit::focusOut,
            showableWidget, &QWidget::hide);
}

Result:

enter image description here

Related