Automatically hiding a Qt QWidget after the user clicks elsewhere - qt

I would like to create a QWidget that acts like a pop-up, and I am having trouble figuring out how to do it.
I want to create a sidebar/drawer that gets shown when the user clicks a button. The user will briefly interact with child widgets of the sidebar. The tricky part is that I want the sidebar to be hidden automatically when the user clicks elsewhere in the application. Worst case I can require the user to manually toggle the sidebar, but automatically hiding it is convenient.
I tried or investigated:
Setting the Qt::Popup window flag, but the sidebar needs to be a widget, not a separate window.
Listening for focus events, but I want to support keyboard navigation. The user should be able to Tab to other widgets while the sidebar remains visible. The sidebar should be hidden when the user actually interacts with the other widgets (such as hitting Enter).
Adding an event filter on the top-level widget, but it doesn't receive events that occur on its children (such as a button being clicked).
Adding an invisible "overlay" widget over the rest of the application, but I want the user to be able to directly click on other widgets. Capturing clicks in the invisible overlay would require the user to click twice (once to clear the overlay, then again to interact with other widgets).
Is this just not feasible?

One possibility might be to install an event filter on the QApplication instance itself. Note, though, that application wide event filters can have a noticeable performance impact so it should only be installed as/when necessary. A mock-up might look something like...
class sidebar: public QWidget {
using super = QWidget;
protected:
virtual bool eventFilter (QObject *obj, QEvent *event) override
{
/*
* Your stuff goes here...
*/
if (auto *widget = dynamic_cast<QWidget *>(obj)) {
if (isAncestorOf(widget)) {
/*
* obj refers to a QWidget of some description that is
* a child of this sidebar instance.
*/
} else {
/*
* widget _isn't_ a child of this sidebar so may need
* to take action (including hiding) depending on the
* specifics of widget and the event details.
*/
}
}
return super::eventFilter(obj, event);
}
virtual void showEvent (QShowEvent *event) override
{
super::showEvent(event);
qApp->installEventFilter(this);
}
virtual void hideEvent (QHideEvent *event) override
{
super::hideEvent(event);
qApp->removeEventFilter(this);
}
};
Will require a certain amount of experimentation obviously :-)

Related

How to hide a QWidget when mouse clicked on out side that widget

I want hide a QWidget when mouse clicks out of that widget just like it have a popup flag:
auto widget = new QWidget(this);
widget->setWindowFlag(Qt::Popup); // this widget will hide, when mouse click out of that widget.
For some reason, I can't set these flags and must implement some thing myself which behaves like this.
Or can I get a mouse event out of that widget?
Solution:
As I said, I can't use Qt flags and have to implement the similar behavior myself.My solution is installEventFilter to QApplication, in the override method eventFilter,I filter the QMouseEvent and send a signal.
Yes, you can get a mouse event out of the widget.
Make a custom widget and reimplement mousePressEvent, which will catch the last click (the ouside-the-widget click that hides the "popup"). But be careful to add the call to QWidget::mousePressEvent(event) in the end, otherwise the last click will be lost and your widget will remain onscreen.
CustomWidget::CutomWidget(QWidget *parent) : QWidget(parent)
{
setWindowFlags(Qt::Popup);
}
void CustomWidget::mousePressEvent(QMouseEvent *event)
{
if (!this->underMouse()) {
//if the click is not on the widget, i.e. if it's the click that hides it,
// you caught it, do what you want to do here.
}
QWidget::mousePressEvent(event);
}
Hope it helps.

Triggering an action based on its custom shortcut

Suppose I have some action to happen. For that I can create a QAction object and connect its triggered() signal to the slot that executes the desired function. Also, I can have a shortcut associated with the action; by changing the shortcut I'll be able to execute the same action with that shortcut.
My problem now is that the "shortcut" I wanna set to the action, contains also a mouse button press (and mouse events cannot be assigned to action shortcuts); say I want Shift+Left mouse button. Maybe this sounds a little bit harsh but bear with me.
What do I need? Well, I have a button, and an action (say "execute a script"). I want the script to execute when Shift+Left click is clicked, and I want this "shortcut" to be customized, i.e. the user should be able to change to shortcut to, say Ctrl+Left click (from some GUI element, e.g. button text), and now Ctrl+Left click should execute the script.
How can I achieve this?
Note: I as a user would expect an action triggered by a mouse button to be position dependent. If so, the following gets a bit simpler.
Qt doesn't have an option to specify such a shortcut.
You can roll your own by reacting to mouse events:
Maybe you have an event handler mousePressEvent(),
or a generic eventFilter(QObject *obj, QEvent *evt),
or utilize QApplication::notify
Whichever, at some place you need to catch a QMouseEvent *mouseEvt.
Choose the widget (or qApp) that is as outmost as needed.
There, compare mouseEvt->button() and mouseEvt->modifiers() to your list of actions and trigger the selected action. When the user chooses another trigger method, adjust your list of actions.
Let's put this to practice:
class MainWindow : public QWidget {
Q_OBJECT
public:
QMap<QPair<Qt::MouseButton, Qt::KeyboardModifiers>, QAction*> mapMouseShortcuts;
QAction *pLaunchScript;
MainWindow() : QWidget() {
mapMouseShortcuts.insert(qMakePair(Qt::LeftButton, Qt::ControlModifier), pLaunchScript);
}
void mousePressEvent(QMouseEvent *me) {
QAction *action = mapMouseShortcuts.value(qMakePair(me->button(), me->modifiers()), Q_NULLPTR);
if(action != Q_NULLPTR) {
action->trigger();
me->accept(); // optional
}
// optional:
if(!me->isAccepted()) {
QWidget::mousePressEvent(me);
}
}
};

Qt, how to make Tooltip visible without hovering over the control?

I want tooltips to be visible by default when the container widget gets focus/visible.
I want tooltip to appear without being mouse hover on the respective control.
You need to subclass the widget and override handler(s) for event(s) which should produce tooltip display. In the handler, create a QHelpEvent of type QEvent::ToolTip and enqueue it at the event loop. Finally call the parent's original handler, to let it do what was originally intended.
So specifically for getting focus on button, it would be
class MyButton : public QPushButton {
virtual void focusInEvent(QFocusEvent *) {
if(evt->gotFocus()) {
QPoint pos(0,0);
QHelpEvent* help = new QHelpEvent(
QEvent::ToolTip,pos,this->mapToGlobal(pos));
QCoreApplication::postEvent(this,help);
}
QPushButton::focusInEvent(evt);
}
}
For visibility you would override
void QWidget::showEvent(QShowEvent * event);
and do similar code. You need to adjust relative pos to your taste, because originally tooltip dependens on mouse position which you don't have here. Also keep very tight control over making your widgets focused and/or visible. By default, something gets focus all the time, so you will get tooltips all over the place.

How to caprure keyevents of child widgets in QT

I have created the Ui screen named HomeScreen.ui in Qt. This screen have no of different widget like QPushButton, QLabel etc. I want change the default navigation, for that I was trying to capture the keyEvents using keyPressedEvent function
void HomeScreen::keyPressEvent(QKeyEvent *event )
{
int keyCode = event->key();
qDebug() << keyCode;
}
but this is captuting event only when the focus is on homescreen, if the focus is on any child widget like push button keyPressEvent is not getting called. I want to capture event on child widgets so that i can write the navigation for them.
Can anybody tell me how to do that?

Close Widget Window if mouse clicked outside of it

This is sort of a chicken and egg problem. I'd like my widget window to be closed when the mouse clicks outside. As I understand it, there will be no mouse events for my widget for a click occurring outside of it. There is a SetFocus slot, but where is its counterpart or focus loss? "focusOutEvent" doesn't get called for my class.
My widget window is a child window of a widget always shown on my main window and it's a "Qt::ToolTip", so I assume some problems could arise from that fact. Any way around that?
My Goal: I have a custom toolbar widget where buttons on it may have “drop down” widgets. These drop down widgets have no standard windows frame. I don’t want them to “steal” caption focus from the main window and I want them to disappear as soon as the user clicks ANYWHERE on the screen outside of their region. I have having serious difficulties finding a strategy that’s not compromise on Qt to get this done.
Am I missing something? (bet I am).
I used:
setWindowFlags(Qt::FramelessWindowHint | Qt::Popup);
This seems to work well on OSX and Windows. My window appears correctly, does not steal the focus from my main window's caption, and the focus loss event is called correctly as soon as I click outside of it.
If your widget could have focus, and 'steal' the caption focus of some of your other widgets, it would have been easier. Something like this could work:
class ToolBarWidget : public QWidget
{
Q_OBJECT
public:
explicit ToolBarWidget(QWidget * parent = 0)
{
setFocusPolicy(Qt::ClickFocus);
}
protected:
void focusOutEvent(QFocusEvent * event)
{
close();
}
}
And when you create any of your widgets you'd do:
ToolBarWidget * pWidget = new ToolBarWidget(this);
pWidget->show();
pWidget->setFocus();
Done! Well, I guess not quiet. first, you don't want the ToolBarWidget to get any focus in the first place. And second, you want for the user to be able to click anywhere and the ToolBarWidget to be hidden.
So, you may keep track of every ToolBarWidget that you create. For example, in a 'QList ttWidgets' member variable. Then, whenever you create a new ToolBarWidget, you'd do this:
ToolBarWidget * pWidget = new ToolBarWidget(this);
pWidget->installEventFilter(this);
pWidget->show();
and in your main widget class, implement the eventFilter() function. Something like:
bool MainWidget::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::FocusOut ||
event->type() == QEvent::KeyPress ||
event->type() == QEvent::MouseButtonPress)
{
while (!ttWidgets.isEmpty()) {
ToolBarWidget * p = ttWidgets->takeFirst();
p->close();
p->deleteLater();
}
}
return MainWidget::eventFilter(obj, event);
}
And that will work. Because this way, even though your ToolTabWidgets aren't getting focus, some other widget in your main widget has focus. And once that changes (whether the user clicked out of your window, or on another control inside it, or in this case, a key or mouse button is pressed, the control will reach that eventFilter() function and close all your tab widgets.
BTW, in order to capture the MouseButtonPress, KeyPress etc. from the other widgets, you would either need to installEventFilter on them too, or just reimplement the QWidget::event(QEvent * event) function in your main widget, and look for those events there.
you can do this by using QDesktopWidget.h like this
void MainWindow::on_actionAbout_triggered()
{
AboutDialog aboutDialog;
//Set location of player in center of display
aboutDialog.move(QApplication::desktop()->screen()->rect().center() -aboutDialog.rect().center());
// Adding popup flags so that dialog closes when it losses focus
aboutDialog.setWindowFlags(Qt::Popup);
//finally opening dialog
aboutDialog.exec();
}
This is what worked for me in order to not steel the focus from the main application:
.h
bool eventFilter(QObject *obj, QEvent *event) override;
.cpp
bool Notification::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::MouseButtonPress)
deleteLater();
return QObject::eventFilter(obj, event);
}
...
// somewhere else (i.e. constructor, main window,...)
qApp->installEventFilter(this);
OP's own answer is great for Qt versions below 4.8, but as they mention in their answer, it does not work for versions above that. The Qt::Popup widget will not disappear when the mouse is clicked outside of the widget, and it will sink all of the input that would normally close it.
Upon further investigation, this is only an issue for non-dialog widgets. A QDialog using Qt::Popup will properly close when the user clicks outside of it, but any other QWidget, like a QFrame, will not. So in order to work around this behavior change in Qt 4.8, all that is necessary is to wrap the widget in a QDialog.

Resources