Clickable menu item with submenu in Qt - c++

I am writing in Qt 4.6. I am wondering if it's possible to achieve such a menu item, that it's possible to be triggered, but also has a submenu. Clicking it triggers associated action, hovering it causes submenu to appear.

Let me start by saying that this is not a good plan of attack. There are corner cases here that will take a rediculous amount of time and code to get just right, and will probably require per-operating system customization.
With that said, however, the actual implementation isn't too complicated. Just subclass the QMenu that you're making your submenu from, and override the event handlers, forcing the parent menu closed when a 'selection' is made. Something like the following basically works:
from PyQt4 import QtCore, QtGui
import sys
app = QtGui.QApplication(sys.argv)
widget = QtGui.QMainWindow()
widget.resize(250,150)
menu = widget.menuBar().addMenu("test")
class submenu(QtGui.QMenu): #Override the submenu class
def __init__(self,name):
QtGui.QMenu.__init__(self,name)
def mouseReleaseEvent(self,event): #catch mouseRelease Events
global menu
QtGui.QMenu.mouseReleaseEvent(self,event)
if not self.rect().contains(event.pos()):
print("Parent Selected")
menu.hide() #If the parent was selected, hide it
else: #Likely ignore these
print("Parent NOT Selected")
def c():
print("Sub-item selected")
cMenu = submenu("Sub-menu")
menu.addMenu(cMenu)
actionC = QtGui.QAction("sub-item",widget)
actionC.triggered.connect(c)
cMenu.addAction(actionC)
widget.show()
sys.exit(app.exec_())

This behavior is a bit confusing, but i am trying to develop a UI with as little clicking as possible. Although a bit unexpected, this behavior makes it a bit faster to use when you get used to it.
I haven't wrote that in my previous message, but i am writing in c++, and i have no idea about python... Anyway i managed to translate idea to c++, and it works but it's quite ugly... I found a bit better approach by looking through qt source (when i was asking this question I was hoping there is some better, "intended" method)
class MyMenu : public QMenu
{
Q_OBJECT
public:
MyMenu(QWidget* parent);
~MyMenu();
virtual void mouseReleaseEvent( QMouseEvent * event );
};
MyMenu::MyMenu(QWidget* parent):QMenu(parent)
{
}
MyMenu::~MyMenu()
{
}
void MyMenu::mouseReleaseEvent( QMouseEvent * event ){
QAction* act = menuAction();
if (act){
QMenu* men = act->menu();
act->setMenu(0);
QMenu::mouseReleaseEvent(event);
act->setMenu(men);
}else{
QMenu::mouseReleaseEvent(event);
}
}
The only disadvantage is that such a menu would react to clicking on all options with submenus, not only desired ones. Perhaps it would be a good idea to check if anything is connected to action's signals?
On my ubuntu it works. However i guess it wouldn't work on windows, where system manages menus exclusively (unless qt uses some windows with menu's look and feel, not system menus), but i am too lazy to instal windows just to check it ;)

void CustomMenu::mouseReleaseEvent( QMouseEvent* event )
{
QAction* action = menuAction();
// Filter out submenus. TODO: Is there a better way to do this?
if ( action && !rect().contains( event->pos() ) ) {
QMenu* menu = action->menu();
action->setMenu( 0 );
QMenu::mouseReleaseEvent( event );
emit customTriggeredSignal();
action->setMenu( menu );
}
else {
QMenu::mouseReleaseEvent( event );
}
}
Combining both helpful answers, from j_kubik and jkerian, this mouseReleaseEvent() in the top-level menu subclassing QMenu ignores unwanted invocations of submenus.
I will report back as to whether the approach is suitable on Windows.

Related

Call button click function from grandchild

I'm creating my first C++ wxWidgets application. I'm trying to create some kind of split button where the options are displayed in a grid. I have a custom button class which, when right-clicked on, opens a custom wxPopupTransientWindow that contains other buttons.
When I click on the buttons in the popup, I want to simulate a left click on the main button. I'm trying to achieve this through events, but I'm kinda confused.
void expandButton::mouseReleased(wxMouseEvent& evt)
{
if (pressed) {
pressed = false;
paintNow();
wxWindow* mBtn = this->GetGrandParent();
mBtn->SetLabel(this->GetLabel());
mBtn->Refresh();
wxCommandEvent event(wxEVT_BUTTON);
event.SetId(GetId());
event.SetEventObject(mBtn);
mBtn-> //make it process the event somehow?
wxPopupTransientWindow* popup = wxDynamicCast(this->GetParent(), wxPopupTransientWindow);
popup->Dismiss();
}
}
What is the best way to do this?
You should do mBtn->ProcessWindowEvent() which is a shorter synonym for mBtn->GetEventHandler()->ProcessEvent() already mentioned in the comments.
Note that, generally speaking, you're not supposed to create wxEVT_BUTTON events from your own code. In this particular case and with current (and all past) version(s) of wxWidgets it will work, but a cleaner, and guaranteed to also work with the future versions, solution would be define your own custom event and generate it instead.

Adding event to the context menu in QPlainTextEdit

This is my Context Menu after right click on QPlainTextEdit. I want to add function to load data from file in Context Menu. Can I? How?
Method 1: QPlainTextEdit::contextMenuEvent
You should override the QPlainTextEdit::contextMenuEvent as mentioned in the Qt documentation:
void MyQPlainTextEdit::contextMenuEvent(QContextMenuEvent *event)
{
QMenu *menu = createStandardContextMenu();
menu->addAction(tr("My Menu Item"));
//...
menu->exec(event->globalPos());
delete menu;
}
You can connect the QAction::triggered signal to your method (slot) to load the data or you can use one of the QMenu::addAction overloads, which allows you to specify a slot directly.
If you do not want to subclass QPlainTextEdit (to override contextMenuEvent), you can use event filtering in Qt.
Note that contextMenuEvent() is only called when contextMenuPolicy is not set (or set to its default value Qt::DefaultContextMenu)
Method 2: QWidget::customContextMenuRequested
As an alternative, you can use Qt's signal and slot mechanism to create the context menu when requested by the user.
The contextMenuPolicy property should be set to Qt::CustomContextMenu, in which case the QWidget::customContextMenuRequested signal is invoked whenever a context menu is requested by the user. This signal should be connected to your own slot, which should create the context menu as shown in the code above (Method 1).
Using MyQPlainTextEdit in Qt Designer
To use your MyQPlainTextEdit in a .ui file, you should implement it as a promoted QPlainTextEdit and use it in your .ui file instead of a regular QPlainTextEdit. See the Qt documentation for more information.
To be able to use your class in the Qt Designer, you should not forget to implement a constructor accepting a parent QWidget as is done in the AnalogClock example. Note that implementing such a constructor is always a good idea, because Qt typically manages ownership through a child-parent relationship.
Building on #m7913d answer.
The downside to the techniques is you must derive from the QPlainTextEdit class for a very minor extension. My preferred method, especially when using designer based widgets, is to add an eventFilter, and filter out the mouse event that is a mousebutton press
MyWidget::MyWidget(...)
{
...
ui->plainTextEdit->installEventFiler( this )
}
MyWidget::eventFilter( QObject * obj, QEvent * event )
{
if ( ( obj == ui->plainTextEdit )
&& ( event->type() = QEvent::MouseButtonPress )
&& ( dynamic_cast< QMouseEvent * >( event )->buttons() & Qt::MouseButton::RightButton )
{
// create menu
auto menu = ui->plainTextEdit->createStandardContextMenu();
// modify menu
menu->exec( mouseEvent->globalPos() );
delete menu;
return true;
}
return false;
}

Qt event when widget goes out of sight

Is there a way in Qt to handle situation when any widget of Window goes out of sight. I.e if a widget was in tab control and user have changed active tab, or if user just scrolls and widget goes offscreen, and also when it goes back on screen.
Is that possible to add some code to this two events?
Best if this can be done globally...
Is there a way in Qt to handle situation when any widget of Window goes out of sight. I.e if a widget was in tab control and user have changed active tab, or if user just scrolls and widget goes offscreen, and also when it goes back on screen.
The way the question asked makes one think that the widget show-hide-expose state changes need to be handled:
bool MyWidget::event(QEvent* pEvent)
{
if (pEvent->type() == QEvent::Show)
{
// event "shown"
}
else if (pEvent->type() == QEvent::Hide)
{
// event "hidden"
}
else if (pEvent->type() == QEvent::Expose)
{
// event "exposure changed"
// deal with QExposeEvent and evaluate the exposed region
// QExposeEvent* pExposeEvent = reinterpret_cast<QExposeEvent*>(pEvent);
}
return QWidget::event(pEvent);
}
Best if this can be done globally...
Event filter at the top level widget may solve that. Or you can override event() function for the top level widget but finding what exact widget was affected is another thing.
Refer to QExposeEvent description.

How do you close a QMenu when its QWidgetAction is triggered?

I have a QMenu for which I've created a QColorModel action widget (It's effectively just a QStandardItemModel). My desired behavior is that when a user clicks one of the colors in the model, that the action should trigger, and the menu close. However, it doesn't seem to do that, even when I trigger the action manually.
I've tried manually hiding the menu, but it's a kludge because it won't hide parent menus which th menu may be attached to.
Here's the relevant section of code:
// color menu
m_colorMenu = new QMenu("color", this);
m_colorView = new QColorView(m_colorMenu);
m_colorViewAction = new QWidgetAction(m_colorMenu);
m_colorViewAction->setDefaultWidget(m_colorView);
m_colorView->setModel(new QStandardColorModel);
connect(m_colorView, &QColorView::clicked, [&](QModelIndex index)
{
QColor color = qvariant_cast<QColor>(index.data(Qt::DecorationRole));
if (m_pen.color() != color)
{
m_pen.setColor(color);
drawIcon();
drawColorIcon();
update();
}
//this->hide(); // kludge, didn't close all parent menus
m_colorViewAction->trigger(); // doesn't seem to cause menu closure
});
m_colorMenu->addAction(m_colorViewAction);
EDIT
I've also tried adding something to the effect of:
QMenu* menu = m_colorMenu;
do
{
menu->close();
menu = dynamic_cast<QMenu*>(menu->parent());
} while (menu);
but it also is fragile/kludgey because it assumes a) all widgets are properly parented, and b) that all the parents are actually supposed to be menus. In my case, they aren't.
If the containing menus aren't in the parentage tree, and the menu you want to close isn't the top level menu, there is no easy way to do this. That said, there is:
THE NUCLEAR OPTION
Adding this to the end of the lambda function
auto topLevelWidgets = qApp->topLevelWidgets();
for (auto widget : topLevelWidgets)
{
QMenu* menu = dynamic_cast<QMenu*>(widget);
if (menu)
{
menu->close();
}
}
will cause ALL top level menus to close once the action is triggered. This is a relatively OK way to accomplish what you want because:
one of the top level menus will contain the menu in question, and
never say never, but I can't think of a single case where you would have (or want) more than one menu open at a time, so most likely the only open menu tree you would close is the intended one.

Qt UI testing: How to simulate a click on a QMenuBar item using QTest?

I am trying to simulate a mouse click on a QMenu item from a QMenuBar, for example clicking on "Save As" QAction using the QTestLib framework.
I am triyng this under Windows XP 32 bit and Qt 5.0.2.
Any Ideas?
Probably this question is not relevant for question owner, but I suppose it could be helpful for others.
Unlike QToolBar, QMenu doesn't have method widgetForAction. I found a simple workaround for this case. Try popup menu with QTest::mouseClick if nothing happens try to use QTest::keyClick(..., first_char_in_menu_tite, Qt::AltModifier). To simulate action execution you can navigate with Qt::Key_Down to action until you reach it and then press Qt::Key_Enter. I suppose following code can help you to understand
QMenu *menu = getMenu(mainWindow, menuName);
if (menu != nullptr) {
QTest::keyClick(mainWindow, menu->title().at(1).toLatin1(), Qt::AltModifier);
}
QList<QAction *> actions = menu->actions();
foreach (QAction *action, actions) {
if (action->objectName() == actionName) {
QTest::keyClick(menu, Qt::Key_Enter);
break;
}
QTest::qWait(1000);
QTest::keyClick(menu, Qt::Key_Down);
}
You should use the QTest::mouseClick function. It will simulate the click on any QWidget. I have found that trying to click on a QMenu that causes a blocking call will not work with a unit test.