rightclick event in Qt to open a context menu - c++

I have a segment of code that calls a mousePressEvent. I have the left-click output the coordinates of the cursor, and I have rightclick do the same, but I also want to have the rightclick open a context menu. The code I have so far is:
void plotspace::mousePressEvent(QMouseEvent*event)
{
double trange = _timeonright - _timeonleft;
int twidth = width();
double tinterval = trange/twidth;
int xclicked = event->x();
_xvaluecoordinate = _timeonleft+tinterval*xclicked;
double fmax = Data.plane(X,0).max();
double fmin = Data.plane(X,0).min();
double fmargin = (fmax-fmin)/40;
int fheight = height();
double finterval = ((fmax-fmin)+4*fmargin)/fheight;
int yclicked = event->y();
_yvaluecoordinate = (fmax+fmargin)-finterval*yclicked;
cout<<"Time(s): "<<_xvaluecoordinate<<endl;
cout<<"Flux: "<<_yvaluecoordinate<<endl;
cout << "timeonleft= " << _timeonleft << "\n";
returncoordinates();
emit updateCoordinates();
if (event->button()==Qt::RightButton)
{
contextmenu->setContextMenuPolicy(Qt::CustomContextMenu);
connect(contextmenu, SIGNAL(customContextMenuRequested(const QPoint&)),
this, SLOT(ShowContextMenu(const QPoint&)));
void A::ShowContextMenu(const QPoint &pos)
{
QMenu *menu = new QMenu;
menu->addAction(tr("Remove Data Point"), this,
SLOT(test_slot()));
menu->exec(w->mapToGlobal(pos));
}
}
}
I know that my problem is very fundamental in nature, and that 'contextmenu' is not properly declared. I have pieced together this code from many sources, and do not know how to declare something in c++. Any advice would be greatly appreciated.

customContextMenuRequested is emitted when the widget's contextMenuPolicy is Qt::CustomContextMenu, and the user has requested a context menu on the widget. So in the constructor of your widget you can call setContextMenuPolicy and connect customContextMenuRequested to a slot to make a custom context menu.
In the constructor of plotspace :
this->setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(ShowContextMenu(const QPoint &)));
ShowContextMenu slot should be a class member of plotspace like :
void plotspace::ShowContextMenu(const QPoint &pos)
{
QMenu contextMenu(tr("Context menu"), this);
QAction action1("Remove Data Point", this);
connect(&action1, SIGNAL(triggered()), this, SLOT(removeDataPoint()));
contextMenu.addAction(&action1);
contextMenu.exec(mapToGlobal(pos));
}

Related

Coordinates of QPushButton in QList

I have created a QList of QPushButton that I have assigned to a slot function.
But I would like to get coordinates of the button which is clicked in the list.
For example, pushbutton n°5 is clicked, it return me (25,150).
listButton = new QList<QPushButton*>;
//some code here
QPushButton *button = new QPushButton(QString::number(1),ui->widget);
button->setGeometry(50, 50, 30, 30);
button->setStyleSheet("background-color:red;");
QObject::connect(button, SIGNAL(clicked()), this, SLOT(selectP()));
listeButton->append(button);
//some code here
void selectP()
{
//I'd like to print here, coordinates of the button which has called "selectP()"
}
Sorry for my language, thanks in advance !
In you slot you can get the pointer of the button that had been clicked:
void selectP()
{
QPushButton *btn = qobject_cast<QPushButton *>(sender());
if (btn) {
qDebug() << "The coordinates are:" << btn->geometry();
}
}

Mapping QSlider::valueChanged signals in custom QWidget with multiple sliders

I have a custom QDialog with a set of custom sliders (i.e., QWidgets each consisting of a slider and an associated label) on it. In essence, this dialog is used to change the coordinates of an object in a 3D scene by adjusting the sliders for each dimension.
Currently, the QDialog itself stores a pointer to the scene object it modifies. Thus, the slot that takes care of object movement when a slider emits valueChanged is also part of the QDialog class. Since it has no way of knowing which slider was moved, the movement function (rather inefficiently) just loops through all of the sliders on the dialog, gathers their values, and assigns a new configuration to the 3D object.
Ideally, only the dimension that changed would have to be re-assigned when a slider is moved. So I tried using QSignalMapper to identify each slider with a numerical index. This would require the ability to send a valueChanged signal with two parameters: one identifying the sender slider, and one giving the new value itself. Unfortunately, as I learned here, QSignalMapper can't do this.
Another way to get the functionality I want would perhaps be to use the sender() method. But, according to the documentation, that's bad practice--it violates the principle of modularity.
I can think of a couple of other solutions: allow the custom slider class to store its parent dialog (seems bad in the same say that sender() is bad), or maybe even store the movable object itself as a static member of the custom slider class instead of (non-statically/as it is now) in the overall dialog.
Which of these approaches, if any, would be the best way to go here? What alternatives should I consider?
Possible solution is connect QSlider signal sliderReleased(), emitted when the user releases the slider with the mouse, with QSignalMapper map() and store sliders id with pointer on some list. When value has hanged, QDialog could emit another signal with information of slider id and new value.
QSignalMapper *mapper = new QSignalMapper(this);
connect(slider_0, SIGNAL(sliderReleased()), mapper, SLOT(map()));
mapper->setMapping(slider_0, 0);
tab_s[0] = slider_0;
connect(slider_1, SIGNAL(sliderReleased()), mapper, SLOT(map()));
mapper->setMapping(slider_1, 1);
tab_s[1] = slider_1;
connect(slider_2, SIGNAL(sliderReleased()), mapper, SLOT(map()));
mapper->setMapping(slider_2, 2);
tab_s[2] = slider_2;
connect(mapper, SIGNAL(mapped(int)),
this, SLOT(checkSlider(int)));
and in some slot:
void SomeDialog::checkSlider(int id)
{
emit valueChanged(id, tab_s[id]->value());
}
The ideal solution would be to subclass QSlider and re-emit the valueChanged() signal with added parameters (x/y/z axis). Let's say MySlider which constructs with given axis index (0/1/2):
class MySlider : public QSlider
{
Q_OBJECT
public:
MySlider(int axis, QWidget *parent);
signals:
void valueChanged(int axis, int value);
private slots:
void reemitValueChanged(int value);
private:
int m_axis;
};
MySlider::MySlider(int axis, QWidget *parent)
: QSlider(parent)
{
m_axis = axis;
connect(this, SIGNAL(valueChanged(int)),
this, SLOT(reemitValueChanged(int)));
}
void MySlider::reemitValueChanged(int value)
{
emit valueChanged(m_axis, value);
}
MySlider intercepts valueChanged(int) signal from QSlider, and emits its own signal valueChanged(int,int) with the axis index (0/1/2). You can use MySlider in the application now:
for (int i=0; i<3; i++) {
sliders[i] = new MySlider(i, this);
connect(sliders[i], SIGNAL(valueChanged(int,int)),
this, SIGNAL(updateScene(int,int)));
}
Of course, you'll have to arrange these sliders in a layout or something. Here's the source of this approach.
I think using the sender() method is fine.
I'd write something like this:
enum Axes
{
axisX,
axisY,
axisZ
}
class MyDialog : public QDialog
{
...
signals:
void valueChanged(int value, int type);
}
MyDialog::MyDialog(QWidget *parent) :
QDialog(parent)
{
...
connect(xSlider, SIGNAL(valueChanged(int)),
this, SLOT(onSliderChange(int));
connect(ySlider, SIGNAL(valueChanged(int)),
this, SLOT(onSliderChange(int));
connect(zSlider, SIGNAL(valueChanged(int)),
this, SLOT(onSliderChange(int));
}
void MyDialog::onSliderChange(int value)
{
int axis = -1;
QSlider *slider = dynamic_cast<QSlider*>(sender());
if (slider == xSlider)
{
axis = axisX;
}
else if (slider == ySlider)
{
axis = axisY;
}
else if (slider == zSlider)
{
axis = axisZ;
}
else
{
qWarning() << "Wrong sender";
}
if (axis != -1)
{
emit valueChanged(value, axis);
}
}
//signalMapper.h
//--------------
#ifndef SIGNALMAPPER_H
#define SIGNALMAPPER_H
#include <QWidget>
#include <QGridLayout>
#include <QSlider>
#include <QLabel>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
private:
QGridLayout* m_pGridLayoutMain;
QLabel* m_pLabel;
private slots:
void setLabelText(QWidget *pWidget);
};
#endif // SIGNALMAPPER_H
//signalMapper.cpp
//----------------
#include "signalMapper.h"
#include <QSignalMapper>
#include <QString>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
setMinimumSize(400, 200);
QSignalMapper* pSignalMapper = new QSignalMapper(this);
m_pGridLayoutMain = new QGridLayout(this);
m_pGridLayoutMain->setContentsMargins(10, 10, 10, 10);
m_pGridLayoutMain->setSpacing(10);
m_pLabel = new QLabel(this);
m_pLabel->setMinimumSize(150, 20);
m_pLabel->setAlignment(Qt::AlignCenter);
m_pLabel->setFrameStyle(QFrame::Box | QFrame::Sunken);
m_pGridLayoutMain->addWidget(m_pLabel, 0, 0);
for(int i=1; i < 10; i++)
{
QSlider* pSlider = new QSlider(this);
QString strObjName = "Slider " + QString().setNum(i);
pSlider->setObjectName(strObjName);
pSlider->setMinimum(0);
pSlider->setMaximum(100);
pSlider->setSingleStep(1);
pSlider->setOrientation(Qt::Horizontal);
pSlider->setValue(35);
connect(pSlider, SIGNAL(valueChanged(int)), pSignalMapper, SLOT(map()));
pSignalMapper->setMapping(pSlider, pSlider);
m_pGridLayoutMain->addWidget(pSlider, i, 0);
}
connect(pSignalMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setLabelText(QWidget*)));
}
Widget::~Widget()
{
}
void Widget::setLabelText(QWidget *pWidget)
{
QSlider* pSlider = dynamic_cast<QSlider*>(pWidget);
if(pSlider)
{
qDebug("Success");
m_pLabel->setText(pSlider->objectName()+" value changed to "+QString().setNum(pSlider->value()));
}
else
{
qDebug("Failure");
}
}

QGraphicsView / QGraphicsScene Timer Event not working

I'm trying to implement a basic qtimer in a qgraphicsview but can't seem to get it working.
Here's my main.cpp code:
int main(int argc, char * argv[]) {
QApplication app(argc, argv);//should stay on the stack
QGraphicsScene * scene = new QGraphicsScene();//this is the scene -- the canvas that holds everything!
// a view just adds stuff to the scene in that view port
QGraphicsView * view = new Game(scene);//create a new view
return app.exec();
}
And here is the view header ... note the qtimer and advance function
class View: public QGraphicsView {//inherits qwidget -- so we can make it full screen there
Q_OBJECT
public:
View(QGraphicsScene * _scene);//this is responsible for setting up the screen and instantiating several elements
~View();
protected://variables
QGraphicsScene * scene;
QTimer * timer;
protected://functions
int const int_height();//converts qreal to an int
int const int_width();
qreal const height();//
qreal const width();//this is the actual qreal floating point number
virtual void paintEvent(QPaintEvent * event) {};
void timerEvent(QTimerEvent * event) {cout << "HELLO << ads" << endl;};
virtual void keyPressEvent(QKeyEvent * event) {};
virtual void update() = 0;
void advance() { cout << "HELLO WORLD" << endl;}
private:
qreal _height;
qreal _width;
};
And finally, my view implementation constructor:
View::View(QGraphicsScene * _scene) : QGraphicsView(_scene) {
scene = _scene;//set the scene which is the parent over this
this->showMaximized();
QRectF geometry = this->contentsRect();
_height = geometry.height();
_width = geometry.width();
this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scene->setSceneRect(0, 0, this->int_width(), this->int_height());
// this->centerOn(qreal(0), qreal(0));
this->setGeometry(0, 0, _width, _height);
this->setFixedSize(_width, _height);
timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), scene, SLOT(advance()));
timer->start(1000);
timer->setInterval(100);
}
You need to declare your advance() function as a slot in the header file. Otherwise Qt does not know that this particular function is a slot:
protected slots:
void advance() { cout << "HELLO WORLD" << endl; }
Then, you are connecting the timeout signal to the advance() slot in the scene - but you declared it in your view. As you are currently in the view, you can use the this pointer to connect the signal to your view. Change your connect like this:
connect(timer, SIGNAL(timeout()), this, SLOT(advance()));
// ^^^^
[Edit] As a side node: You are creating a QGraphicsView subclass of type Game, but you showed the source code of View. This might be irrelevant if Game inherits from View though.

Signals not being detected for QTabWidget

My implementation of QTabWidget is not detecting its tabCloseRequested() and currentChanged() signals.
TileSheetManager::TileSheetManager(QWidget *parent)
: QTabWidget(parent)
{
int w = WIDTH;
int h = HEIGHT;
this->setMinimumSize(w, h);
this->setMaximumSize(w, h);
setTabsClosable(true);
setTabShape(QTabWidget::Rounded);
connect(this, SIGNAL(tabCloseRequested(int index)), this, SLOT(closeTileWidget(int index)));
connect(this, SIGNAL(currentChanged(int index)), this, SLOT(tabChanged(int index)));
}
qDebug() was not working for me, so I'm using a QMessageBox for this.
void TileSheetManager::closeTileWidget(int index)
{
QMessageBox msgBox;
msgBox.setText("Tab " + QString::number(index) + " removed!");
msgBox.exec();
TileWidget *t = (TileWidget *) widget(index) ;
t->deleteLater();
removeTab(index);
}
void TileSheetManager::tabChanged(int index)
{
QMessageBox msgBox;
msgBox.setText("Tab was Changed!");
msgBox.exec();
TileWidget *t;
for(int i = 0; i < count(); i++)
{
t = (TileWidget *) widget(i) ;
t->resetSetBrush();
}
}
Tabs aren't getting closed, selected brushes are not being reset, and I get no messages, so I'm concluding the signals aren't being picked up. Which is weird, because I have used similar code for a previous project, in which case it worked.
Don't use variable names in the connect function :
Note that the signal and slots parameters must not contain
any variable names, only the type.
The connection should be
connect(this, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTileWidget(int)));
connect(this, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));

QWidgetAction stays visible after trigger()

I have a a QWidgetAction which holds a QWidget composed of a QLineEdit and a QPushButton. Once the user press the button the QWidgetAction call the trigger slot.
Now I have a QMenu which I activate with exec. The problem is that even though trigger is called (I've connected it to a print function as well to check) the menu won't close.
Regular QActions works well.
Any idea why?
P.S. Googling this issue I came across people with the same problem, but no solutions.
Years old question, but still I have an answer, hope it helps anybody!
I will describe my complete solution which not only hides the menu but also manages the visual representation.
QWidgetAction subclass: MyClass.h
class MyClass : public QWidgetAction {
Q_OBJECT
public:
MyClass(QObject* parent);
bool eventFilter(QObject*, QEvent*) override;
signals:
void mouseInside();
void mouseOutside();
protected:
QWidget* createWidget(QWidget* parent) override;
private:
QWidget* w;
QWidget* border;
QFormLayout *form;
QHBoxLayout *mainLayout;
}
QWidgetAction subclass MyClass.cpp
QWidget* MyClass::createWidget(QWidget* parent) {
w = new QWidget(parent);
border = new QWidget(parent);
mainLayout = new QHBoxLayout(w);
layout = new QFormLayout();
border->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum));
border->setMaximumWidth(10);
border->setMinimumWidth(10);
border->setMaximumHeight(1000); //Anything will do but it needs a height
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
w->setLayout(mainLayout);
mainLayout->addWidget(border);
mainLayout->addLayout(layout);
layout->setContentsMargins(6, 11, 11, 11);
layout->setSpacing(6);
// Insert your widgets here, I used a QFormLayout
QLineEdit *l = new QLineEdit(w);
form->addRow("Test", l);
// I added a button to accept input
QPushButton* b = new QPushButton("Send", w);
connect(b, SIGNAL(clicked()), this, SLOT(trigger()));
layout->addWidget(b);
w->installEventFilter(this); // This is to avoid non button clicks to close the menu
return w;
}
bool MyClass::eventFilter(QObject*, QEvent* ev) {
if (ev->type() == QEvent::MouseButtonPress
|| ev->type() == QEvent::MouseButtonDblClick
|| ev->type() == QEvent::MouseButtonRelease) {
return true;
} else if (ev->type() == QEvent::Enter) {
border->setStyleSheet("background-color: #90c8f6;");
emit mouseInside();
} else if (ev->type() == QEvent::Leave) {
border->setStyleSheet("");
emit mouseOutside();
}
return false;
}
Finally to insert the QWidgetAction in a menu, in your code add the following:
QMenu *m = new QMenu(this);
MyClass *item = new MyClass(m);
connect(item, &QAction::triggered, [=] { m->hide(); YOUR CODE HERE}); // Add your action here
// This is to give a visual cue to your item, while deselecting the stuck
// action which was previously selected
connect(item, &MyClass::mouseInside, [=] { m->setActiveAction(nullptr); });
ui->yourButton->setMenu(m);
m->addAction(item);