I'm trying to create a pseudo remote control widget, the remote control widget (an overridden QLabel) receives a screenshot (pixmap) of the widget to be controlled every 2 seconds or so through a REST api. I've overridden the QLabel mouse events and can store the positions of clicks on the overridden QLabel.
How can I convert these positions into mouse events and execute them on the "remote controlled" widget?
I've attached the cpp of the overridden QLabel and would appreciate any input.
#include "RemoteControlLabel.h"
#include <QDebug>
#include <QMouseEvent>
RemoteControlLabel::RemoteControlLabel(QWidget* parent) : QLabel(parent)
{
}
RemoteControlLabel::~RemoteControlLabel()
{
}
void RemoteControlLabel::mousePressEvent(QMouseEvent* event)
{
QPoint pos = event->pos();
qDebug() << "mouse pressed at " << pos;
}
void RemoteControlLabel::mouseReleaseEvent(QMouseEvent* event)
{
qDebug() << "Mouse released";
}
You can try this:
virtual void RemoteControlLabel::mousePressEvent(QMouseEvent* event) override
{
QPoint pos = event->pos();
qDebug() << "mouse pressed at " << pos;
// create new event on the stack
QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0);
// use sendEvent - it sends the event directly
QApplication::sendEvent(remotelyControlledWidget, &event);
// at the end of scope event will be automatically deleted, which is our intention
}
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 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());
}
}
}
}
Why QMouseEvent passing multiple events for single movement on QWidget?
I'm implementing simple dragging effect, but the result is not what I expected.
The following code will move the widget to new location but instantly move it back to the original location.
customwidget.h
#ifndef CUSTOMWIDGET_H
#define CUSTOMWIDGET_H
#include <QWidget>
#include <fstream>
class CustomWidget : public QWidget
{
Q_OBJECT
public:
explicit CustomWidget(QWidget *parent = nullptr);
~CustomWidget();
protected:
// define the painting agorithm to see the area of this widget
void paintEvent(QPaintEvent* ev);
// handle the pressing event to initialize the dragging algorithm
// and to track the start of moving event
void mousePressEvent(QMouseEvent* ev);
// implement the dragging algorithm
void mouseMoveEvent(QMouseEvent* ev);
// handle the releasing event to track the end of moving event
void mouseReleaseEvent(QMouseEvent* ev);
private:
std::ofstream fout; // open file "debug.txt"
QPoint prev; // to save the previous point of cursor.
};
#endif // CUSTOMWIDGET_H
customwidget.cpp
#include "customwidget.h"
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPainter>
#include <QBrush>
CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent)
{
// open file for output
fout.open("debug.txt");
// set the widget size and position
setGeometry(0, 0, 100, 100);
}
CustomWidget::~CustomWidget()
{
// close file when program ended
fout.close();
}
void CustomWidget::paintEvent(QPaintEvent *ev)
{
// draw the area with blue color
QPainter painter(this);
QBrush brush(Qt::GlobalColor::blue);
painter.setBrush(brush);
painter.setBackground(brush);
painter.drawRect(ev->rect());
}
void CustomWidget::mousePressEvent(QMouseEvent *ev)
{
ev->accept();
// debug output
fout << "pressed at (" << ev->x() << ',' << ev->y() << ')' << std::endl;
// initialize the dragging start point
prev = ev->pos();
}
void CustomWidget::mouseMoveEvent(QMouseEvent *ev)
{
ev->accept();
// get the cursor position of this event
const QPoint& pos = ev->pos();
// debug output
fout << "moved from (" << prev.x() << ',' << prev.y() << ") to ("
<< pos.x() << ',' << pos.y() << ')' << std::endl;
// calculate the cursor movement
int dx = pos.x() - prev.x();
int dy = pos.y() - prev.y();
// move the widget position to match the direction of the cursor.
move(geometry().x() + dx, geometry().y() + dy);
// update the cursor position for the next event
prev = pos;
}
void CustomWidget::mouseReleaseEvent(QMouseEvent *ev)
{
ev->accept();
fout << "released at (" << ev->x() << ',' << ev->y() << ')' << std::endl;
}
main.cpp
#include "customwidget.h"
#include <QApplication>
#include <QMainWindow>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// generate simple main window.
QMainWindow w;
// set the size of the window.
w.setGeometry(0, 0, 800, 800);
// generate the CustomWidget
CustomWidget *widget = new CustomWidget(&w);
// display the window containing the widget
w.show();
return a.exec();
}
And the result of debug.txt for one single movement of cursor is
CustomWidget pressed at (79,83)
CustomWidget moved from (79,83) to (79,83)
CustomWidget moved from (79,83) to (80,83)
CustomWidget moved from (80,83) to (79,83)
CustomWidget released at (80,83)
The result is moving the widget to new location for a little time then move it back to its original location.
The look of this program will almost looked like the widget is never being moved no mater how you dragging the widget.
My theory is the event manager pass the event when you moved the cursor. But
after the first event is processed, the manager passes another event related to the new location of the widget and the cursor current position. Then the process will move the widget back to where it was.
Although I can change the method of getting location of cursor from
ev->pos()
to
ev->globalPos()
to solve the problem.
But still want to know why the event manager act like that.
You have to do the following:
On mouse press event store the mouse cursor offset relative to the widget,
Move your widget so that the mouse cursor will always preserve the initial non zero offset,
Reset the offset on mouse release event.
The code (draft) might look like:
void CustomWidget::mousePressEvent(QMouseEvent* event)
{
// m_offset is a member variable of CustomWidget
m_offset = event->globalPos() - pos();
QWidget::mousePressEvent(event);
}
void CustomWidget::mouseMoveEvent(QMouseEvent* event)
{
if (!m_offset.isNull()) {
move(event->globalPos() - m_offset);
}
QWidget::mouseMoveEvent(event);
}
void CustomWidget::mouseReleaseEvent(QMouseEvent* event)
{
// Reset the offset value to prevent the movement.
m_offset = QPoint();
QWidget::mouseReleaseEvent(event);
}
I tried to implement a way for changing the background when an SVG button is pressed and reseting when it is released. My problem is that the mouseReleaseEvent is not called when I hide the QSvgWidget on which the mousePressEvent was called.
Here is my code:
SvgButton.cpp
#include "SvgButton.h"
SVGButton::SVGButton(QByteArray backgroundImage, QWidget *parent) :
QPushButton(parent)
{
this->init(backgroundImage);
}
SVGButton::SVGButton(QString backgroundImagePath, QWidget *parent) : QPushButton(parent)
{
SVGDom normalBackgroundImage(backgroundImagePath);
this->init(normalBackgroundImage.byteArray());
}
void SVGButton::init(QByteArray backgroundImage)
{
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
_backgroundImageWidget = new QSvgWidget();
_backgroundImageWidget->load(backgroundImage);
setLayout(new QHBoxLayout(this));
layout()->addWidget(_backgroundImageWidget);
this->setFlat(true);
}
void SVGButton::select()
{
this -> setStyleSheet("background-color:rgba(0, 0, 0, 10);");
}
void SVGButton::deselect()
{
this -> setStyleSheet("background-color:rgba(0, 0, 0, 0)");
}
int SVGButton::tag()
{
return _tag;
}
void SVGButton::setTag(int tag)
{
_tag = tag;
}
void SVGButton::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
SVGButton::~SVGButton()
{
delete _backgroundImageWidget;
}
and BaseNavigationButton.cpp
#include "BaseNavigationButton.h"
const int kButtonWidth = 140;
const int kButtonHeight = 70;
BaseNavigationButton::BaseNavigationButton(QString backgroundImagePath, QString pressedBackgroundImagePath, QWidget *parent)
: SVGButton(backgroundImagePath, parent)
{
this->setMinimumSize(kButtonWidth, kButtonHeight);
if (!pressedBackgroundImagePath.isNull())
{
SVGDom pressedBackgroundImage(pressedBackgroundImagePath);
_pressedBackgroundImageWidget = new QSvgWidget();
_pressedBackgroundImageWidget->load(pressedBackgroundImage.byteArray());
layout()->addWidget(_pressedBackgroundImageWidget);
_pressedBackgroundImageWidget->hide();
}
else
{
_pressedBackgroundImageWidget = NULL;
}
}
void BaseNavigationButton::mouseReleaseEvent(QMouseEvent * event)
{
qDebug() << "SVGButton::mouseReleaseEvent";
if (_pressedBackgroundImageWidget) {
_backgroundImageWidget->setVisible(false);
_pressedBackgroundImageWidget->setVisible(true);
//_backgroundImageWidget->show();
//_pressedBackgroundImageWidget->hide();
}
QPushButton::mouseReleaseEvent(event);
//emit released();
}
void BaseNavigationButton::mousePressEvent(QMouseEvent *event)
{
qDebug() << "SVGButton::mousePressEvent";
if(_pressedBackgroundImageWidget)
{
_backgroundImageWidget->setVisible(true);
_pressedBackgroundImageWidget->setVisible(false);
}
QPushButton::mousePressEvent(event);
// emit pressed();
}
BaseNavigationButton::~BaseNavigationButton()
{
if (_pressedBackgroundImageWidget)
{
delete _pressedBackgroundImageWidget;
}
}
The SVGDom basically just create a ByteArray from the SVG images. The code works, it is relatively correct, the only problem is that I described above.
When you hide a QWidget, it lose the focus, and a Widget --or some child widget-- must have the focus in order to the events work on it.
Try this simple example:
Press the mouse button when the cursor is over a button.
Move the pointer out of the button without release the mouse button.
Release the mouse button.
As you will see, this not will trigger the button clicked --clicked is a mousePressEvent followed by a mouseReleaseEvent-- event.
Hence, you cannot receive mouse buttons events from hidden objects.
What can I do to implement the "mouse pressed" style behaviour?
If by "mouse pressed style behaviour" you mean: "I want my widget style change when I press the mouse button".
Well, you can use the setStyleSheet function and applpy a CSS style to your widget. See Qt Style Sheets Examples
I am working on a C++ application with Qt 4.8 as the GUI framework:
A subclassed QGraphicsView rendering a subclassed QGraphicsScene containing one or more subclassed QGraphicsPixMapItem(s) in which I need to know the mouse cursor's relative pixel position.
Following How to show pixel position and color from a QGraphicsPixmapItem I subclass QGraphicsPixmapItem:
class MyGraphicsPixmapItem : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
public:
explicit MyGraphicsPixmapItem(QGraphicsItem *parent = 0);
signals:
public slots:
void mouseMoveEvent(QGraphicsSceneMouseEvent * event);
void mousePressEvent(QGraphicsSceneMouseEvent *event);
};
and implement constructor and handler:
MyGraphicsPixmapItem::MyGraphicsPixmapItem(QGraphicsItem *parent) : QGraphicsPixmapItem(parent)
{
qDebug() << "constructor mygraphicspixmapitem";
setCacheMode(NoCache);
setAcceptHoverEvents(true);
setFlag(QGraphicsItem::ItemIsSelectable,true);
}
void MyGraphicsPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent * event)
{
QPointF mousePosition = event->pos();
qDebug() << "mouseMoveEvent";
qDebug() << mousePosition;
QGraphicsPixmapItem::mouseMoveEvent(event);
}
I have also subclassed QGraphicsView and QGraphicsScene with respective mouseMoveEvent handlers that pass the event further down successfully:
void MyGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << "QGraphicsScene: mouseMoveEvent";
QGraphicsScene::mouseMoveEvent(event);
}
However, although MyGraphicsView is configured with ...
setMouseTracking(true);
setInteractive(true);
... MyGraphicsPixmapItem only executes the mouseMoveEvent handler when being clicked or after setting it as grabber:
void MyGraphicsPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
qDebug() << QString("item clicked at: %1, %2").arg( event->pos().x()).arg( event->pos().y() );
event->accept();
grabMouse();
}
I would like to know whether I am making a mistake or the solution given in the above-mentioned answer is flawed or incomplete. Further, I would like to know whether the mouseMoveEvent handler can be triggered without the QGraphicsPixmapItem being configured as 'grabber'. It is also acceptable if the configuration as grabber occurs automatically when the mouse first moves onto the item.
Here is a thread where a very similar problem seems to be related to the item's reference, but since I add the item to the scene like this:
thepixMapItem = new MyGraphicsPixmapItem();
scene->addItem(thepixMapItem);
I suppose this is not related.
The solution is to reimplement or filter the item's hoverMoveEvent, as mouseMoveEvent only triggers when receiving a mouse press:
If you do receive this event, you can be certain that this item also
received a mouse press event, and that this item is the current mouse
grabber.