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
Related
Trying to drag & drop label from QWidget to QGraphicsScene. I have made custom implementation from QLabel. Also QGraphicsView is accepting drops. The problem is that QGraphicsScene doens't get any events when dropping element to it.
QLabel implementation .cpp
void ItemLabel::mousePressEvent(QMouseEvent *ev)
{
if(ev->button() == Qt::LeftButton){
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setText("Test");
drag->setMimeData(mimeData);
drag->setPixmap(*this->pixmap());
drag->exec();
Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
}
}
Scene .cpp
void Scene::dropEvent(QGraphicsSceneDragDropEvent *event)
{
event->acceptProposedAction();
qDebug() << "drop";
}
void Scene::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
{
if(event->mimeData()->hasFormat("text/plain"))
event->acceptProposedAction();
}
What I need to do to get scene receive drops?
You should extend a QGraphicsView, set up a QGraphicsScene and then override the QGraphicsView drag/drop slots.
Take a look at this working sample:
MyGraphicsView::MyGraphicsView(QObject *p_parent):
QGraphicsView(),
m_scene(nullptr)
{
...
m_scene = new QGraphicsScene(rect(), p_parent);
setScene(m_scene);
setAcceptDrops(true);
}
void MyGraphicsView::dragEnterEvent(QDragEnterEvent *p_event)
{
p_event->acceptProposedAction();
}
void MyGraphicsView::dragMoveEvent(QDragMoveEvent *p_event)
{
p_event->acceptProposedAction();
}
void MyGraphicsView::dropEvent(QDropEvent *p_event)
{
p_event->acceptProposedAction();
}
If you do not acceptProposedAction() in dragEnter and dragMove, you will never get dropEvent triggered.
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());
}
}
}
}
Let's say I have a custom widget and add it to the main window in qt.
As you can see, the red area is the custom widget. What I want to do is when the mouse is pressed in the red area and moved, the whole window will move as well.
I know how to simply implement mousePressEvent and mouseMoveEvent; but when dealing with a window with the custom widget, I do not know how to move the whole window when mouse is pressed on the custom widget.
Also I want to mention that I only want the window movable when mouse is pressed and moved in the red area, and when mouse is pressed and moved in the rest part of the main window area, nothing will happen.
This is what my CustomWidget class looks like:
CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent)
{
setFixedSize(50, 50);
setStyleSheet("QWidget { background: red; }");
}
void CustomWidget::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter painter(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
}
void CustomWidget::mousePressEvent(QMouseEvent *event)
{
xCoord = event->x();
yCoord = event->y();
}
void CustomWidget::mouseMoveEvent(QMouseEvent *event)
{
move(event->globalX() - xCoord, event->globalY() - yCoord);
}
In case you wonder why I want to do this, in my app, I hid the title bar and drew a custom title bar by myself. But the window is not movable, so I want to make the whole window movable when mouse is pressed and moved on the title bar.
Hope I explained myself clearly.
To move the window from any widget it is necessary to be able to access the window, and for this we use the method window() that returns the top level, it is not necessary to separate the coordinates x() and y(), the following code implements the solution:
customwidget.h
#ifndef CUSTOMWIDGET_H
#define CUSTOMWIDGET_H
#include <QWidget>
class CustomWidget : public QWidget
{
Q_OBJECT
public:
explicit CustomWidget(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
private:
QPoint startPos;
};
#endif // CUSTOMWIDGET_H
customwidget.cpp
#include "customwidget.h"
#include <QMouseEvent>
#include <QPainter>
#include <QStyleOption>
CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent)
{
setFixedSize(50, 50);
setStyleSheet("QWidget { background: red; }");
}
void CustomWidget::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter painter(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
}
void CustomWidget::mousePressEvent(QMouseEvent *event)
{
startPos = event->pos();
QWidget::mousePressEvent(event);
}
void CustomWidget::mouseMoveEvent(QMouseEvent *event)
{
QPoint delta = event->pos() - startPos;
QWidget * w = window();
if(w)
w->move(w->pos() + delta);
QWidget::mouseMoveEvent(event);
}
If you are working on Windows, can use it:
#include "mywidget.h"
#include <windows.h>
#include <QWindow>
MyWidget::MyWidget(QWidget *parent)
: QWidget(parent)
{
}
MyWidget::~MyWidget()
{
}
void MyWidget::mousePressEvent(QMouseEvent* event)
{
if (event->buttons().testFlag(Qt::LeftButton))
{
HWND hWnd = ::GetAncestor((HWND)(window()->windowHandle()->winId()), GA_ROOT);
POINT pt;
::GetCursorPos(&pt);
::ReleaseCapture();
::SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, POINTTOPOINTS(pt));
}
}
void QHexWindow::mousePressEvent(QMouseEvent *event)
{
QLabel *child = static_cast<QLabel*>(childAt(event->pos()));
if (child!=mTitleBar) //mTitlebar is the QLabel on which we want to implement window drag
{
return;
}
isMousePressed = true;
mStartPos = event->pos();
}
void QHexWindow::mouseMoveEvent(QMouseEvent *event)
{
if(isMousePressed)
{
QPoint deltaPos = event->pos() - mStartPos;
this->move(this->pos()+deltaPos);
}
}
void QHexWindow::mouseReleaseEvent(QMouseEvent *event)
{
QLabel *child = static_cast<QLabel*>(childAt(event->pos()));
if (child!=mTitleBar)
{
return;
}
isMousePressed = false;
}
I have implemented the above in one of my github project https://github.com/VinuRajaKumar/AVR-HEX-Viewer where QLabel is used as TitleBar for the window.
I've done many searches (leading me to this and that) and adding a few lines to my classes
MainWindow.cpp
#include <QtGui/QDragEnterEvent>
#include <QtGui/QDragLeaveEvent>
#include <QtGui/QDragMoveEvent>
#include <QtGui/QDropEvent>
#include <QtCore/QMimeData>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
....
setAcceptDrops(true);
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
event->acceptProposedAction();
}
void MainWindow::dropEvent(QDropEvent *event)
{
qDebug() << "On Drop Event";
const QMimeData* mimeData = event->mimeData();
if (mimeData->hasUrls())
{
QStringList pathList;
QList<QUrl> urlList = mimeData->urls();
for (int i = 0; i < urlList.size() && i < 32; ++i)
{
pathList.append(urlList.at(i).toLocalFile());
}
if(openFiles(pathList))
event->acceptProposedAction();
}
}
void MainWindow::dragMoveEvent(QDragMoveEvent * event)
{
event->acceptProposedAction();
}
void MainWindow::dragLeaveEvent(QDragLeaveEvent* event)
{
event->accept();
}
But I can't drop a file onto my MainWindow (from the finder). It's not that my code crashes or does not compile, it's just that I literally can't. No reaction from the MainWindow, no highlight, nothing.
What am I missing?
I suspect you should also be overloading the dragMoveEvent: -
void QWidget::dragMoveEvent(QDragMoveEvent * event)
As the docs state: -
This event handler is called if a drag is in progress, and when any of the following conditions occur: the cursor enters this widget, the cursor moves within this widget, or a modifier key is pressed on the keyboard while this widget has the focus. The event is passed in the event parameter.
There's an example of a Qt drag and drop here. Specifically this is a good reference.