I am building a Qt5 application that allows a user to draw a rubberband with his mouse over an image, to select certain area of the image for further processing.
I got my code to only allow the user to start drawing the rubberband, by subclassing a QLabel to a custom class (frame_displayer) which mousePressEvent() is overridden and thus be only invoked when the mouse press happens within the custom classed widget.
The problem is that when the initial click is inside the frame_displayer, mouseMoveEvent(), the function that I use to change the rubberband size accordingly, keeps getting called even when the mouse cursor has been dragged outside of the frame_displayer.
I have tried using leaveEvent() and enterEvent() to control a class boolean flag that codes inside mouseMoveEvent could rely on to know whether the cursor is still within the widget. However, both leaveEvent() and enterEvent() are only called while a mouse button is not being held, thus rendering them no use for constraining the rubberband.
Also, the underMouse() always return true, for a reason unknown to me.
Segment of frame_displayer.cpp
frame_displayer::frame_displayer(QWidget * parent) : QLabel(parent)
{
_rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
}
void frame_displayer::mousePressEvent(QMouseEvent *event)
{
_lastClickedBtn = event->button();
if (_lastClickedBtn == Qt::LeftButton)
{
_mouseOriginClickPoint = event->pos();
_rubberBand->setGeometry(QRect(_mouseOriginClickPoint, _mouseClickPoint));
_rubberBand->show();
}
}
void frame_displayer::mouseMoveEvent(QMouseEvent *event)
{
if(_rubberBand != nullptr)
{
if (this->underMouse())
{
if (_lastClickedBtn == Qt::LeftButton)
{
QPoint mouseCurrentPoint = event->pos();
_rubberBand->setGeometry(QRect(_mouseOriginClickPoint, mouseCurrentPoint).normalized());
}
}
}
}
void frame_displayer::mouseReleaseEvent(QMouseEvent *event)
{
_mouseOriginClickPoint = QPoint();
_lastClickedBtn = Qt::MidButton;
if(_rubberBand != nullptr)
{
_rubberBand->hide();
_rubberBand->clearMask();
}
}
void frame_displayer::leaveEvent(QEvent *event)
{
qDebug() << "Leaving";
}
void frame_displayer::enterEvent(QEvent *event)
{
qDebug() << "Entering";
}
Thanks in advance!
I think that's the expected behaviour. If you want to limit the extents of the rubber band then simply clamp them in the mouseMoveEvent override...
void frame_displayer::mouseMoveEvent(QMouseEvent *event)
{
if(_rubberBand != nullptr)
{
if (this->underMouse())
{
if (_lastClickedBtn == Qt::LeftButton)
{
QPoint mouseCurrentPoint = event->pos();
/*
* Clamp mouseCurrentPoint to the QRect of this widget.
*/
auto clamp_rect = rect();
mouseCurrentPoint.rx() = std::min(clamp_rect.right(), std::max(clamp_rect.left(), mouseCurrentPoint.x()));
mouseCurrentPoint.ry() = std::min(clamp_rect.bottom(), std::max(clamp_rect.top(), mouseCurrentPoint.y()));
_rubberBand->setGeometry(QRect(_mouseOriginClickPoint, mouseCurrentPoint).normalized());
}
}
}
}
Related
I'm developing a program that contains a MainWindow and a Widget called Diagrama from QWidget, which is the central widget of my mainwindow.
In this diagrama widget I have the ability to create a label in a the position that I clicked on the screen and the ability to drag an drop those same labels.
But now, I want to add an ability to get a clicked signal of the label every time that I click it.
I know that to enable the clicked signal function of a label, I have to create a class of a custom label, but when I do this and I replace the class QLabel to the customLabel class in the code, the drag and drop function stop working.
void Diagrama::dragEnterEvent(QDragEnterEvent *event)
{....}
void Diagrama::dragMoveEvent(QDragMoveEvent *event)
{....}
void Diagrama::dropEvent(QDropEvent *event)
{....}
void Diagrama::mousePressEvent(QMouseEvent *event)
{....}
I put this for just you guys know that I have the function to the whole process
And now I don't know what to do.
I though that there is a conflict of the function mousePressEvent of my customLabel class and the same function in my Diagrama class.
How can I solve it?
void Diagrama::dragMoveEvent(QDragMoveEvent *event)
{
if (event->mimeData()->hasFormat("application/x-dnditemdata")) {
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->acceptProposedAction();
}
} else {
event->ignore();
}
}
void Diagrama::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasFormat("application/x-dnditemdata")) {
QByteArray itemData = event->mimeData()->data("application/x-dnditemdata");
QDataStream dataStream(&itemData, QIODevice::ReadOnly);
QPixmap pixmap;
QPoint offset;
dataStream >> pixmap >> offset;
QLabel *newIcon = new QLabel(this);
newIcon->setPixmap(pixmap);
newIcon->move(event->pos() - offset);
newIcon->show();
newIcon->setAttribute(Qt::WA_DeleteOnClose);
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->acceptProposedAction();
}
} else {
event->ignore();
}
}
void Diagrama::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
painter.drawImage(0,0,*mImage);
e->accept();
}
void Diagrama::mousePressEvent(QMouseEvent *event)
{
if(modo=="trafo")
{
if(event->button()==Qt::LeftButton){
QLabel *child = static_cast<QLabel*>(childAt(event->pos()));
if (!child)
return;
QPixmap pixmap = *child->pixmap();
QByteArray itemData;
QDataStream dataStream(&itemData, QIODevice::WriteOnly);
dataStream << pixmap << QPoint(event->pos() - child->pos());
QMimeData *mimeData = new QMimeData;
mimeData->setData("application/x-dnditemdata", itemData);
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(pixmap);
drag->setHotSpot(event->pos() - child->pos());
QPixmap tempPixmap = pixmap;
QPainter painter;
painter.begin(&tempPixmap);
painter.fillRect(pixmap.rect(), QColor(127, 127, 127, 127));
painter.end();
child->setPixmap(tempPixmap);
if (drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction) == Qt::MoveAction) {
child->close();
} else {
child->show();
child->setPixmap(pixmap);
}
}
else if(event->button()==Qt::RightButton)
{
QLabel *child = new QLabel(this);
child->setPixmap(QPixmap(url_trafo));
child->move(event->x(),event->y());
child->show();
}
}
else if(modo=="linha")
{
if(event->button()==Qt::RightButton){
p_ini=event->pos();
drawing=true;
event->accept();}
else {
event->ignore();
drawing=false;
}
}
}
That is the responsible for the events of drag and drop and the event of appearing a label every time I click on the screen
I tried to create a customLabel class to emit a clicked signal every time I click in the label, but disable the drag and drop event
A click has to be registered on mouse release, not mouse press. A mouse press can evolve into different things, depending on what the user does next. Mouse press plus move or mouse press plus a long delay evolves into a drag operation.
So you need to override both mousePressEvent() as well as mouseReleaseEvent().
In your mousePressEvent() you need to save the time the press happened as well as the position. You then call QLabel::mousePressEvent() and pass it the event, so that QLabel can still detect drag operations.
In your mouseReleaseEvent() you need to compare the current time to the time of the press. If the difference is larger than QApplication::startDragTime, or the position of the mouse release compared to the mouse press position is further away than QApplication::startDragDistance, or the position is outside the label, then you don't treat the mouse release as a click. Finally, forward the event to the overriden QLabel::mouseReleaseEvent() so that the base class knows the mouse press event ended.
Here's an example ClickableQLabel implementation:
#include <QApplication>
#include <QElapsedTimer>
#include <QLabel>
#include <QPoint>
class ClickableQLabel: public QLabel
{
Q_OBJECT
public:
explicit ClickableQLabel(QWidget* parent = nullptr)
: QLabel(parent)
{}
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent* e) override
{
QLabel::mousePressEvent(e);
if (e->button() != Qt::LeftButton) {
rerurn;
}
mouse_press_time_.start();
mouse_press_pos_ = e->pos();
e->accept();
}
void mouseReleaseEvent(QMouseEvent* e) override
{
QLabel::mouseReleaseEvent(e);
if (!rect().contains(e->pos(), true)
|| e->button() != Qt::LeftButton
|| !mouse_press_time_.isValid()
|| mouse_press_pos_.isNull()
|| mouse_press_time_.hasExpired(QApplication::startDragTime())
|| (e->pos() - mouse_press_pos_).manhattanLength() >= QApplication::startDragDistance())
{
// Not a click.
return;
}
e->accept();
mouse_press_time_.invalidate();
mouse_press_pos_ = QPoint();
emit clicked();
}
private:
QElapsedTimer mouse_press_time_;
QPoint mouse_press_pos_;
};
If you now want something to happen when the label is clicked, connect the clicked() signal.
I write desktop application that works with map,
and I want to react on pan and long press events.
It is possible to use QGestureEvent on Qt/Linux/X11 with ordinary mouse?
I took Qt gesture example, it works on tablet,
but not reaction on press left mouse button and move (I expect that application recognizes it as tap or swipe event).
Then I added to Qt gesture example app.setAttribute(Qt::AA_SynthesizeTouchForUnhandledMouseEvents, true); at main and such code to imagewidget.cpp:
void ImageWidget::mousePressEvent(QMouseEvent *e)
{
e->ignore();
}
void ImageWidget::mouseReleaseEvent(QMouseEvent *e)
{
e->ignore();
}
void ImageWidget::mouseMoveEvent(QMouseEvent *e)
{
e->ignore();
}
this code still works on tablet, but again no reaction on mouse on Linux/X11.
Any way to enable qgesture on linux/x11, should I write my own gesture recognition
for mouse?
The official way to make gestures out of mouse events in Qt is deriving from the QGestureRecognizer class, which allows to listen to relevant mouse events, set gesture properties accordingly, then trigger the gesture (or cancel it).
Here follows an example for pan gestures only, just to give an idea of what has to be done.
Have a QGestureRecognizer subclass like this:
#include <QGestureRecognizer>
#include <QPointF>
class PanGestureRecognizer : public QGestureRecognizer
{
QPointF startpoint;
bool panning;
public:
PanGestureRecognizer() : panning(false){}
QGesture *create(QObject *target);
Result recognize(QGesture *state, QObject *watched, QEvent *event);
};
The create method has been overridden to return a new instance of our gesture of interest:
QGesture *PanGestureRecognizer::create(QObject *target)
{
return new QPanGesture();
}
The recognize method override is the core of our recognizer class, where events are passed in, gesture properties set, gesture events triggered:
QGestureRecognizer::Result PanGestureRecognizer::recognize(QGesture *state, QObject *, QEvent *event)
{
QMouseEvent * mouse = dynamic_cast<QMouseEvent*>(event);
if(mouse != 0)
{
if(mouse->type() == QMouseEvent::MouseButtonPress)
{
QPanGesture * gesture = dynamic_cast<QPanGesture*>(state);
if(gesture != 0)
{
panning = true;
startpoint = mouse->pos();
gesture->setLastOffset(QPointF());
gesture->setOffset(QPointF());
return TriggerGesture;
}
}
if(panning && (mouse->type() == QMouseEvent::MouseMove))
{
QPanGesture * gesture = dynamic_cast<QPanGesture*>(state);
if(gesture != 0)
{
gesture->setLastOffset(gesture->offset());
gesture->setOffset(mouse->pos() - startpoint);
return TriggerGesture;
}
}
if(mouse->type() == QMouseEvent::MouseButtonRelease)
{
QPanGesture * gesture = dynamic_cast<QPanGesture*>(state);
if(gesture != 0)
{
QPointF endpoint = mouse->pos();
if(startpoint == endpoint)
{
return CancelGesture;
}
panning = false;
gesture->setLastOffset(gesture->offset());
gesture->setOffset(mouse->pos() - startpoint);
return FinishGesture;
}
}
if(mouse->type() == QMouseEvent::MouseButtonDblClick)
{
panning = false;
return CancelGesture;
}
return Ignore;
}
}
Basically, we track mouse events, updating a couple of properties of our own (panning and startpoint) and the passed in gesture properties as well. For each mouse event type, we also return a QGestureRecognizer::Result . All other events are discarded (the method returns Ignore).
This code can be tested with the Image Gestures Example, though: just add the class to the project and this line in the ImageWidget constructor:
QGestureRecognizer::registerRecognizer(new PanGestureRecognizer());
This should let the user grab the picture and move it around, using a mouse.
Look into this image widget gestures example. (search for mouseDoubleClickEvent)
http://doc.qt.io/qt-5/qtwidgets-gestures-imagegestures-imagewidget-cpp.html
Based on that you need to reimplement the required mouse events.
MyWidget::MyWidget() {
---
---
}
bool MyWidget::event(QEvent *ev)
{
---
---
}
void MyWidget::mouseReleaseEvent(QMouseEvent *event)
{
}
void MyWidget::mouseMoveEvent(QMouseEvent *event)
{
}
And declare those two functions in header
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
I want to change my QGraphicsItem position by resize event over graphicsview.
I scaled position by newSize/oldSize but my items stay at the same position.
I don't know what wrong with my code or a better way to change my items position.
bool cameraItems::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::Resize) {
QResizeEvent *e = static_cast<QResizeEvent*>(event);
if(obj == ui->graphicsView) {
setFixedSizeForGraphicsView(e->size(), e->oldSize());
ui->graphicsView->scene()->setSceneRect(0, 0, e->size().width(), e->size().height());
ui->graphicsView->setSceneRect(0, 0, e->size().width(), e-->size().height());
}
}
QWidget::eventFilter(obj, event);
}
void cameraItems::setFixedSizeForGraphicsView(QSize size, QSize oldSize)
{
foreach (singleCamera *cam, m_cameras) {
prevImageSize = cam->imageSize();
QPointF ppos = cam->pos();
QPointF newPos = QPointF((ppos.x()/prevSize.width())*size.width(), (ppos.y()/prevSize.height())*size.height());
cam->setPos(newPos);
}
}
You can make your custom class which inherits from QGraphicsView and then reimplement resizeEvent( QResizeEvent *event ) like:
void MyView::resizeEvent(QResizeEvent *event)
{
QRectF rect = this->scene()->itemsBoundingRect();
fitInView(rect, ,Qt::KeepAspectRatio);
QGraphicsView::resizeEvent(event);
}
This way the view will always display the whole scene. I.e. if the window size is changed and the graphicsView is resized, The scene gets scaled and you can see everything appropriately.
I want to change the color of an ellipse when I move my mouse over it.
But I haven't found anything from reference and auto-complete from Qt Creator.
Do you guys know how to do it?
Some of my code:
void DrawingWidget::paintEvent(QPaintEvent *event) {
QPainter painter(this);
painter.fillRect(event->rect(), Qt::white);
for(int i = 0; i < pointList.size(); i++) {
if (pointList[i].x() >= 0 && pointList[i].y() >= 0)
painter.drawEllipse(pointList[i], 10, 10);
}
painter.drawLines(lineList);
m_mainWindow->updateCount();
}
Mouse press event handler:
void DrawingWidget::mousePressEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton
&& event->buttons() == Qt::LeftButton) {
// DO STUFFF
}
}
Mouse move event handler:
void DrawingWidget::mouseMoveEvent(QMouseEvent *event) {
if (m_mainWindow->getSelectedTool() == MainWindow::moveVertexTool) {
m_x = event->x();
m_y = event->y();
if (isPointNear(m_x, m_y)) {
//STUFF
}
update();
}
}
}
Now I just need a mouse OVER event (handler).
I think what you are looking for are enter and leave events.
Use QWidget::underMouse() for check if the widget is under the mouse cursor.
For example:
void IconButton::paintEvent(QPaintEvent *)
{
QPainter painter(this);
// Note isDown should really use the active state but in most styles
// this has no proper feedback
QIcon::Mode mode = QIcon::Disabled;
if (isEnabled()) {
if (isDown())
mode = QIcon::Selected;
else
mode = underMouse() ? QIcon::Active : QIcon::Normal;
}
QPixmap pixmap = icon().pixmap(iconSize(), mode);
QRect pixmapRect = QRect(0, 0, pixmap.width(), pixmap.height());
pixmapRect.moveCenter(rect().center());
if (m_autoHide)
painter.setOpacity(m_iconOpacity);
painter.drawPixmap(pixmapRect, pixmap);
}
This value is not updated properly during drag and drop operations.
I am currently able to load my image into a grahpics scene, and then again into a QGraphicsViewer.
I am able to implement a zoom feature by dtecting a QEvent::Wheel and then calling the graphicsViews's scale() function.
However, I can't seem to figure out how to get the pan functionality working. I basically want to detect when a mouse has clicked down on the image, and then move the image left, right, up or down along with the mouse.
As of right now, I basically have a MouseFilter class that is detecting events, and doing different things depending on the event type. I attached that listener to the QGraphicsView object
In case someone is wondering how to do it on their own, it's actually quite simple. Here's the code from my app:
class ImageView : public QGraphicsView
{
public:
ImageView(QWidget *parent);
~ImageView();
private:
virtual void mouseMoveEvent(QMouseEvent *event);
virtual void mousePressEvent(QMouseEvent *event);
virtual void mouseReleaseEvent(QMouseEvent *event);
bool _pan;
int _panStartX, _panStartY;
};
You need to store the start position of the drag, for example like this (I used the right button):
void ImageView::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::RightButton)
{
_pan = true;
_panStartX = event->x();
_panStartY = event->y();
setCursor(Qt::ClosedHandCursor);
event->accept();
return;
}
event->ignore();
}
Also, you need to clear the flag and restore the cursor once the button is released:
void ImageView::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::RightButton)
{
_pan = false;
setCursor(Qt::ArrowCursor);
event->accept();
return;
}
event->ignore();
}
To actually manage the drag, you need to override the mouse move event. QGraphicsView inherits a QAbstractScrollArea, and its scrollbars are easily accessible. You also need to update the pan position:
void ImageView::mouseMoveEvent(QMouseEvent *event)
{
if (_pan)
{
horizontalScrollBar()->setValue(horizontalScrollBar()->value() - (event->x() - _panStartX));
verticalScrollBar()->setValue(verticalScrollBar()->value() - (event->y() - _panStartY));
_panStartX = event->x();
_panStartY = event->y();
event->accept();
return;
}
event->ignore();
}
QGraphicsView has build-in mouse-panning support. Set correct DragMode and it will handle the rest. You do need the enable scroll bars for that to work.
neuviemeporte solution requires to subclass a QGraphicsView.
Another working drag implementation can be obtained without subclassing the view using eventFilter. If you don't need to customize other behaviors of the QGraphicsView, this technique will save you some work.
Let's say your GUI logic is maintained by a QMainWindow subclass and the QGraphicsView & QGraphicsScene are declared as a private members of this subclass. You would have to implement the eventFilter function as follows:
bool MyMainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == scene && event->type() == Event::GraphicsSceneMouseMove)
{
QGraphicsSceneMouseEvent *m = static_cast<QGraphicsSceneMouseEvent*>(event);
if (m->buttons() & Qt::MiddleButton)
{
QPointF delta = m->lastScreenPos() - m->screenPos();
int newX = view->horizontalScrollBar()->value() + delta.x();
int newY = view->verticalScrollBar()->value() + delta.y();
view->horizontalScrollBar()->setValue(newX);
view->verticalScrollBar()->setValue(newY);
return true;
}
}
return QMainWindow::eventFilter(obj, event);
}
To filter events from the QGraphicsScene, you'll have to install MyMainWindow as an eventFilter of the scene. Perhaps you could do this in the same function where you setup your GUI.
void MyMainWindow::setupGUI()
{
// along with other GUI stuff...
scene->installEventFilter(this);
}
You can extend this idea to replace the cursor with the drag "hand" as previously shown.