How to convert QMenu to QMenuBar? - c++

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:

Related

Click for "more items..." option on context menu in QT C++

I have implemented a context menu project in QT by using C++ . I want to have maximum of 5 elements in the context menu to be shown at first. But it has a total of 15 elements and I want user to click on "More items..." or some kind of "Down" arrow button so that the context menu gets expanded to show more than 5 elements. Is it possible? If yes, I would appreciate some pointer in the right direction. Thanks.
Please note that the way I am trying to implement it, the context menu disappears on click of More items... So that is not what I want.
//Code
void myWidget::contextMenuEvent(QContextMenuEvent *event)
{
QMenu menu(this);
menu.addAction(new QAction("Action 1", this));
menu.addAction(new QAction("Action 2", this));
menu.addAction(new QAction("Action 3", this));
menu.addAction(new QAction("Action 4", this));
menu.addAction(new QAction("Action 5", this));
QAction *moreItems = new QAction("More items...", this);
QObject::connect(moreItems , SIGNAL(clicked()), this, SLOT(moreItemsClicked()));
menu.addAction(moreItems);
menu.exec(event->globalPos());
}
void myWidget::moreItemsClicked()
{
//Now what! Need help!
}
First of all, I would suggest you to not use macros SIGNAL and SLOT or if you want to use them - always check connection result. Your intension was to connect to clicked signal of QAction, but there is no such signal.
If you will use function pointer instead of SIGNAL - you will get compilation error which is always better then runtime assertion.
Regarding question itself, please consider simple multilevel menu:
But if you really want, you can extend menu in this way:
void contextMenuEvent(QContextMenuEvent* event)
{
auto menu = new QMenu(this);
menu->addAction(new QAction("Action 1", this));
menu->addAction(new QAction("Action 2", this));
menu->addAction(new QAction("Action 3", this));
menu->addAction(new QAction("Action 4", this));
menu->addAction(new QAction("Action 5", this));
QAction* moreItems = new QAction("More items...", this);
const auto connection_result = connect(moreItems, &QAction::triggered, [=]()
{
menu->removeAction(moreItems);
menu->addAction(new QAction("Action 6", this));
menu->addAction(new QAction("Action 7", this));
menu->exec(event->globalPos());
});
Q_ASSERT(connection_result);
Q_UNUSED(connection_result);
menu->addAction(moreItems);
menu->exec(event->globalPos());
}
You will be forced to reopen menu cause it will be hidden after click.
Also, menu must be created on heap to be available inside lambda. That force us to take care about memory leaks. To do that you can add this connection:
connect(menu, &QMenu::triggered, menu, &QMenu::deleteLater);
But in that case we need to rely on connections order since we expect that menu is still alive when lambda is executed, which is not the best. Or call QMenu::deleteLater from each action which is not good either.
So I would just recreate extended menu like this:
QMenu* CreateContextMenu(bool is_extended, QAction* extra_action, QWidget* parent)
{
auto menu = new QMenu(parent);
const auto connection_result = connect(menu, &QMenu::triggered, menu, &QMenu::deleteLater);
Q_ASSERT(connection_result);
Q_UNUSED(connection_result);
menu->addAction(new QAction("Action 1", this));
menu->addAction(new QAction("Action 2", this));
menu->addAction(new QAction("Action 3", this));
menu->addAction(new QAction("Action 4", this));
menu->addAction(new QAction("Action 5", this));
if (is_extended)
{
menu->addAction(new QAction("Action 6", this));
menu->addAction(new QAction("Action 7", this));
} else {
menu->addAction(extra_action);
}
return menu;
}
void contextMenuEvent(QContextMenuEvent* event)
{
QAction* moreItems = new QAction("More items...", this);
const auto connection_result = connect(moreItems, &QAction::triggered, [=]() {
auto extended_menu = CreateContextMenu(true, nullptr, this);
extended_menu->exec(event->globalPos());
});
Q_ASSERT(connection_result);
Q_UNUSED(connection_result);
auto menu = CreateContextMenu(false, moreItems, this);
menu->exec(event->globalPos());
}
You can also improve interface of CreateContextMenu, since first and second argument can't be present in same time, probably using std::variant. But I decided that it will be offtopic.

How to programmatically show dropdown-menu of ribbon button?

I have ribbon button with a set of sub-items added. Such items are displayed when the user clicks on the tiny arrow below the button. I'd like to display such dropdown-menu when the button itself is clicked. How can I do that?
My initial idea was to programmatically show the menu when the user clicks the button. I've been able to do the same on toolbars (here) but using a similar solution on ribbons creates an infinite recursion:
// ...
ON_COMMAND(ID_RIBBON_BUTTON, &MainFrame::OnButtonClicked)
// ...
CMFCRibbonPanel *panel = /* initialization */
CMFCRibbonButton *button = new CMFCRibbonButton(ID_RIBBON_BUTTON, "Caption");
panel->Add(button);
CMFCRibbonButton *item1 = new CMFCRibbonButton(ID_RIBBON_BUTTON, "Item 1");
button->AddSubItem(item1);
CMFCRibbonButton *item2 = new CMFCRibbonButton(ID_RIBBON_BUTTON, "Item 2");
button->AddSubItem(item2);
// ...
void MainFrame::OnButtonClicked()
{
if (auto button = static_cast<CMFCRibbonButton *>(m_ribbons.wndRibbonBar.FindByID(ID_RIBBON_BUTTON))) {
// button->OnClick({}); // <- causes infinite recursion
// What to do here?
}
}
The easiest way I've found so far is to use the protected method CMFCRibbonButton::OnShowPopupMenu. It implies deriving the CMFCRibbonButton class and changing the visibility of the method:
#include <afxribbonbutton.h>
class CMyMFCRibbonButton : public CMFCRibbonButton {
public:
using CMFCRibbonButton::CMFCRibbonButton;
virtual void OnShowPopupMenu() override {
CMFCRibbonButton::OnShowPopupMenu();
}
};
// ...
ON_COMMAND(ID_RIBBON_BUTTON, &MainFrame::OnButtonClicked)
// ...
CMFCRibbonPanel *panel = /* initialization */
CMyMFCRibbonButton *button = new CMyMFCRibbonButton(ID_RIBBON_BUTTON, "Caption");
panel->Add(button);
CMFCRibbonButton *item1 = new CMFCRibbonButton(ID_RIBBON_BUTTON, "Item 1");
button->AddSubItem(item1);
CMFCRibbonButton *item2 = new CMFCRibbonButton(ID_RIBBON_BUTTON, "Item 2");
button->AddSubItem(item2);
// ...
void MainFrame::OnButtonClicked()
{
if (auto button = static_cast<CMyMFCRibbonButton*>(m_ribbons.wndRibbonBar.FindByID(ID_RIBBON_BUTTON))) {
button->OnShowPopupMenu();
}
}

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;
}

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!

QTooltip for QActions in QMenu

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");