Qt: Pass QGraphicsSceneContextMenuEvent from QGraphicsView - c++

I have derived from both QGraphicsView and QGraphicsRectItem. I overloaded the contextMenuEvent on both classes to provide popup menus. I want the QGraphicsView context menu when you click on white space the the QGraphicsItem popup menu when you click on an item.
At first implementation, I got the QGraphicsView popup no matter where I clicked. So I modified the contextMenuEvent as follows:
void CustomGraphicsView::contextMenuEvent(QContextMenuEvent* event)
{
if (QGraphicsItem *item = itemAt(event->pos())) {
MyRect* rect = dynamic_cast<MyRect*>(item);
QGraphicsSceneContextMenuEvent* context_event = dynamic_cast<QGraphicsSceneContextMenuEvent*>(event);
if (rect && context_event)
rect->contextMenuEvent(context_event);
}
else {
QMenu menu;
... create the QGraphicsView popup menu
}
}
The dynamic_cast for the QGraphicsSceneContextMenuEvent fails so I never call the contextMenuEvent for the rect. It won't compile if I just try to pass the event to the rect->contextMenu(), so I tried the cast.
What is the right way to do this?
This is a learning project to just create/move/rotate/delete 2D shapes using Qt. If someone wants to look at the whole thing, let me know.

OK, so I figured it out. Just make sure to pass the event through the base class method. Simple! This also works for the mousePressEvent(), mouseMoveEvent(), and mouseReleaseEvent functions.
void CustomGraphicsView::contextMenuEvent(QContextMenuEvent* event)
{
// if the event is on a GGraphicsItem just pass the event along
if (itemAt(event->pos())) {
QGraphicsView::contextMenuEvent(event);
}
else
{
QMenu menu;
... create popup for the CustomGraphicsView

Related

Adding a shortcut to QAction inside QGraphicsScene context menu

My QGraphicsScene subclass WorkspaceScene contains a variable for each action that it later uses inside the context menu. I have a function that sets up the actions and adds the shortcuts (which is called in the constructor of the class), and then I have a function that creates the context menu, which is called in the contextMenuEvent function I reimplemented.
Here's some relevant code:
// Constructor
WorkspaceScene::WorkspaceScene()
{
_setUpActions();
}
// ContextMenuEvent
void WorkspaceScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
_setUpItemMenu();
_itemContextMenu.exec(event->screenPos());
}
void WorkspaceScene::_setUpActions()
{
deleteAction = new QAction("Delete");
deleteAction->setShortcut(QKeySequence::Delete); // This should add the shortcut
connect(deleteAction, &QAction::triggered, this, &WorkspaceScene::deleteItemSlot);
}
void WorkspaceScene::_setUpItemMenu()
{
_itemContextMenu.clear();
_itemContextMenu.addAction(deleteAction);
}
This successfully sets up the actions and they work inside the context menu but the shortcut doesn't seem to work. What's the correct way of doing this?
I solved it by adding the QAction to the QGraphicsView parent of the QGraphicsScene.

Using Qt Undo Framework With QPainter

I am creating a drawing application with Qt and want to include undo and redo commands for whatever is drawn on a QImage. For this, I want to at least try and incorporate the Qt undo framework, since manually doing it will probably be above my skill level.
I have a MainWindow class where the main work is done and where I have the menu with undo and redo QActions.
The painting is done in a QWidget class called DrawingArea like this (PenShape is an enum of shapes):
void DrawingArea::drawLine(const QPoint &endPoint) {
QPainter painter(&image);
//some pen config code
switch (currentShape){
case PenShape::Polygon:
painter.drawPolygon(&endPoint, 5, Qt::OddEvenFill);
break;
case PenShape::Line:
painter.drawLine(lastPoint, endPoint);
break;
//other shapes and their draw methods..
}
update()
}
So as can be seen, the painter can draw in multiple shapes. I know I would have to somehow transfer this to a QUndoCommand class, but how? The drawline method is called only in DrawingArea's mouse event methods like this:
void DrawingArea::mousePressEvent(QMouseEvent *event){
if (event->button() == Qt::LeftButton) {
lastPoint = event->pos();
}
}
The QtUndoStack is located in MainWindow and the QActions are also there, but somehow I would have to connect the drawLine QtUndoCommand class and the mousePressEvent and mainWindow QAction with its connected slot method all together. This is what I don't know how to do.
Any help is appreciated!
Edit, here's the menu button actions:
m_undo = undoStack->createUndoAction(this, tr("&Undo"));
m_undo->setShortcuts(QKeySequence::Undo);
m_redo = undoStack->createRedoAction(this, tr("&Redo"));
m_redo->setShortcuts(QKeySequence::Redo);
I have no QUndoCommand classes because I'm unsure of how to create them yet.
In my MainWindow, I have a undoStack declared like this:
undoStack = new QUndoStack(this);

focusInEvent not called in QLineEdit subclass

I have a Qt/cpp code and display a subclassed QLineEdit. When double-clicking the QLineEdit, the focusInEvent is never called (launched in Maya).
void myQLineEditClass::focusInEvent(QFocusEvent *e)
{
MGlobal::displayInfo(MQtUtil::toMString(QString().sprintf("HERE")));
QLineEdit::focusInEvent(e);
}
HERE is never displayed, event if the focusInEvent is present in the .h protect part. Any idea how to get focusInEvents ?
Try the below. For several occasions that worked for me when focusInEvent did not.
void YourWidget::changeEvent(QEvent* event)
{
if (event->type() == QEvent::ActivationChange)
{
if (isActiveWindow())
{
// gaining the focus
}
else
{
// loosing the focus
}
}
// or whatever *parent* class call is
QWidget::changeEvent(event);
}
The event gets intercepted by the editor widget. See QItemDelegate::createEditor. The widget returned there will get it.
The issue was linked to the fact that the QLineEdit was in a QGraphicsView that was in another QGraphicsView. Bringing the QLineEdit to the higher-level QGraphicsView made it work.

Show/Hide of QMenu

I created the Start Menu by inheriting QMenu. I want to show and hide it using QPropertyAnimation in sliding style
Problem:
Show & hide are working fine when I call them explicitly(on click of start button). But when I click outside of start menu it hides instantly. Please suggest me what could be cause behind this:
My class is StartMenuUiClass which inherited from QMenu
mptrobj_animation is QPropertyAnimation object
void StartMenuUiClass::show()
{
this->raise();
disconnect(mptrobj_animation,SIGNAL(finished()),this,SLOT(this_hide()));
QMenu::show();
mptrobj_animation->setDuration(500);
mptrobj_animation->setStartValue(*mptrobj_startPosition);
mptrobj_animation->setEndValue(*mptrobj_endPosition);
mptrobj_animation->start();
}
void StartMenuUiClass::hide()
{
mptrobj_animation->setDuration(450);
mptrobj_animation->setStartValue(*mptrobj_endPosition);
mptrobj_animation->setEndValue(*mptrobj_startPosition);
connect(mptrobj_animation,SIGNAL(finished()),this,SLOT(this_hide()));
mptrobj_animation->start();
}
void StartMenuUiClass::this_hide()
{
this->lower();
emit work_Done();
QMenu::hide();
}
I think, if you click outside of your menu widget, it simply hides or closes without involving your StartMenuUiClass::hide() function. You can try to handle QMenu::hideEvent(QHideEvent *event) and/or QWidget::closeEvent(QCloseEvent *event). Something like this:
StartMenuUiClass::closeEvent(QCloseEvent *event) // the same for hideEvent()
{
this->hide();
event->accept();
}

Prevent a QMenu from closing when one of its QAction is triggered

I'm using a QMenu as context menu. This menu is filled with QActions. One of these QActions is checkable, and I'd like to be able to check/uncheck it without closing the context menu (and having to re-open it again to choose the option that I want).
I've tried disconnecting the signals emitted by the checkable QAction with no luck.
Any ideas? Thanks.
Use a QWidgetAction and QCheckBox for a "checkable action" which doesn't cause the menu to close.
QCheckBox *checkBox = new QCheckBox(menu);
QWidgetAction *checkableAction = new QWidgetAction(menu);
checkableAction->setDefaultWidget(checkBox);
menu->addAction(checkableAction);
In some styles, this won't appear exactly the same as a checkable action. For example, for the Plastique style, the check box needs to be indented a bit.
There doesn't seem to be any elegant way to prevent the menu from closing. However, the menu will only close if the action can actually trigger, i.e. it is enabled. So, the most elegant solution I found is to trick the menu by shortly disabling the action at the moment when it would be triggered.
Subclass QMenu
Reimplement relevant event handlers (like mouseReleaseEvent())
In the event handler, disable the action, then call base class' implementation, then enable the action again, and trigger it manually
This is an example of reimplemented mouseReleaseEvent():
void mouseReleaseEvent(QMouseEvent *e)
{
QAction *action = activeAction();
if (action && action->isEnabled()) {
action->setEnabled(false);
QMenu::mouseReleaseEvent(e);
action->setEnabled(true);
action->trigger();
}
else
QMenu::mouseReleaseEvent(e);
}
To make the solution perfect, similar should be done in all event handlers that may trigger the action, like keyPressEvent(), etc...
The trouble is that it is not always easy to know whether your reimplementation should actually trigger the action, or even which action should be triggered. The most difficult is probably action triggering by mnemonics: you would need to reimplement the complex algorithm in QMenu::keyPressEvent() yourself.
This is my solution:
// this menu don't hide, if action in actions_with_showed_menu is chosen.
class showed_menu : public QMenu
{
Q_OBJECT
public:
showed_menu (QWidget *parent = 0) : QMenu (parent) { is_ignore_hide = false; }
showed_menu (const QString &title, QWidget *parent = 0) : QMenu (title, parent) { is_ignore_hide = false; }
void add_action_with_showed_menu (const QAction *action) { actions_with_showed_menu.insert (action); }
virtual void setVisible (bool visible)
{
if (is_ignore_hide)
{
is_ignore_hide = false;
return;
}
QMenu::setVisible (visible);
}
virtual void mouseReleaseEvent (QMouseEvent *e)
{
const QAction *action = actionAt (e->pos ());
if (action)
if (actions_with_showed_menu.contains (action))
is_ignore_hide = true;
QMenu::mouseReleaseEvent (e);
}
private:
// clicking on this actions don't close menu
QSet <const QAction *> actions_with_showed_menu;
bool is_ignore_hide;
};
showed_menu *menu = new showed_menu ();
QAction *action = menu->addAction (new QAction (menu));
menu->add_action_with_showed_menu (action);
Here are couple ideas I've had... Not sure at all they will work tho ;)
1) Try to catch the Event by using the QMenu's method aboutToHide(); Maybe you can "Cancel" the hide process ?
2) Maybe you could consider using an EventFilter ?
Try to have a look at : http://qt.nokia.com/doc/4.6/qobject.html#installEventFilter
3) Otherwise you could reimplement QMenu to add your own behavior, but it seems a lot of work to me...
Hope this helps a bit !
(I started with Andy's answer, so thank you Andy!)
1) aboutToHide() works, by re-popping the menu at a cached position, BUT it can also enter an infinite loop. Testing if the mouse is clicked outside the menu to ignore re-opening should do the trick.
2) I tried an event filter but it blocks the actual click to the menu item.
3) Use both.
Here is a dirty pattern to prove that it works. This keeps the menu open when the user holds down CTRL when clicking:
# in __init__ ...
self.options_button.installEventFilter(self)
self.options_menu.installEventFilter(self)
self.options_menu.aboutToHide.connect(self.onAboutToHideOptionsMenu)
self.__options_menu_pos_cache = None
self.__options_menu_open = False
def onAboutToHideOptionsMenu(self):
if self.__options_menu_open: # Option + avoid an infinite loop
self.__options_menu_open = False # Turn it off to "reset"
self.options_menu.popup(self.__options_menu_pos_cache)
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.MouseButtonRelease:
if obj is self.options_menu:
if event.modifiers() == QtCore.Qt.ControlModifier:
self.__options_menu_open = True
return False
self.__options_menu_pos_cache = event.globalPos()
self.options_menu.popup(event.globalPos())
return True
return False
I say it is dirty because the widget here is acting as an event filter for both the button that opens the menu as well as the menu itself. Using explicit event filter classes would be easy enough to add and it would make things a little easier to follow.
The bools could probably be replaced with a check to see if the mouse is over the menu, and if not, don't pop it open. However, the CTRL key still has to be factored in for my use case, so it probably isn't far off a nice solution as it is.
When the user holds down CTRL and clicks on the menu, it flips a switch so the menu opens itself back up when it tried to close. The position is cached so it opens at the same position. There is a quick flicker, but it feels OK since the user knows they are holding a key down to make this work.
At the end of the day (literally) I already had the whole menu doing the right thing. I just wanted to add this functionality and I definitely didn't want to change to using a widget just for this. For this reason, I am keeping even this dirty patch for now.
Subclass QMenu and override setVisible. You can utilize activeAction() to know if an action was selected and the visible arg to see if the QMenu is trying to close, then you can override and call QMenu::setVisible(...) with the value you want.
class ComponentMenu : public QMenu
{
public:
using QMenu::QMenu;
void setVisible(bool visible) override
{
// Don't hide the menu when holding Shift down
if (!visible && activeAction())
if (QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier))
return;
QMenu::setVisible(visible);
}
};
Connect the QMenu.show to the action trigger. I know this is code for Qt5 (and in Python), but the principle should be back compatible.
from PyQt5 import QtWidgets
class CheckableMenu(QtWidgets.QMenuBar):
def __init__(self,parent=None):
super().__init__(parent)
self.menuObj=QtWidgets.QMenu("View")
self.addMenu(self.menuObj)
for i in ['Both','Even','Odd']: #my checkable items
t=QtWidgets.QAction(i,self.menuObj,checkable=True)
t.triggered.connect(self.menuObj.show)
self.menuObj.addAction(t)
Starting from baysmith solution, the checkbox didn´t work as I was expecting, because I was connecting to the action triggered(), rather than connecting to the checkbox toggled(bool). I´m using the code to open a menu with several checkboxes when I press a button :
QMenu menu;
QCheckBox *checkBox = new QCheckBox("Show Grass", &menu);
checkBox->setChecked(m_showGrass);
QWidgetAction *action = new QWidgetAction(&menu);
action->setDefaultWidget(checkBox);
menu.addAction(action);
//connect(action, SIGNAL(triggered()), this, SLOT(ToggleShowHardscape_Grass()));
connect(checkBox, SIGNAL(toggled(bool)), this, SLOT(ToggleShowHardscape_Grass()));
menu.exec(QCursor::pos() + QPoint(-300, 20));
For my use case, this works like a charm
I have been struggling with this for half a day.
There was many accepted answers on the net suggesting overriding setVisible function of the QMenu which did not work for me at all.
I found a solution based on this post (The last reply by the OP)
my C++ implementation for this matter is as follows:
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::MouseButtonRelease)
{
auto action = static_cast<QMenu*>(watched)->activeAction();
if (action && action->isCheckable())
{
action->trigger();
return true;
}
}
return QObject::eventFilter(watched, event);
}