I have QGraphicsView, which has many QGraphicsItem. I am trying to create a right click menu on these QGraphicsItem. Right click menu has multiple options. But only 1st option works. It means, if I click on 2nd option, it does not work. If I change the sequence ( means 1st one will go to 2nd position, and 2nd one will come to 1st position ) then still 2nd one will not work.
bool myClass::eventFilter(QObject *watched, QEvent *event)
{
switch(event->type())
{
case QEvent::ContextMenu:
{
foreach(QGraphicsItem* pItem, _scene->items())
{
if(pItem->isUnderMouse())
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*> (event);
menu = new QMenu(this);
myMenu = menu->addMenu("Copy");
myMenu ->addAction(Name);
myMenu ->addAction(Address);
if(Name == menu->exec(mouseEvent->globalPos()))
{
// logic
}
if(Address == menu->exec(mouseEvent->globalPos()))
{
// logic
}
}
}
}
}
Always works only 1st mouse right click option. Why is so ?
The usual way to do something like this is to override the QGraphicsItem::mouseReleaseEvent() or QGraphicsItem::mousePressEvent() function of your item class.
This way, you won't have to do anything (no looping, etc...), it is already handled by the event loop.
Here you can find a simple example:
void MyItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
{
if(event->button() == Qt::RightButton)
{
QMenu my_menu;
// Build your QMenu the way you want
my_menu.addAction(my_first_action);
my_menu.addAction(my_second_action);
//...
my_menu.exec(event->globalPos());
}
}
From the Qt documentation:
Note that all signals are emitted as usual. If you connect a QAction to a slot and call the menu's exec(), you get the result both via the signal-slot connection and in the return value of exec().
You just need to QObject::connect() the QActions you added to the context menu to the proper slots (here goes the "logic") and the job is done.
If you prefer to check the returned value by yourself, you just have to get the returned QAction* once and for all (only one call to QMenu::exec()) and branch on it.
For example:
void MyItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
{
if(event->button() == Qt::RightButton)
{
QMenu my_menu;
// Build your QMenu the way you want
my_menu.addAction(my_first_action);
my_menu.addAction(my_second_action);
//...
QAction * triggered = my_menu.exec(event->globalPos());
if(triggered == my_first_action)
{
// Do something
}
else if(triggered == my_second_action)
{
// Do some other thing
}
//...
}
}
I would personnally prefer to stick with the signal-slot connections instead that manually handling the returned value, especially since each QAction is most likely to be already connected to its corresponding slot.
Related
I have set a large menu in event filter on right click with 45-50 actions
inside and I find that when I right click the response to show the menu is slow
I did try the same code with 5 actions in the menu and the response was fine.
Is there something wrong with this way of coding on a contex menu ?
eventFilter
bool Editor::eventFilter(QObject *o, QEvent *e)
{
Q_UNUSED (o);
QGraphicsSceneMouseEvent *me = (QGraphicsSceneMouseEvent*) e;
switch ((int) e->type()){
case QEvent::GraphicsSceneMousePress:{
switch ((int) me->button()){
case Qt::RightButton:{
QGraphicsItem *item = itemAt(me->scenePos());
showContextMenu(item->scenePos().toPoint());
return true;
}
//more cases here//
}
break;
}
}
return QObject::eventFilter(o, e);
}
showContextMenu
void Editor::showContextMenu(const QPoint &pos)
{
QGraphicsItem *item =itemAt(pos);
// Create main effe menu
effeMenu= new QMenu("Menu");
QString menuStyle(
"QMenu {"
"border:10px };"
//more code here
);
effeMenu->setStyleSheet(menuStyle);
AmpMenu=effeMenu->addMenu(QIcon(":/effectImg/img/effePng/amp.png"),"Amp");
Amp1 =AmpMenu->addAction(QIcon(":/effectImg/img/effePng/amp.png"),"Amp 1");
Amp2 =AmpMenu->addAction(QIcon(":/effectImg/img/effePng/amp.png"),"Amp 2");
CabMenu=effeMenu->addMenu(QIcon(":/effectImg/img/effePng/cab.png"),"Cab");
Cab1 =CabMenu->addAction(QIcon(":/effectImg/img/effePng/cab.png"),"Cab 1");
Cab2 =CabMenu->addAction(QIcon(":/effectImg/img/effePng/cab.png"),"Cab 2");
.
.
.
.
//45 actions more
connect(effeMenu, &QMenu::triggered,this,[this,&item](QAction * k){
menuSelection(k,item);
});
Instead of creating a new QMenu each time you call showContextMenu you could make it a member of the class and build it once. On the other hand it is not necessary to use a signal, you could simply use the exec() method of QMenu:
*.h
class Editor: ...{
...
private:
QMenu effeMenu;
}
*.cpp
Editor::Editor(...){
effeMenu.setTitle("Menu");
QString menuStyle(
"QMenu {"
"border:10px };"
//more code here
);
effeMenu.setStyleSheet(menuStyle);
AmpMenu=effeMenu.addMenu(QIcon(":/effectImg/img/effePng/amp.png"),"Amp");
Amp1 =AmpMenu->addAction(QIcon(":/effectImg/img/effePng/amp.png"),"Amp 1");
Amp2 =AmpMenu->addAction(QIcon(":/effectImg/img/effePng/amp.png"),"Amp 2");
CabMenu=effeMenu.addMenu(QIcon(":/effectImg/img/effePng/cab.png"),"Cab");
Cab1 =CabMenu->addAction(QIcon(":/effectImg/img/effePng/cab.png"),"Cab 1");
Cab2 =CabMenu->addAction(QIcon(":/effectImg/img/effePng/cab.png"),"Cab 2");
...
}
void Editor::showContextMenu(const QPoint &pos){
QGraphicsItem *item =itemAt(pos);
QAction *action = menu.exec(pos);
menuSelection(action, item);
}
There are two things you can do to improve speed:
1 - itemAt(pos) is costly, and you are doing it twice, one in the event, and one in the showContextMenu. From what I could understand from your code you don't need the item in the event, just in the showMenu.
2 - The menu creation that you are doing is expensive: all the actions have pixmaps. this allocs memory for the QPixmap, loads, execute, dumps. Because you told us that you use around 40 actions (and really, that's too much for a menu), this can get costly.
My advice:
Create a class for your menu, create one instance of it, add a setter for the current QGraphicsObject that your menu will work on, and always use that one instance.
In our application that uses Qt 4 and supports touch input, we use the QFileDialog with the options QFileDialog::DontUseNativeDialog and QFileDialog::ExistingFiles.
The first is needed because we set our own stylesheet and that does not work with the native dialog. The second is for needed for selecting multiple files, which is what we want to do.
The problem ist that one can not select multiple files with touch input in the QFileDialog, because we have no "shift" or "ctrl"-key available. In Windows the problem is solved by adding checkboxes to the items. QFileDialog has no checkboxes.
I tried to manipulate the QFileDialog to make it displays check boxes for the items, but I failed.
I tried to exchanged the QFileSystemModel that is used by the underlying QTreeView and QListView, but this breaks the signal-slot connections between the model and the dialog. I could not find a way to restore them because they are burried deep in the private intestants of the dialog.
At this moment the only solution I can imagine is writing a whole new dialog, but I would like to avoid the effort.
So is there a way to add checkboxes to the QFileDialog model views ?
Do you have another idea how selecting multiple files could be made possible?
Is the problem fixed in Qt 5? We want to update anyway.
Thank you for your Help.
As I failed to add checkboxes to the item views, I implemented a "hacky" work-around. It adds an extra checkable button to the dialog that acts as a "ctrl"-key. When the button is checked, multiple files can be selected. The solution is a little bit ugly, because it relies on knowing the internals of the dialog, but it does the job.
So here is the code for the header ...
// file touchfiledialog.h
/*
Event filter that is used to add a shift modifier to left mouse button events.
This is used as a helper class for the TouchFileDialog
*/
class EventFilterCtrlModifier : public QObject
{
Q_OBJECT;
bool addCtrlModifier;
public:
EventFilterCtrlModifier( QObject* parent);
void setAddCtrlModifier(bool b);
protected:
virtual bool eventFilter( QObject* watched, QEvent* e);
};
/*
TouchDialog adds the possibility to select multiple files with touch input to the QFileDialog.
This is done by adding an extra button which can be used as control key replacement.
*/
class QTOOLS_API TouchFileDialog : public QFileDialog
{
Q_OBJECT
EventFilterCtrlModifier* listViewEventFilter;
EventFilterCtrlModifier* treeViewEventFilter;
bool initialized;
public:
TouchFileDialog( QWidget* parent);
protected:
virtual void showEvent( QShowEvent* e);
private slots:
void activateCtrlModifier(bool b);
private:
void initObjectsForMultipleFileSelection();
};
with the implementation ...
// file touchfiledialog.cpp
#include "touchfiledialog.h"
EventFilterCtrlModifier::EventFilterCtrlModifier(QObject* parent)
: QObject(parent)
, addCtrlModifier(false)
{
}
void EventFilterCtrlModifier::setAddCtrlModifier(bool b)
{
addCtrlModifier = b;
}
bool EventFilterCtrlModifier::eventFilter(QObject* watched, QEvent* e)
{
QEvent::Type type = e->type();
if( type == QEvent::MouseButtonPress || type == QEvent::MouseButtonRelease)
{
if( addCtrlModifier)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(e);
// Create and post a new event with ctrl modifier if the event does not already have one.
if( !mouseEvent->modifiers().testFlag(Qt::ControlModifier))
{
QMouseEvent* newEventWithModifier = new QMouseEvent(
type,
mouseEvent->pos(),
mouseEvent->globalPos(),
mouseEvent->button(),
mouseEvent->buttons(),
mouseEvent->modifiers() | Qt::ControlModifier
);
QCoreApplication::postEvent(watched, newEventWithModifier);
return true; // absorb the original event
}
}
}
return false;
}
//#######################################################################################
TouchFileDialog::TouchFileDialog(QWidget* parent)
: QFileDialog(parent)
, listViewEventFilter(NULL)
, treeViewEventFilter(NULL)
, initialized(false)
{
}
void TouchFileDialog::showEvent(QShowEvent* e)
{
// install objects that are needed for multiple file selection if needed
if( !initialized)
{
if( fileMode() == QFileDialog::ExistingFiles)
{
initObjectsForMultipleFileSelection();
}
initialized = true;
}
QFileDialog::showEvent(e);
}
void TouchFileDialog::initObjectsForMultipleFileSelection()
{
// install event filter to item views that are used to add ctrl modifiers to mouse events
listViewEventFilter = new EventFilterCtrlModifier(this);
QListView* listView = findChild<QListView*>();
listView->viewport()->installEventFilter(listViewEventFilter);
treeViewEventFilter = new EventFilterCtrlModifier(this);
QTreeView* treeView = findChild<QTreeView*>();
treeView->viewport()->installEventFilter(treeViewEventFilter);
QGridLayout* dialogLayout = static_cast<QGridLayout*>(layout()); // Ugly because it makes assumptions about the internals of the QFileDialog
QPushButton* pushButtonSelectMultiple = new QPushButton(this);
pushButtonSelectMultiple->setText(tr("Select multiple"));
pushButtonSelectMultiple->setCheckable(true);
connect( pushButtonSelectMultiple, SIGNAL(toggled(bool)), this, SLOT(activateCtrlModifier(bool)));
dialogLayout->addWidget(pushButtonSelectMultiple, 2, 0);
}
void ZFFileDialog::activateCtrlModifier(bool b)
{
listViewEventFilter->setAddCtrlModifier(b);
treeViewEventFilter->setAddCtrlModifier(b);
}
The TouchFileDialog installs an event filter to the item views that will add a ControlModifier to the mouse events of the views when the corresponging button in the dialog is checked.
Feel free to post other solutions, because this is somewhat improvised.
In MouseReleaseEvent(QMouseEvent *e), is there a way to know which button was released without using a new variable ? I mean something like in the MousePressEvent(QMouseEvent *e) with e.buttons().
I tried e.buttons() in the releaseEvent it's not working (which is logical).
e is already a variable. Just use:
void mouseReleaseEvent(QMouseEvent *e)
{
if (e->button() == Qt::LeftButton) // Left button...
{
// Do something related to the left button
}
else if (e->button() == Qt::RightButton) // Right button...
{
// Do something related to the right button
}
else if (e->button() == Qt::MidButton) // Middle button...
{
// Do something related to the middle button
}
}
A switch statement also works. I prefer the series of if -- else if because they make it easier to handle evente modifiers, i.e., e->modifiers() in order to check for alt or control clicks. The series of if's is short enough not to create any burden on the program.
EDIT: Note that you should use the button() function, not its plural buttons() version. See the explanation in #Merlin069 answer.
The problem in the posted code is this: -
if(e->buttons() & Qt::LeftButton)
As the Qt documentation states for the release event: -
... For mouse release events this excludes the button that caused the event.
The buttons() function will return the current state of the buttons, so since this is a release event, the code will return false, as it's no longer pressed.
However, the documentation for the button() function states:-
Returns the button that caused the event.
So you can use the button() function here.
I have a qdialog, with a buttonbox at the bottom; Why isn't this slot not getting fired when a "signal" occurs? The code look like the following:
std::unique_ptr<MW::GenStd> box(new MW::GenStd(&tOut, &tIn));
box->ui.ChoiceButtons->addButton ("Ask",
QDialogButtonBox::AcceptRole );
box->ui.ChoiceButtons->addButton ("OverWrite",
QDialogButtonBox::AcceptRole );
box->ui.ChoiceButtons->addButton ("merge",
QDialogButtonBox::AcceptRole );
box->ui.ChoiceButtons->addButton ("Skip",
QDialogButtonBox::RejectRole );
QObject::connect(box->ui.ChoiceButtons, SIGNAL(clicked(QPushButton* b)), box.get(), SLOT(OnClick(QPushButton* b)));
return box->exec();
Where MW::GenStd is a dialog box (and ui.ChoicButtons a buttonbox). The modal dialog is correctly displayed - however it doesn't seem to interact at all.. Pressing the buttons doesn't fire the event. The slot is declared like the following:
public slots:
void OnClick(QPushButton* b) {
auto s(b->text());
if (s == "Merge") {
setResult(2);
} else if (s == "Overwrite") {
setResult(1);
} else if (s == "Skip") {
setResult(0);
} else if (s == "Ask") {
setResult(3);
}
}
};
(I know it's terribly to do such a string comparison here, but it's just as a quick mock up test to validate the buttons). But debugging shows the function isn't ever reached!
EDIT: as suggested looking at the output showed a culprit:
Object::connect: No such signal QDialogButtonBox::clicked(QPushButton*) in AskGUISupport.cpp:150
However that seems totally strange as the QDialogButtonBox does have a clicked signal? documentation
Do not use variable names in connect:
QObject::connect(box->ui.ChoiceButtons, SIGNAL(clicked(QPushButton*)),
box.get(), SLOT(OnClick(QPushButton*)));
QDialogButtonBox has a signal clicked ( QAbstractButton * button ) so you need to define a slot void OnClick(QAbstractButton* b) and connect to it. Use QAbstractButton, not QPushButton.
QDialogButtonBox class does not has signal
clicked(QPushButton*).
It has clicked ( QAbstractButton*) insted.
You should be very precise in signatures, when using signals/slots mechanisms. Any casts not allowed, because Qt uses strings internally to check signatures.
You should use clicked (QAbstractButton*) signature and adjust your slot to accpet QAbstractButton*. Make a slot
void OnClick(QAbstractButton* b);
void MainWindow::addRadioToUI()
{ int button_cunter=4;
while(!database.isEmpty())
{ button_cunter++;
QPushButton *one = new QPushButton("Play: "+name(get_r.getTrackId()));
one->setIcon(QIcon(":/images/play_button.png"));
one->setMaximumWidth(140);
one->setFlat(true);
QGroupBox* get_rGB = new QGroupBox("somethink");
QFormLayout* layout = new QFormLayout;
if(button_cunter%5 == 0){
layout->addWidget(one);
}
get_rGB->setLayout(layout);
scrollAreaWidgetContents->layout()->addWidget(get_rGB);
}
}
I have a few QPushButtons which are added automaticlly.
Is there a way to add "id attribute or sth else" to button and next know which button was clicked? I have different action for each button.
QApplication offers sender() which contains which object sent the signal. So you can do:
//slot, this could also be done in a switch
if(button[X] == QApplication::sender()){
doX();
}else if(button[Y] == QApplication::sender()){
doY();
}
http://doc.qt.io/qt-4.8/qobject.html#sender
QSignalMapper is pretty good for this type of thing.
You would define your slot like this for instance:
public slots:
void clicked(int buttonId); // or maybe trackId
Then add a QSignalMapper* member to your class and connect it to that slot:
signalMapper = new QSignalMapper(this);
connect(signalMapper, SIGNAL(mapped(int)),
this, SLOT(clicked(int)));
In the addRadioToUI, after creating your push button, do:
signalMapper.setMapping(one, button_cunter);
// or trackId if that's more practical
If all you need is a pointer to the object that triggered the signal though, you can use the static QOjbect::sender function in your slot to get a handle to that.
Use QButtonGroup. It takes id as a parameter when a button is added and provides the id to a slot when a button in the group is pressed.