QwtPlot mouse click event - c++

I need to get x and y coordinates of mouse click on QwtPlot drawing area (not the whole widget!). The problem is that there's only method bool event(QEvent*) that is called on every event. I've found some solutions (http://www.qtcentre.org/archive/index.php/t-9502.html), using QwtPlotPicker but that doesn't work for me, I'm using Qwt 6 and there are no such methods like setSelectionState().
What is other methods for achieving mouse click events on drawing area in QwtPlot?

There's been some changes in Qwt 6 comparing to 5.
Now we need to set state machine using QwtPlotPicker::setStateMachine(QwtPickerMachine) method. There are a few options (derived classes):
QwtPickerClickPointMachine
QwtPickerClickRectMachine
QwtPickerDragPointMachine
QwtPickerDragRectMachine
QwtPickerPolygonMachine
QwtPickerTrackerMachine
depending on our needs.
Next thing we need to do is connect() signal selected(...) from QwtPlotPicker with our custom slot where we can obtain x and y coordinates or other interesting data.

I'm capturing the mouse events using the mouse event methods in a subclass of QwtPlotCanvas, and using the conversion methods in QwtPlot to map the mouse event x and y coordinates to the values they represent in the plot.
Create a subclass of QwtPlotCanvas and override the mousePressEvent method like this.
void SpecialMapPlotCanvas::mousePressEvent (QMouseEvent* event) {
QWidget::mousePressEvent (event);
double x = plot() -> invTransform (plot() -> xBottom, event -> pos().x());
double y = plot() -> invTransform (plot() -> yLeft, event -> pos().y());
std::cout << "Values " << x << " " << y << "\n";
}
Then set the canvas on the QwtPlot by instantiating an object of this class and passing it to QwtPlot::setCanvas. Then SpecialMapPlotCanvas::plot() gives you a reference to the owning QwtPlot, and its invTransform methods can be used to convert the click coordinates to plot values. If you use the mouse events on QwtPlot itself you get the wrong answers because the mouse event coordinates here relate to the whole QwtPlot widget area (as you say), not just the canvas.

Related

Q3DSurface selected point signal

I am using QtDataVisualization (Q3DSurface in particular) to make a simple 3D surface graph.
The Q3DSurface supports selection of a point on the graph by showing a highlighted ball on the data point where the user has clicked. The selection pointer shows the coordinates of the point. It looks like this: surface with selected point
However, I'm not able to find a signal that is emitted when the selection happens. Having read through the documentation of Q3DSurface and QSurface3DSeries, I failed to find any corresponding signal. There is only a selectedPointChanged(const QPoint &position) in QSurface3DSeries, but it operates with a two-dimensional QPoint which is not suitable for the case.
What I am trying to do is store a history of selected points, that is why I need such a signal to keep track of previous coordinates. I tried looking into implementing a custom Q3DInputHandler, but not sure that it can resolve the issue. I would be grateful for any advice on the solution.
The desired signal is itemLabelChanged(const QString &label) in QAbstract3DSeries
It is emitted when a label, which represents the chosen point's coordinates, is changed. By connecting this signal to the slot, it is possible to retrieve the label's text (const QString &label parameter).
A good solution can be found here
Briefly, it is possible to get the 3D position by using const QSurfaceDataItem *QSurfaceDataProxy::itemAt(const QPoint &position) being position the value provided by the QAbstract3DGraph signal selectedElementChanged(QAbstract3DGraph::ElementType type).
For example:
Q3DSurface* surface = new Q3DSurface();
surface->setSelectionMode(QAbstract3DGraph::SelectionRowAndColumn);
QSurface3DSeries* surfaceSeries = new QSurface3DSeries();
surfaceSeries->dataProxy()->resetArray(dataArray); // QSurfaceDataArray* dataArray containing the surface data
surface->addSeries(surfaceSeries);
QObject::connect(surfaceSeries, &QSurface3DSeries::selectedPointChanged, this, [surfaceSeries](const QPoint &pos)
{
if ((pos.x() >= 0) && (pos.y() >= 0)) {
QVector3D vector = surfaceSeries->dataProxy()->itemAt(pos)->position();
qDebug() << vector;
}
});

Get mouse coordinates in QChartView's axis system

Is there a way to get mouse's coordinates on plotting area of a QChartView? Preferably in the axis units. The goal is to display mouse's coordinates while moving the mouse around on the plot so the user can measure plotted objects.
I couldn't find any built in function for this on QChartView, so I'm trying to use QChartView::mouseMoveEvent(QMouseEvent *event) to try and calculate the resulting position in the plotting area. The problem is I can't get any reference to the plot area's coordinate system.
I've tried using mapToScene, mapToItem and mapToParent and also the reverse mapFrom... on all objects I can grab a hold of to try to do this, but to no avail.
I've found that QChartView::chart->childItems()[2] is indeed the plotting area, excluding the axis and axis labels. I can then call QChartView::chart->childItems()[2]->setCursor(Qt::CrossCursor) to make a cross appear only on the plotting area and not on the adjacent objects. But still, nothing I try seems to make a correct reference to this object's coordinate system.
QChartView is simply a QGraphicsView with an embedded scene(). To get coordinates within any of the charts, you have to go through several coordinate transformations:
Start with the view widget coordinates
view->mapToScene: widget (view) coordinates → scene coordinates
chart->mapFromScene: scene coordinates → chart item coordinates
chart->mapToValue: chart item coordinates → value in a given series.
End with value coordinates in a given series.
The term "chart item" and "chart widget" are synonyms, since QChart is-a QGraphicsWidget is-a QGraphicsItem. Note that QGraphicsWidget is not a QWidget!
Implementing it like this works like a charm (thanks, Marcel!):
auto const widgetPos = event->localPos();
auto const scenePos = mapToScene(QPoint(static_cast<int>(widgetPos.x()), static_cast<int>(widgetPos.y())));
auto const chartItemPos = chart()->mapFromScene(scenePos);
auto const valueGivenSeries = chart()->mapToValue(chartItemPos);
qDebug() << "widgetPos:" << widgetPos;
qDebug() << "scenePos:" << scenePos;
qDebug() << "chartItemPos:" << chartItemPos;
qDebug() << "valSeries:" << valueGivenSeries;

How to appropriately get position of QGraphicsRectItem after drag-release?

I wanted to have an online monitoring system that could tell where the shape is currently, but am getting very weird coordinates of the item, also the dimensions of it get higher by 1 each time I create new one and drag it.
Initial position (map size is 751 by 751, checked by outputting to qDebug(), scene bound to yellow space) :
Dragging it to the left top corner.
As you can see in the beginning it was on (200;200), but after dragging it is on (-201;-196). After deleting it and creating new shape on the same position with the same properties, new shape can't be seen because it is outside of the map, which suggests that edits don't show correct data.
Here is the code of updating the edits:
void CallableGraphicsRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
QGraphicsRectItem::mouseReleaseEvent(event);
ptr->updateEdits(this);
}
Here is what I managed to cut down into updateEdits():
void MainWindow::updateEdits(QAbstractGraphicsShapeItem* item)
{
//stuff not related to scene
auto posReal = item->scenePos();
auto pos = posReal.toPoint();
//create QString from coordinates
QString coordinate;
coordinate.setNum(pos.x());
ui->leftXEdit->setText(coordinate);
coordinate.setNum(pos.y());
ui->upperYEdit->setText(coordinate);
//get width and height for rect, radius for circle
auto boundingRectReal = item->sceneBoundingRect();
auto boundingRect = boundingRectReal.toRect();
ui->widthEdit->setText(QString::number(boundingRect.width()));
//disables height edit for circles, not really relevant
if (!items[currentShapeIndex].isRect)
{
ui->heightEdit->setDisabled(true);
}
else
{
ui->heightEdit->setDisabled(false);
ui->heightEdit->setText(QString::number(boundingRect.height()));
}
}
Here is how I anchor the QGraphicsScene to the left top corner of the yellow area:
scene->setSceneRect(0, 0, mapSize.width() - 20, mapSize.height() - 20);
ui->graphicsView->setScene(scene);
How can I report the right data to the edits?
You're better off overriding the itemChange method and using the ItemPositionHasChanged notification. You have to set the ItemSendsGeometryChanges flag on the item so that it receives these notifications.
I'm not sure that your item's final position has been set when you're still in the mouseReleaseEvent method. Tracking it in itemChange will ensure that the data is valid, and this kind of thing is what it's for.
Also, note that "pos" is in the item's parent coordinates, and "boundingRect" is in the item's coordinate space. You should use "scenePos" and "sceneBoundingRect" if you want to be sure you're using scene coordinates. If the item doesn't have a parent, then "pos" and "scenePos" will return the same values, but "boundingRect" and "sceneBoundingRect" will generally differ.

How to prevent mouseMoveEvent on QCursor::setPos() using Qt?

I am currently developing on an image viewer application. In this application I have a so called "pan-zoom" feature. This means that, when holding a certain mouse button, the user can zoom the image by panning forth and back.
It works fine, but as the feature is used, the mouse (naturally) moves up and down on the screen and will at some point reach the screen borders, which will make it stop. Instead I would like a behaviour where the mouse remains stationary and only the image magnification changes.
I tried to achieve this by invoking QCursor::setPos inside the QWidget::mouseMoveEvent and reset the mouse to the initial position after I have processed the move. It works so far as that the mouse is staying nearly stationary (it's wiggling forth and back). However, this will cause the mouse move event to be called again effectively annulling the adjustment I just made. This will result in a "wiggling" effect. Every adjustment will immediately be reversed.
Here is a code snipped, so you get an idea of what I am doing:
void ImageView::mouseMoveEvent(QMouseEvent *e) {
//some code
if (_panZooming) {
//some code here
//doesn't work as expected because it invokes this event again
QCursor::setPos(mapToGlobal(_initialMousePosition.toPoint()));
}
}
Is there a way to prevent the mouse move event to happen when using QCursor::setPos?
Assuming you're not calling the base class mouseMoveEvent, you should accept the event to mark it as being handled. By default, they're accepted when you re-implement the event, but it's clearer to be explicit. Call e->accept( ).
It's also recommended that if you handle any of the mouse events, you should handle all, with the possible exception of mouse double click.
Here's an example of keeping the mouse still, though on OS X there's an occasional flicker which appears to be due to how Qt is handling the events
class MyWidget : public QWidget
{
void mousePressEvent(QMouseEvent* e)
{
m_pos = e->globalPos();
m_lastPos = m_pos;
QWidget::mousePressEvent(e);
}
void mouseMoveEvent(QMouseEvent* e)
{
// Calculate relative zoom factor
// scaled down ( / 10 ) for image zooming
m_zoomFactor += ((float)e->globalPos().y() - m_lastPos.y()) / 10;
QCursor::setPos(m_pos);
m_lastPos = m_pos;
e->accept();
qDebug() << m_zoomFactor << endl;
}
void mouseReleaseEvent(QMouseEvent* e)
{
QWidget::mouseReleaseEvent(e);
}
private:
QPoint m_pos;
QPoint m_lastPos;
float m_zoomFactor = 0; // C++ 11 initialisation
};
If you're not bothered at keeping the mouse stationary, take out the QCursor::setPos call and this will still receive move events when the cursor is outside the widget, whilst the mouse button is held down.
However, it may be a better user experience hiding the cursor when zooming.
I would have a flag to disable the event with will be false by default.
inside the event check if flag is false, then perform the zoom operation, set flag to true and reset cursor.
then the event will be called again and the flag will be true, so you set flag to false and you will be ready to handle the next event.
You just have to make sure you dont have two or more calls to the mouse event firing from the actual mouse before receiving the event from the setCursor call.
Don't use event->pos() in mouse events, use QCursor::pos() intead and check if it changed. Like this:
void MyWidget::mousePressEvent(QMouseEvent *)
{
mPrevPos=QCursor::pos();
mMoving=false;
}
void MyWidget::mouseMoveEvent(QMouseEvent *)
{
auto cursorPos=QCursor::pos();
if(mPressedPos==cursorPos){
return;
}
if(!mMoving
&& (cursorPos-mPrevPos).manhattanLength()>QApplication::startDragDistance()){
mMoving=true;
}
if(mMoving){
auto diff=cursorPos-mPrevPos;
// move something using diff
QCursor::setPos(mPrevPos);
}
}
void MyWidget::mouseReleaseEvent(QMouseEvent *)
{
mMoving=false;
}
void MyWidget::leaveEvent(QEvent *)
{
mMoving=false;
}

Dynamically add QGraphicsEllipseItems

Basically, each time I click on a graphicsview, I'd like a new QGraphicsEllipseItem to appear. The number is up to the user. Also, I'd like to query all of them in the end with pos() to get all their positions. The number of ellipses is not known before hand and their positions are movable by the ItemIsMovable flag. Anyone know how could I do this?
I could make an array of pointers to the graphicsitem class but that would possibly waste memory and limit the number of ellipses I can make. Thanks.
You can add as much items as you want to the scene (as long as there is free memory space, of course):
myGraphicsScene->addEllipse(rect, pen, brush);
In order to add items for each click, reimplement the mousePressEvent of the QGraphicsView:
void MyGraphicsView::mousePressEvent(QMouseEvent *e)
{
int rx = 10; // radius of the ellipse
int ry = 20;
QRect rect(e->x() - rx, e->y() - ry, 2*rx, 2*ry);
scene()->addEllipse(rect, pen, brush);
// call the mousePressEvent of the super class:
QGraphicsView::mousePressEvent(e);
}
You don't have to store the pointers by yourself. When you want to query some piece of information of all items in the scene, just loop over the list of items provided by the scene:
foreach(QGraphicsItem *item, myGraphicsScene->items())
qDebug() << "Item geometry =" << item->boundingRect();
(or for the position only: item->pos())
If you want to query a piece of information of a subclass of QGraphicsItem, you can cast the item to your type using Qt's QGraphicsItem casting mechanism, which returns a null pointer if the item isn't of the requested type. After checking the pointer to be not null, you can access your own member:
foreach(QGraphicsItem *item, myGraphicsScene->items())
{
MyDerivedItem *derivedItem = qgraphicsitem_cast<MyDerivedItem*>(item);
if(derivedItem) // check success of QGraphicsItem cast
qDebug() << derivedItem->yourCustomMethod();
}