Qt::WindowStaysOnTopHint widget is always active - qt

I have a main window (QMainWindow) and a widget with flag Qt::WindowStaysOnTopHint that provides auxiliary information and options (let's call it helper). If I work with main window and then click on any other application this window ceases to be active (isActiveWindow() == false). But if I work with the helper first and then I switch to other application/window (not to Main Window) it stays to be active. How can I handle switching from the helper window to other application? Even QApplication::activeWindow() returns true because of this.
.h file:
//! Popup structure that contains listw_popup that shows tips
struct PopupWidget {
PopupWidget(QWidget*);
QWidget *base;
QListWidget *listw_popup;
};
class MainWindow : public QMainWindow
{
...
private :
PopupWidget popup_spec;
...
}
.cpp file:
PopupWidget::PopupWidget(QWidget* parent)
{
base = new QWidget(parent, Qt::SplashScreen | Qt::WindowStaysOnTop);
listw_popup = new QListWidget(base);
}
MainWindow::MainWindow(QWidget *parent) : ... popup_spec(this) ...
{
...
}
When popup_spec.base.show() is called this widget appears. And if I click on this widget (means the widget becomes active) it stays to be active even if I switch to other application.

Ok so if I follow your requirements correctly,
You want you helper widget(which has Qt::WindowStaysOnTopHint) to be hidden when the application loses focus.
You could try something like this:
In your MainWindow.cpp say in the constructor add:
qApp->installEventFilter(this);
and add an event filter in MainWindow.cpp (don't forget to declare in .h as well):
bool MainWindow::eventFilter(QObject* object, QEvent* event) {
if (event->type() == QEvent::ApplicationDeactivate)
popup_spec->base->hide();
return QWidget::eventFilter(object, event);
}
This will hide the helper widget when switching apps. If you want the reverse functionality to show the helper widget when application gets activated, In the same event filter check for QEvent::ApplicationActivate and call popup_spec->base->show();

Related

QT: MAC menu shared among multiple QWidgets only triggers MainWindow's functions

I have an application generally structured as the following:
class MultiEditor : public QWidget
{
...
MultiSplitter *mSplitter;
...
}
MultiEditor::MultiEditor( Main *main, QWidget * parent )
{
...
mActions[SplitHorizontally] = action = new QAction(tr("Split To Right"), this);
action->setShortcut( tr("Ctrl+P, 3", "Split To Right"));
connect(action, SIGNAL(triggered()), this, SLOT(splitHorizontally()));
settings->addAction( action, "editor-split-right", editorCategory);
...
}
void MultiEditor::splitHorizontally()
{
... do something on &mSplitter (above);
}
and a class MainWindow:
MainWindow::MainWindow(Main * main)
{
...
mEditors = new MultiEditor(main);
setCurrentMultiEditor(mEditors);
...
createActions();
mMenu = createMenus();
this->setMenuBar(mMenu);
...
}
and a class SubWindow that creates a widget:
SubWindow::SubWindow( QVariant * splitterData = 0, QWidget * parent = 0 )
{
...
sEditors = new MultiEditor(Main::instance(), this);
setCurrentMultiEditor(sEditors);
...
#ifndef Q_OS_MAC
QMenuBar *newMenu = main->createMenus();
newMenu->setSizePolicy(QSizePolicy ::Expanding , QSizePolicy ::Fixed );
windowLayout->addWidget(newMenu);
#endif
...
}
and the actual menu constructor:
QMenuBar * MainWindow::createMenus()
{
QMenuBar *menuBar;
QMenu *menu;
QMenu *submenu;
...
#ifdef Q_OS_MAC
menuBar = new QMenuBar(0);
#else
menuBar = new QMenuBar();
#endif
...
menu->addAction( currentMultiEditor()->action(MultiEditor::SplitHorizontally) );
...
return menuBar;
}
Let's suppose that I have the MainWindow and a open SubWindow.
Both create a MultiEditor with a Splitter inside.
All this works good on Ubuntu: every action is associated with the right Splitter. If I click on the SubWindow's Splitter, for example, and I trigger the action "splitHorizontally", the SubWindow's MultiEditor:splitHorizontally() is triggered, and the SubWindow's Splitter is affected.
This does not happen on Mac. Here, if I click on a SubWindow's Splitter, the mSplitter of the MainWindow is affected.
I believed that clicking on a mSplitter, I would focus that mSplitter, so the MAC's parentless menu would act on whatever mSplitter focused. But it happens that it get stucked with the mSplitter on the MainWindow.
Actually, could it be that I was thinking that the function currentMultiEditor() was dynamic (every time the action was triggered, a different MultiEditor was called), but in the truth the multiEditor is fixed when creating the menu (in MAC the menu is created only on the MainWindow, so the currentMultiEditor() would be MainWindow's mEditors, and that would still be anytime the action were triggered from anywhere)?
What is the best design for you?
Should I create new menus on MAC too? How?
If I understood your question correctly, this looks indeed like a focus issue and I'm not surprised Linux behaves differently than macOS.
Probably on macOS even if you open a window and click somewhere, the focus remains on the parent window, thus calling the unexpected QAction slot.
I think you have 2 options:
when a subwindow is open, make sure it gains the focus. Call the QWidget::setFocus() method on one of the visible widgets of the window
accept keystrokes only in one place and redirect them to the current context, which in other terms is your active window
Hope that helps

Show a QWidget after pressing a button

I want to change a QWidget in a QMainWindow dynamically. Therefore, the old widget (if it exists) will be deleted, a new one will be constructed and added to the main window.
The widget (_visualization) is a QMainWindow itself that contains a menu bar and a widget that shows an OSG scene graph.
If I don´t call show() on the new widget, I will only see the menu bar, but not the scene graph.
My goal is to call show(), when the user clicks on a button. The button is connected with the outer QMainWindow (MyQMainWindow).
Unfortunately, when I call show() on _visualization in the connected method, the scene graph will not be shown. In contrast to that, the scene graph will be shown, if I call it in the constructor method (loadVisualization(...)).
MyQMainWindow::MyQMainWindow(QWidget *parent ) :
QMainWindow(parent) {
...
loadVisualization(...);
connect(_ui->nextButton, SIGNAL(clicked()), this, SLOT(showNext()));
...
}
void MyQMainWindow::loadVisualization(QString filePath) {
if (_visualization) {
_heightWidgetLayout->removeWidget(_visualization);
delete _visualization;
}
_visualization= new HeightVisualization(0, filePath);
_visualization->setParent(_mainWindowWidget);
_heightWidgetLayout->addWidget(_visualization);
//_visualization->show(); // will work here
}
void MyQMainWindow::showNext() {
_visualization->show(); // does not work here!
}
I know that QT will call setVisible(...) on the widget. This method first tests some states in QT (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)). If I call show() in showNext(), these tests lead to a return without any change.
Is it a problem with connectors and slots? Is there a possibility to show the widget, when the user clicked on a button?
What I have learned is that it is easy to use stackedWidget.
You can then programmatically change it with this
currentPageIndex = (currentPageIndex + 1) % 3;
ui->stackedWidget->setCurrentIndex(0);
The 3 can be the total pages you have in your stack widget. You can either use currentPageIndex, or you can just create some constants with the page ids like I have done with the 0.

make Custom QWidget selectable

I'm working on a boardgame and I'm trying to make QWidgets (Rectangles) selectable.
So I have a BoardView (inherited from QWidget) which contains BuildingViews, PlantationViews (both inherited from QWidget). It all shows up on the window, but it is not clickable. How can I make this clickable?
You could try to make a QMouseEvent implementation where the widget ID is forwarded,
something like this:
In the implementation of your widget (e.g. YourWidget.cpp):
YourWidget::MouseReleaseEvent(QMouseEvent *event)
{
emit clickedWithMouse(this); // this is a signal, declared in YourWidget.h
}
In the "main" game file (e.g. Game.cpp):
Game::onButtonClicked(YourWidget* widget) // this is a public slot, you must connect all YourWidgets's clickedWithMouse signals to this slot (in Game object code, e.g. when initialising the board)
{
lastWidget = widget; //save the widget "ID" (lastWidget is a member of class Game)
someFunction(widget); //do something with the widget (if you wish)
}

Drag and drop widget outside source application widgets

I have a Qt Desktop aplication which has several top-level widgets. Subwidgets of top-level widgets can be moved between top-level widgets by using drag-and-drop mechanism.
The problem i have now is to drop a sub-widget outside any of existing top-level widgets and create a new top-level widget to contain this one. Lets call this separation.
Can this be done using drag-and-drop? I could not find a way where my dropEvent goes?
Can i want to handle the drop event in my application even if the drop place is not allowed? Maybe a mouse release or something?
I cannot change everything now but also a question for the future. Is docking/undocking a better way to do this?
Regards
Mihai
I found a way to do this. When drag moves outside of the application widgets QDrag object emits a targetChanged signal with 0 parameter.
So i inherited from QDrag and then emit a custom signal in destructor if the target() is null.
The only problem is that the cursor looks like interdiction of drop and this i could not fix because QDrag can only set cursor pixmap for valid actions like Move or Copy or Link
Update:
Here is the inherited class.
class TabDrag: public QDrag
{
Q_OBJECT
public:
explicit TabDrag(QWidget *dragSource);
~TabDrag();
signals:
void tearOff(); /// emit tearOff signal if the QDrag object is destroyed and target was null
};
TabDrag::TabDrag(QWidget *dragSource):QDrag(dragSource)
{
}
TabDrag::~TabDrag()
{
// check if we need to detach this tab
if(!target())
{
emit tearOff();
}
}
The tearOff signal should be connected to whatever you want to happen. In my case i pull out the widget from the tab and change parent to a new window.
Example of usage
void MyTabBar::mouseMoveEvent(QMouseEvent* event)
{
..................
TabDrag * drag = new TabDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(*m_tabPixmap.data());
drag->setHotSpot(QPoint(m_dragStartPos.x() - tabAtRect.x(), m_dragStartPos.y() - tabAtRect.y()));
drag->exec();
connect(drag, SIGNAL(tearOff()), this, SLOT(onTearOff()));
}

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