QTooltip for QActions in QMenu - c++

I want to be able to show ToolTips for QMenu items (QActions). The best I have achieved is to connect the hovered signal of the QAction to a QTooltip show:
connect(action, &QAction::hovered, [=]{
QToolTip::showText(QCursor::pos(), text, this);
});
The problem is that sometimes the program will position the tooltip below the menu, specially when changing menus.
Is there any way to force the tooltip to show on top?

Since Qt 5.1, you can use QMenu's property toolTipsVisible, which is by default set to false.
See the related Qt suggestion.

You can subclass QMenu and reimplementing QMenu::event() to intercept the QEvent::ToolTip event and call QToolTip::showText to set the tooltip for the active action :
#include <QtGui>
class Menu : public QMenu
{
Q_OBJECT
public:
Menu(){}
bool event (QEvent * e)
{
const QHelpEvent *helpEvent = static_cast <QHelpEvent *>(e);
if (helpEvent->type() == QEvent::ToolTip && activeAction() != 0)
{
QToolTip::showText(helpEvent->globalPos(), activeAction()->toolTip());
} else
{
QToolTip::hideText();
}
return QMenu::event(e);
}
};
Now you can use your custom menu like :
Menu *menu = new Menu();
menu->setTitle("Test menu");
menuBar()->addMenu(menu);
QAction *action1 = menu->addAction("First");
action1->setToolTip("First action");
QAction *action2 = menu->addAction("Second");
action2->setToolTip("Second action");

Related

qt create button when right click

I am new in qt I want to create a button when I right click
There is my code:
void MainWindow::right_clicked(QMouseEvent *event)
{
if(event->button() == Qt::RightButton)
{
QPushButton *item = new QPushButton();
item->setIcon(QIcon(":/images/7928748-removebg-preview(1).ico"));
item->setIconSize(QSize(32, 32));
item->setGeometry(QRect(QPoint(event->x(), event->y()), QSize(32, 32)));
}
}
But nothing appears
To capture any mouse event in a QWidget you must override the mousePressEvent method.
class MainWindow : public QMainWindow
{
Q_OBJECT
protected:
void mousePressEvent(QMouseEvent *event);
};
And in the mainwindow.cpp, implement it as follows:
void MainWindow::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::RightButton) {
// make mainwindow parent of this button by passing "this" pointer
QPushButton *item = new QPushButton(QIcon(":/images/close-button-icon"), "", this);
// set button position to the location of mouse click
item->setGeometry(QRect(QPoint(event->x()-16, event->y()-16), QSize(32, 32)));
item->show();
}
}
If you don't save the pointer to QPushButton, then you will not be able to use it afterwards.

Qt: propagate mouse clicks through a transparent widget

I'm trying to create a widget which, when shown, it will intercept any mouse clicks, process them, but then forward the click to the widget that was under the mouse when it was clicked. I've created a class that represents this widget; all it does for now is capture mouse release events, hide itself and post the event to the widget the click was intended for using QApplication::postEvent:
class Overlay : public QWidget
{
Q_OBJECT
public:
Overlay(QWidget* parent)
: QWidget(parent){}
protected:
void mouseReleaseEvent(QMouseEvent* event)
{
hide();
auto child = parentWidget()->childAt(event->pos()); // get the child widget the event was intended for
auto e = new QMouseEvent(*event);
if(child) QCoreApplication::postEvent(child, e); // why doesn't this have any effect?
event->accept();
}
void paintEvent(QPaintEvent* event)
{
QPainter p(this);
p.fillRect(rect(), QColor(0,0,0,150));
}
};
Unfortunately, the call to postEvent doesn't seem to have any effect at all. Here is an example where I'm setting up some child widgets; clicking on any widget will print something out:
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr)
{
m_overlay = new Overlay(this);
m_overlay->hide();
auto button = new QPushButton("Add overlay");
auto button2 = new QPushButton("PushButton");
auto list = new QListWidget();
list->addItems(QStringList() << "item1" << "item2" << "item3");
connect(list, &QListWidget::itemClicked, [](const QListWidgetItem* item)
{
qDebug() << item->text();
});
connect(button2, &QPushButton::clicked, []()
{
qDebug() << "Button clicked";
});
connect(button, &QPushButton::clicked, [this]()
{
m_overlay->setGeometry(rect());
m_overlay->raise();
m_overlay->show();
});
auto layout = new QVBoxLayout(this);
layout->addWidget(list);
layout->addWidget(button);
layout->addWidget(button2);
}
public:
~Widget(){}
private:
Overlay* m_overlay;
};
I've confirmed that clicks on the overlay are captured and the overlay is hidden as intended but the event is not propagated to the underlying widget. Using QApplication::sendEvent doesn't work either. What am I missing?
Using Qt 5.15.1 on MacOS 11.2.3. Thanks

QScintilla: How to add a user context menu to textEdit? (C++)

I'm struggeling with telling a QScitilla textEdit that is the main widget of my MainWindow app to accept showing a personalized context menu on right-clicking the mouse.
What works fine if I use a standard Qt5 textEdit fails if used with the QScintilla alternative. I tried it with defining a user menu from some actions:
void MainWindow::contextMenuEvent(QContextMenuEvent *event)
{
QMenu menu(this);
menu.addAction(cutAct);
menu.addAction(copyAct);
menu.addAction(pasteAct);
menu.exec(event->globalPos());
}
#endif // QT_NO_CONTEXTMENU
reacting on QContextMenuEvent, but the menu only shows up when I right-click an element of the MainWindow instead of the QScintilla textEdit. When I do within the textEdit, only the standard cut/copy/paste menu is shown.
How to implement that for QScintilla textEdit?
There are two methods:
Method 1: set Qt::CustomContextMenu for context menu policy of QScintilla text edit :
textEdit->setContextMenuPolicy( Qt::CustomContextMenu );
connect(textEdit, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(ShowContextMenu(const QPoint &)));
}
void MainWindow::ShowContextMenu(const QPoint &pos)
{
QMenu contextMenu(tr("Context menu"), this);
QAction action1("Action 1", this);
connect(&action1, &QAction::triggered, this, []{
qDebug() << "On action 1 click !!!";
});
contextMenu.addAction(&action1);
contextMenu.exec(mapToGlobal(pos));
}
Method 2: Define a subclass of QScintilla then redefine the override function contextMenuEvent :
class MyQsciScintilla : public QsciScintilla
{
Q_OBJECT
public:
explicit MyQsciScintilla(QWidget *parent = nullptr);
void contextMenuEvent(QContextMenuEvent *event);
//....
};
void MyQsciScintilla::contextMenuEvent(QContextMenuEvent *event)
{
QMenu *menu = createStandardContextMenu();
menu->addAction(tr("My Menu Item"));
//...
menu->exec(event->globalPos());
delete menu;
}

How to convert QMenu to QMenuBar?

I have QMenu, which contains submenus:
QMenu menu;
// Add some submenus
menu.addMenu(new QMenu("1", menu));
menu.addMenu(new QMenu("2", menu));
menu.addMenu(new QMenu("3", menu));
I want to move these submenus from QMenu to QMenuBar:
QMenuBar* menubar = convertFromQMenu(&menu);
Here is how I think the implementation of convertFromQMenu might look like:
QMenuBar* convertFromQMenu(QMenu* menu) {
QMenuBar *menubar = new QMenuBar();
/*
for(QMenu* menu: menu->menus()) {
menu.setParent(menubar);
menubar.addMenu(menu);
}
*/
return menubar;
}
However, the commented code does not compile.
How to fix this?
You don't need that kind of 'conversion'. If you read carefully from the Qt official doc about QMenuBar and addMenu() member, you can easily add your QMenu to your QMenuBar:
QMenu menu;
// Add some menus
menu.add (new QMenu("1", menu));
menu.add (new QMenu("2", menu));
menu.add (new QMenu("3", menu));
QMenuBar menubar;
menubar.addMenu(&menu);
If you want to use the QMainWindow menu bar, the doc states :
In most main window style applications you would use the menuBar() function provided in QMainWindow, adding QMenus to the menu bar and adding QActions to the pop-up menus.
Example (from the Menus example):
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(newAct);
Cause
for(QMenu* menu: menu->menus()) {
QMenu does not have a menus method.
Solution
The path to get to where you want is a little bit longer:
Use QWidget::actions to get a list of the actions, added to the menu
Get the QMenu associated with each action using QAction::menu
Now you can add the menu to the QMenuBar.
Note: Do not change the parenting of the menus and submenus.
Example
Based on your code, I would suggest you to implement convertFromQMenu like this:
QMenuBar *MainWindow::convertFromQMenu(QMenu *menu)
{
auto *menubar = new QMenuBar(this);
foreach (QAction *act, menu->actions())
if (act->menu())
menubar->addMenu(act->menu());
menu->deleteLater();
return menubar;
}
Here is how to test the suggested implementation:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
auto *menu = new QMenu("Menu", this);
// Add some menus
menu->addMenu(new QMenu("Menu 1", this));
menu->addMenu(new QMenu("Menu 2", this));
menu->addSeparator();
auto *submenu = new QMenu("Menu 3", this);
submenu->addActions(QList<QAction *>({new QAction("Action 1", this),
new QAction("Action 2", this),
new QAction("Action 3", this)}));
menu->addMenu(submenu);
setMenuBar(convertFromQMenu(menu));
setCentralWidget(new QWidget(this));
resize(300, 200);
}
I took the liberty to extend this example to switch between compact and extended form of the menus in the menu bar (not shown here). The full code is available on GitHub.
Result
As written, the given example produces the following result:

QWidgetAction Not Being Returned After QMenu Close

I have a QPushButton in a QWidgetAction in a QMenu. When the button is clicked, I want the action to trigger and the menu to close, returning which action was triggered. According to the docs, the widget itself must trigger the action directly.
Here's my code:
QMenu *menu = new QMenu();
QWidgetAction *widgetAction = new QWidgetAction(menu);
QPushButton *button = new QPushButton("Finish");
widgetAction->setDefaultWidget(button);
menu->addAction(widgetAction);
connect(button, SIGNAL(clicked()), widgetAction, SLOT(trigger()));
connect(widgetAction, SIGNAL(triggered()), menu, SLOT(close())); //Menu won't close without this
QAction* selectedAction = menu->exec(mapToGlobal(ui->pushButton->pos()));
if(selectedAction != NULL)
{
qDebug() << "no output from here";
}
However selectedAction always returns NULL. Regular QAction's added to the menu automatically close the menu and return pointers to themselves. Why doesn't QWidgetAction?
Thanks for your time!