How to mix mouseEnterEvent and mouseMove? - c++

in my application I try to connect nodes with lines. I use a QGraphicsView with a QGraphicsScene and my own QGraphicsItems. Now if I click on an item I want to draw a line to another node. To give a visual feedback, the goal should change color if the mouse hovers over the goal. The basics works so far, but my problem is that if I drag a line with the mouse (via mouseMoveEvent), I do not get any hoverEvents any more. I replicated the behaviour with this code:
Header File:
#pragma once
#include <QtWidgets/Qwidget>
#include <QGraphicsItem>
#include <QGraphicsScene>
class HaggiLearnsQt : public QWidget
{
Q_OBJECT
public:
HaggiLearnsQt(QWidget *parent = Q_NULLPTR);
};
class MyScene : public QGraphicsScene
{
public:
MyScene(QObject* parent = 0);
void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent);
};
class MyItem : public QGraphicsItem
{
public:
MyItem(QGraphicsItem* parent = Q_NULLPTR);
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event);
bool mouseOverItem;
};
Implementation:
#include "HaggiLearnsQt.h"
#include <QMessageBox>
#include <QFrame>
#include <QHBoxLayout>
#include <QGraphicsView>
MyScene::MyScene(QObject* parent)
{}
void MyScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
QGraphicsScene::mouseMoveEvent(mouseEvent);
}
MyItem::MyItem(QGraphicsItem* parent) : mouseOverItem(false)
{
setAcceptHoverEvents(true);
}
QRectF MyItem::boundingRect() const
{
return QRectF(-50, -50, 50, 50);
}
void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QBrush b = QBrush(Qt::black);
if(mouseOverItem)
b = QBrush(Qt::yellow);
painter->setBrush(b);
painter->drawRect(boundingRect());
}
void MyItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
mouseOverItem = true;
QGraphicsItem::hoverEnterEvent(event);
}
void MyItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
mouseOverItem = false;
QGraphicsItem::hoverLeaveEvent(event);
}
HaggiLearnsQt::HaggiLearnsQt(QWidget *parent)
: QWidget(parent)
{
QHBoxLayout* layout = new QHBoxLayout(this);
MyScene* graphicsScene = new MyScene();
QGraphicsView* graphicsView = new QGraphicsView();
graphicsView->setRenderHint(QPainter::RenderHint::Antialiasing, true);
graphicsView->setScene(graphicsScene);
layout->addWidget(graphicsView);
graphicsView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
graphicsView->setMinimumHeight(200);
graphicsView->setMinimumWidth(200);
graphicsView->setStyleSheet("background-color : gray");
MyItem* myitem = new MyItem();
myitem->setPos(50, 50);
graphicsScene->addItem(myitem);
}
And the default main.cpp:
#include "HaggiLearnsQt.h"
#include <QtWidgets/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
HaggiLearnsQt w;
w.show();
return a.exec();
}
If you run the code, a box appears in the middle of the window. If you hover over the box, it changes color. Now try to klick outside the box and drag wiht pressed button into the box. The box does not receive a hover and does not change color.
So my question is: Can I somehow change the item while I move the mouse with a pressed button?

You can get the hovered item passing mouseEvent->scenePos() to the QGraphicsScene::itemAt method inside the scene mouse move event handler.
Have a pointer to a MyItem instance, in MyScene:
class MyScene : public QGraphicsScene
{
MyItem * hovered;
//...
initialize it to zero in MyScene constructor:
MyScene::MyScene(QObject* parent)
{
hovered = 0;
}
then use it to track the current highlighted item (if there's one):
void MyScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
if(mouseEvent->buttons())
{
QGraphicsItem * item = itemAt(mouseEvent->scenePos(), QTransform());
MyItem * my = dynamic_cast<MyItem*>(item);
if(my != 0)
{
qDebug() << mouseEvent->scenePos();
if(!my->mouseOverItem)
{
my->mouseOverItem = true;
my->update();
hovered = my;
}
}
else
{
if(hovered != 0)
{
hovered->mouseOverItem = false;
hovered->update();
hovered = 0;
}
}
}
QGraphicsScene::mouseMoveEvent(mouseEvent);
}
The line if(mouseEvent->buttons()) at the beginning prevents the check to be performed if no mouse button is held.
Don't forget to initialize mouseOverItem to false in MyItem constructor:
MyItem::MyItem(QGraphicsItem* parent) : mouseOverItem(false)
{
setAcceptHoverEvents(true);
mouseOverItem = false;
}

Related

Slide animated text on hover

.h
class myButton : public QPushButton
{
Q_OBJECT
public:
QPropertyAnimation* anim;
struct WidgetPos { int x = 0; int y = 0; int w = 0; int h = 0; };
WidgetPos wp;
void CreateAnimation(QByteArray propertyName)
{
if (propertyName == "geometry")
{
anim = new QPropertyAnimation(this, propertyName);
this->anim->setDuration(100);
this->anim->setEasingCurve(QEasingCurve::Linear);
this->wp.x = this->x();
this->wp.y = this->y();
this->wp.w = this->width();
this->wp.h = this->height();
}
}
myButton(QWidget* parent = 0) : QPushButton(parent) {}
bool eventFilter(QObject* obj, QEvent* event)
{
if (event->type() == QEvent::Enter)
{
if (!this->wp.x)
this->CreateAnimation("geometry");
this->anim->stop();
this->anim->setStartValue(
QRect(this->x(), this->y(), this->width(), this->height()));
this->anim->setEndValue(
QRect(this->x(), this->y(), (this->wp.w + 200) - this->width(), this->height()));
this->anim->start();
}
else if (event->type() == QEvent::Leave)
{
this->anim->stop();
this->anim->setStartValue(
QRect(this->x(), this->y(), (this->wp.w + 200) - this->width(), this->height()));
this->anim->setEndValue(
QRect(this->wp.x, this->wp.x, this->wp.w, this->wp.h));
this->anim->start();
}
return QWidget::eventFilter(obj, event);
}
};
.cpp
QtWidgetsApplication::QtWidgetsApplication(QWidget * parent)
: QMainWindow(parent)
{
ui.setupUi(this);
QPushButton* btn = new myButton(this);
btn->setGeometry(100, 100, 50, 40);
btn->setStyleSheet(R"(QPushButton {
background-image: url(:/tutorial.png);
background-repeat: no-repeat; }
)");
QLabel* labl = new QLabel(btn);
labl->setObjectName("label");
labl->setGeometry(32, 0, btn->width() + 32, btn->height());
labl->setText("Hello World");
labl->setAlignment(Qt::AlignCenter);
labl->show();
btn->installEventFilter(btn);
return;
}
So far what I did result on:
If I move the mouse on it so fast it becomes messy, and the "closing" animation <= isn't working.
I'm struggling with the calculation of the animation QRect and handling it when there's an animation already running.
The goal is to create a smooth animation effect similar to see in this gif:
I think the reason for the issue you are having is because when you are leaving the widget you set the start animation to the maximum width the button could take instead of starting it from the current width. I've implemented my own QPushButton subclass in the following way which seems to achieve the result you need. Instead of creating an event filter, I'll just override the enter and leave event. We'll also need to update the initial geometry every time the widget is moved or resized (outside of the animation), so I'm overriding the move and resize event as well.
// MyButton.h
class MyButton : public QPushButton
{
public:
MyButton(QWidget* parent = nullptr);
~MyButton() = default;
protected:
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent* event) override;
void moveEvent(QMoveEvent *event) override;
void resizeEvent(QResizeEvent* event) override;
private:
QPropertyAnimation* m_animation;
QRect m_init_geometry;
double m_duration;
double m_extension;
};
Here is the implementation:
// MyButton.cpp
MyButton::MyButton(QWidget* parent)
: QPushButton(parent)
, m_animation(nullptr)
, m_init_geometry()
, m_duration(200)
, m_extension(100)
{
m_animation = new QPropertyAnimation(this, "geometry", this);
m_animation->setDuration(m_duration);
m_animation->setEasingCurve(QEasingCurve::Linear);
m_init_geometry = geometry();
}
void MyButton::enterEvent(QEvent *event)
{
QPushButton::enterEvent(event);
m_animation->stop();
// update the duration so that we get a uniform speed when triggering this animation midway
m_animation->setDuration(((m_init_geometry.width() + m_extension - width())/m_extension)*m_duration);
m_animation->setStartValue(geometry());
m_animation->setEndValue(QRectF(m_init_geometry.x(), m_init_geometry.y(), m_init_geometry.width() + m_extension, m_init_geometry.height()));
m_animation->start();
}
void MyButton::leaveEvent(QEvent *event)
{
QPushButton::leaveEvent(event);
m_animation->stop();
// update the duration so that we get a uniform speed when triggering this animation midway
m_animation->setDuration(((width() - m_init_geometry.width())/m_extension)*m_duration);
m_animation->setStartValue(geometry());
m_animation->setEndValue(m_init_geometry);
m_animation->start();
}
void MyButton::moveEvent(QMoveEvent *event)
{
// ignore the move event if it's due to the animation, otherwise store the new geometry
if(m_animation->state() == QPropertyAnimation::Running) return;
QPushButton::moveEvent(event);
m_init_geometry.setTopLeft(event->pos());
}
void MyButton::resizeEvent(QResizeEvent *event)
{
// ignore the move event if it's due to the animation, otherwise store the new geometry
if(m_animation->state() == QPropertyAnimation::Running) return;
QPushButton::resizeEvent(event);
m_init_geometry.setSize(event->size());
}
Notice that the start value of the closing animation is the current geometry and not the initial geometry plus the extended width. I'm updating reducing the duration of the opening animation linearly depending on how close the current width is to the full extended width; similarly for the closing animation. The rest now is very similar to your code:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto* btn = new MyButton(this);
btn->setGeometry(100, 100, 60, 80);
btn->setStyleSheet(R"(QPushButton {
background-image: url(:/ubuntu.png);
background-repeat: no-repeat;
background-origin: content;
background-position: left center;}
)");
auto* labl = new QLabel("Hello World", btn);
labl->setAlignment(Qt::AlignCenter);
labl->setGeometry(btn->width(), 0, labl->width(), btn->height());
}
The result looks like this
I try this way:
in mybutton.h
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QLabel>
#include <QPushButton>
class MyButton : public QPushButton
{
Q_OBJECT
public:
MyButton(QWidget* parent = nullptr);
// QObject interface
public:
bool eventFilter(QObject *watched, QEvent *event);
signals:
void mouseEnter();
void mouseLeave();
private:
};
#endif // MYBUTTON_H
in mybutton.cpp
#include "mybutton.h"
#include <QEvent>
#include <QLabel>
MyButton::MyButton(QWidget *parent):
QPushButton(parent)
{
}
bool MyButton::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::HoverEnter)
{
emit mouseEnter();
}
else if (event->type() == QEvent::HoverLeave)
{
emit mouseLeave();
}
}
I use signal and in MainWindow class UI I add widget and layouts:
in mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
QLabel *labl;
QLabel *labl2;
};
#endif // MAINWINDOW_H
in mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <mybutton.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
MyButton* btn = new MyButton(this);
btn->setObjectName("button 1 ");
btn->setText("btn 1");
btn->setGeometry(ui->widget->x(), 100, 50, 50);
ui->widget->layout()->addWidget(btn);
// QLabel
labl= new QLabel(btn,Qt::ToolTip);
connect(btn,&MyButton::mouseEnter,this,[btn,this](){
labl->setObjectName("label");
labl->setGeometry(btn->x()+this->x()+btn->width()+10, btn->y()+this->y()+btn->height()+15,
labl->width(), labl->height());
labl->setText("Hello World");
labl->setAlignment(Qt::AlignCenter);
labl->show();
});
connect(btn,&MyButton::mouseLeave,this,[this](){
labl->hide();
});
btn->installEventFilter(btn);
MyButton* btn2 = new MyButton(this);
btn2->setObjectName("button 2 ");
btn2->setText("btn 2");
btn2->setGeometry(ui->widget->x(), 100, 50, 50);
ui->widget->layout()->addWidget(btn2);
// QLabel
labl2= new QLabel(btn2,Qt::ToolTip);
connect(btn2,&MyButton::mouseEnter,this,[btn2,this](){
labl2->setObjectName("label");
labl2->setGeometry(btn2->x()+this->x()+btn2->width()+10, btn2->y()+this->y()+btn2->height()+15,
labl2->width(), labl2->height());
labl2->setText("Hello World 2");
labl2->setAlignment(Qt::AlignCenter);
labl2->show();
});
connect(btn2,&MyButton::mouseLeave,this,[this](){
labl2->hide();
});
btn->installEventFilter(btn);
btn2->installEventFilter(btn2);
}
MainWindow::~MainWindow()
{
delete ui;
}
and this is my result:
By using the same color in the stylesheet you can have what you show in your Gif.
You can try this approach:
Calculate the start and end rect only once and set in the animation object.
On enter you start the animation as before.
On leave you can change the direction of the animation from play forward to backward.
You can run the animation backwards with
this->anim->setDirection( QAbstractAnimation::Backward );
I don't know if you must surround it with
this->anim->pause();
//[...]
this->anim->resume();
Maybe you must experience with it a little.
Also, maybe you must keep track of
1.) Did you start a animation in forward and backward already for not start it twice or even more often, e.g. use an enum to safe state in a member:
enum class eState { Stopped, Forward, Backward };
2.) Test if the animation is still running or finished already for eventually start a new animation in either forward or backward direction, e.g. test with
this->anim->currentTime() < this->anim->totalDuration();
// or just query the state
this->anim->state() == QAbstractAnimation::Stopped;
I hope I could help you to solve your problem.
EDIT My point 1.) you can also solve with the methods of the animation class by testing:
this->anim->direction(); // and...
this->anim->state();

How to create a vertical (rotated) button in Qt with C++

I'd like to create a vertical button in Qt (using C++, not Python), with text rotated 90ยบ either clockwise or counterclockwise. It doesn't seem to be possible with a standard QPushButton.
How could I do it?
In order to create a vertical button in Qt, you can subclass QPushButton so that the dimensions reported by the widget are transposed, and also modify the drawing event to paint the button with the proper alignment.
Here's a class called OrientablePushButton that can be used as a drop-in replacement of the traditional QPushButton but also supports vertical orientation through the usage of setOrientation.
Aspect:
Sample usage:
auto anotherButton = new OrientablePushButton("Hello world world world world", this);
anotherButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
anotherButton->setOrientation(OrientablePushButton::VerticalTopToBottom);
Header file:
class OrientablePushButton : public QPushButton
{
Q_OBJECT
public:
enum Orientation {
Horizontal,
VerticalTopToBottom,
VerticalBottomToTop
};
OrientablePushButton(QWidget * parent = nullptr);
OrientablePushButton(const QString & text, QWidget *parent = nullptr);
OrientablePushButton(const QIcon & icon, const QString & text, QWidget *parent = nullptr);
QSize sizeHint() const;
OrientablePushButton::Orientation orientation() const;
void setOrientation(const OrientablePushButton::Orientation &orientation);
protected:
void paintEvent(QPaintEvent *event);
private:
Orientation mOrientation = Horizontal;
};
Source file:
#include <QPainter>
#include <QStyleOptionButton>
#include <QDebug>
#include <QStylePainter>
OrientablePushButton::OrientablePushButton(QWidget *parent)
: QPushButton(parent)
{ }
OrientablePushButton::OrientablePushButton(const QString &text, QWidget *parent)
: QPushButton(text, parent)
{ }
OrientablePushButton::OrientablePushButton(const QIcon &icon, const QString &text, QWidget *parent)
: QPushButton(icon, text, parent)
{ }
QSize OrientablePushButton::sizeHint() const
{
QSize sh = QPushButton::sizeHint();
if (mOrientation != OrientablePushButton::Horizontal)
{
sh.transpose();
}
return sh;
}
void OrientablePushButton::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QStylePainter painter(this);
QStyleOptionButton option;
initStyleOption(&option);
if (mOrientation == OrientablePushButton::VerticalTopToBottom)
{
painter.rotate(90);
painter.translate(0, -1 * width());
option.rect = option.rect.transposed();
}
else if (mOrientation == OrientablePushButton::VerticalBottomToTop)
{
painter.rotate(-90);
painter.translate(-1 * height(), 0);
option.rect = option.rect.transposed();
}
painter.drawControl(QStyle::CE_PushButton, option);
}
OrientablePushButton::Orientation OrientablePushButton::orientation() const
{
return mOrientation;
}
void OrientablePushButton::setOrientation(const OrientablePushButton::Orientation &orientation)
{
mOrientation = orientation;
}

How to move the whole window when mouse is on the window's custom widget in Qt?

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.

How to drag the QLabel from one window to another in Qt?

I am learning Qt for fun. And I got a question:
How could I drag and drop the QLabel in Qt among two different windows?
Here is what I have so far:
As you can tell from the .gif(which does not want to become downloaded and visible here for some reasons, but if you click on the link to it, you can clearly see it) provided above right now there are two main problems:
I can not move the QLabel outside of the window (and hence am not able to register the drag and drop event).
The label is flashing for some reasons when I am moving it.
Here is the relevant part of the implementation from the .gif:
#ifndef DRAGGERP_H
#define DRAGGERP_H
#include <QLabel>
#include <QApplication>
#include <QMouseEvent>
#include <QPoint>
class DraggerP : public QLabel
{
QPoint offset;
QPoint startingPosition;
public:
DraggerP(QWidget* parent = nullptr) : QLabel(parent){ }
protected:
void enterEvent(QEvent* event) override
{
QApplication::setOverrideCursor(Qt::PointingHandCursor);
}
void leaveEvent(QEvent* event) override
{
QApplication::restoreOverrideCursor();
}
void mousePressEvent(QMouseEvent* event) override
{
startingPosition = pos();
offset = QPoint(
event->pos().x() - pos().x() + 0.5*width(),
event->pos().y() - pos().y() + 0.5*height()
);
}
void mouseMoveEvent(QMouseEvent* event) override
{
move(event->pos() + offset);
}
void mouseReleaseEvent(QMouseEvent* event) override
{
move(startingPosition);
}
};
#endif // DRAGGERP_H
This is the extension of the QLabel I am using to create the drag and drop effect.
I do not need the whole solution, at least an idea of how to accomplish this and what am I doing wrong here.
Here is a pretty good example and I used it as a starting point.
That strange movement that the QLabel suffers is because the position of the QLabel now depends on the layout, the job of the layout is to establish the position of the widgets depending on the policies you establish.
The solution is not to implement those actions in the QLabel but in the MainWindow as I show below:
#include <QApplication>
#include <QLabel>
#include <QMainWindow>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QTime>
#include <QDrag>
#include <QMimeData>
#include <QMouseEvent>
class MainWindow: public QMainWindow {
QScrollArea scrollArea;
QWidget contentWidget;
QVBoxLayout lay;
public:
MainWindow(QWidget* parent=nullptr): QMainWindow(parent){
qsrand((uint) QTime::currentTime().msec());
setCentralWidget(&scrollArea);
scrollArea.setWidget(&contentWidget);
contentWidget.setLayout(&lay);
scrollArea.setWidgetResizable(true);
for(int i=0; i< 20; i++){
QLabel *label = new QLabel(QString("label %1").arg(i));
QPalette pal = label->palette();
pal.setColor(QPalette::Background, QColor(10 +qrand() % 240, 10 +qrand() % 240, 10 +qrand() % 240));
label->setAutoFillBackground(true);
label->setPalette(pal);
lay.addWidget(label);
}
setAcceptDrops(true);
}
protected:
void mousePressEvent(QMouseEvent *event){
QMainWindow::mousePressEvent(event);
QWidget *child = childAt(event->pos());
if(qobject_cast<QLabel *>(child))
createDrag(event->pos(), child);
}
void dropEvent(QDropEvent *event){
QByteArray byteArray = event->mimeData()->data("Label");
QWidget * widget = *reinterpret_cast<QWidget**>(byteArray.data());
QLabel * new_label = qobject_cast<QLabel *>(widget);
QWidget *current_children = childAt(event->pos());
QLabel * current_label = qobject_cast<QLabel*>(current_children);
int index = 0;
if(new_label){
if(current_label)
index = lay.indexOf(current_label);
else{
index = 0;
QLayoutItem *item = lay.itemAt(index);
while(item->widget()->pos().y() < event->pos().y() && item)
item = lay.itemAt(index++);
}
lay.insertWidget(index, new_label);
}
}
private:
void createDrag(const QPoint &pos, QWidget *widget){
if(widget == Q_NULLPTR)
return;
QByteArray byteArray(reinterpret_cast<char*>(&widget),sizeof(QWidget*));
QDrag *drag = new QDrag(this);
QMimeData * mimeData = new QMimeData;
mimeData->setData("Label",byteArray);
drag->setMimeData(mimeData);
QPoint globalPos = mapToGlobal(pos);
QPoint p = widget->mapFromGlobal(globalPos);
drag->setHotSpot(p);
drag->setPixmap(widget->grab());
drag->exec(Qt::CopyAction | Qt::MoveAction);
}
protected:
void dragEnterEvent(QDragEnterEvent *event){
if(event->mimeData()->hasFormat("Label"))
event->acceptProposedAction();
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w1;
MainWindow w2;
w1.show();
w2.show();
return a.exec();
}

Qt: How to adjust QGraphicsItem to dynamicaly change size

I've this problem for several days. I have created QGraphicsItem and I want to stretch/adjust it's size to size of my QGraphicsView. I was using paint() method, but got with it problems with updating. Now I've used boundingRect() but it uses fixed size. When I set too big size it expand my scene and scrollbars are appearing. Is there way to adjust size of item to size of View?
EDIT: I want only adjust height of my object.
Here's some code:
Header of my Item:
#ifndef POINTER_H
#define POINTER_H
#include <QObject>
#include <QColor>
#include <QRect>
#include <QGraphicsLineItem>
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
class Pointer : public QGraphicsLineItem
{
public:
Pointer();
void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget);
QRectF boundingRect() const;
int position;
void changePosition(int x);
};
#endif // TRACKPOINTER_H
Implementation of my Item:
#include "pointer.h"
Pointer::Pointer()
{
//this->setFlag(QGraphicsLineItem::ItemIsMovable);
//setFlag(QGraphicsLineItem::ItemIsFocusable);
//setFocus();
}
void Pointer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QPen pen(Qt::red);
painter->setPen(pen);
painter->setBrush(QColor(77,77,77));
painter->drawLine(0,0,0,2000);
}
QRectF Pointer::boundingRect() const
{
return QRectF(0,0,2,600);
}
void Pointer::changePosition(int x)
{
//position = x;
setPos(x,0);
update();
}
And my Window:
Window::Window(Timers *timer, TrackPointer *tp)
{
Timeline = new QGraphicsScene(this);
TimelineView = new QGraphicsView(Timeline);
TimelineView->setAlignment(Qt::AlignTop|Qt::AlignLeft);
QVBoxLayout *timeLineLayout = new QVBoxLayout;
timeLineLayout->addWidget(TimelineView);
Pointer *pointer = new Pointer;
Timeline->addItem(pointer);
}
I have also problems with my scene: When my object moves somewhere away - it expand scene. Later when I bring my object back to it's starting position scene still is expanded and I have scrollbars to scroll my view around the scene. Is there way to decrease scene size using my object?
The promised example looks something like this:
MainWindow.h
#include <QMainWindow>
class QGraphicsView;
class QGraphicsRectItem;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
bool eventFilter(QObject *watched, QEvent *event) override;
private:
QGraphicsView *m_view;
QGraphicsRectItem *m_item;
};
MainWindow.cpp
#include "MainWindow.h"
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QEvent>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
m_view(new QGraphicsView(this)),
m_item(new QGraphicsRectItem(0, 0, 1, 1))
{
m_view->setScene(new QGraphicsScene());
m_view->setFrameStyle(QFrame::NoFrame);
m_view->setAlignment(Qt::AlignLeft | Qt::AlignTop);
m_view->setSceneRect(0, 0, 1, 1);
m_view->installEventFilter(this);
m_view->scene()->addItem(m_item);
setCentralWidget(m_view);
resize(600, 400);
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if ((watched == m_view) && (event->type() == QEvent::Resize))
m_item->setRect(m_view->viewport()->rect().adjusted(5, 5, -5, -5));
return QMainWindow::eventFilter(watched, event);
}
look into :QGraphicsView::fitInView
http://doc.qt.io/qt-4.8/qgraphicsview.html#fitInView
QPixmap *pixMap= new QPixmap();;
QGraphicsScene *scene=new QGraphicsScene();;
pixMap->loadFromData(jpegData);
scene->clear();
pixMapItem = scene->addPixmap(*pixMap);
ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
ui->graphicsView->show();
ui->graphicsView->viewport()->update();