QGraphicsItem setShear ? (applicable for single or multiple items) - qt

To apply rotation on a QGraphicsItem, I can either call rotate() or setRotation(). What I see happening:
item.rotate(angle);
results in rotation, as I expect; yet if I copy the item (using a cop constructor in a subclass), no rotation will be copied (even if I copy all transformations).
item.setRotation(angle);
results in the item having a rotation() property which I can copy - but requires an update.
So the second version is what I need.
I would like to be able to apply shear to my items as well.
item.shear(shx, shy);
looks good on initial item - but I cannot find a way to copy this shear. Nor can I find a similar property as for rotation: there is no setShear() that I can find.
Even more, if I try to play with transformations, to achieve a group shear (like this question's rotation), I get very weird results...
How can I create a similar stored property for shear ?
Edit:
Trying
QTransform t;
t.shear(shx, shy);
item->setTransform(t, true);
also gives me a giant scaling and some rotation... I have tried to divide shx and shy by 100 and it seems reasonable (not sure if correct ?).
Note - will move the code snippet in answer, since it seems it will work.

It seems that I have to divide shear factors by 100 (this doesn't seem true always).
void shearItem(qreal shx, qreal shy)
{
QRectF rect;
foreach(QGraphicsItem* item, scene()->selectedItems())
rect |= item->mapToScene(item->boundingRect()).boundingRect();
QPointF center = rect.center();
QTransform t;
t.translate(center.x(), center.y());
// seems shear increases item to gigantic proportions so I tried
t.shear(shx/100, shy/100);
t.translate(-center.x(), -center.y());
foreach(QGraphicsItem* item, scene()->selectedItems())
{
item->setPos(t.map(item->pos()));
item->setTransform(t, true);
}
}
I think the transformations are combined on item, so I don't need to do any action to combine any rotation or scaling or shear... I can just use
item->setTransform(t, true);
for all transforms so there is no need to separately set rotation or scale.
There does not seem to exist any conflict though between translate(), rotate(), scale() and shear() methods and setting the transform matrix. So combining these functions, and then copying the transform seems fine. (http://doc.qt.io/qt-4.8/qtransform.html#setMatrix).
There does not seem to be any documented reason for having to scale down the shear factors... (http://doc.qt.io/qt-4.8/qtransform.html#shear).
If I set shear in item constructor, it works without having to divide by 100.
Yet if I call a shear as in code above, without scaling the factors down I get a resize as well. I find this very confusing... But as long as it works....
Full sample code: included rotation change and shear change
myview.h
#ifndef MYVIEW_H
#define MYVIEW_H
#include <QGraphicsView>
#include <QAction>
#include <QContextMenuEvent>
#include <QGraphicsItem>
class MyView : public QGraphicsView
{
Q_OBJECT
public:
MyView(QWidget *parent = 0);
protected:
virtual void contextMenuEvent(QContextMenuEvent *event);
private slots:
void rotateItem(qreal deg = 10);
void shearItem(qreal shx = 10, qreal shy = 10);
private:
void createActions();
QAction *rotateAct;
QAction *shearAct;
};
#endif // MYVIEW_H
myview.cpp
#include "myview.h"
#include <QMenu>
MyView::MyView(QWidget *parent) : QGraphicsView(parent)
{
createActions();
setScene(new QGraphicsScene(-500, -150, 1000, 600, this));
setDragMode(RubberBandDrag);
}
void MyView::contextMenuEvent(QContextMenuEvent *event)
{
QGraphicsItem* item = itemAt(event->pos());
if(item != NULL)
item->setSelected(true);
if(scene()->selectedItems().isEmpty()) return;
QMenu menu(this);
menu.addAction(rotateAct);
menu.addAction(shearAct);
menu.exec(event->globalPos());
}
void MyView::rotateItem(qreal deg)
{
QRectF rect;
foreach(QGraphicsItem* item, scene()->selectedItems())
rect |= item->mapToScene(item->boundingRect()).boundingRect();
QPointF center = rect.center();
QTransform t;
t.translate(center.x(), center.y());
t.rotate(deg);
t.translate(-center.x(), -center.y());
foreach(QGraphicsItem* item, scene()->selectedItems())
{
item->setPos(t.map(item->pos()));
item->setRotation(item->rotation() + deg);
}
}
void MyView::shearItem(qreal shx, qreal shy)
{
QRectF rect;
foreach(QGraphicsItem* item, scene()->selectedItems())
rect |= item->mapToScene(item->boundingRect()).boundingRect();
QPointF center = rect.center();
QTransform t;
t.translate(center.x(), center.y());
t.shear(shx, shy);
// seems shear increases item to gigantic proportions so I tried
//t.shear(shx/100, shy/100);
t.translate(-center.x(), -center.y());
foreach(QGraphicsItem* item, scene()->selectedItems())
{
item->setPos(t.map(item->pos()));
item->setTransform(t, true);
}
}
void MyView::createActions()
{
rotateAct = new QAction(tr("Rotate 10 degrees"), this);
connect(rotateAct, SIGNAL(triggered()), this, SLOT(rotateItem()));
shearAct = new QAction(tr("Shear 10, 10"), this);
connect(shearAct, SIGNAL(triggered()), this, SLOT(shearItem()));
}
main.cpp:
#include <QtGui>
#include "myview.h"
int main( int argc, char **argv )
{
QApplication app(argc, argv);
MyView* view = new MyView();
QGraphicsRectItem* rItem = view->scene()->addRect(30, 40, 100, 100, Qt::NoPen, Qt::blue);
QGraphicsRectItem* rItem1 = view->scene()->addRect(-30, -40, 100, 100, Qt::NoPen, Qt::red);
QGraphicsEllipseItem* eItem = view->scene()->addEllipse(70, -50, 100, 100, Qt::NoPen, Qt::blue);
foreach(QGraphicsItem* item, view->scene()->items())
{
item->setFlag(QGraphicsItem::ItemIsMovable);
item->setFlag(QGraphicsItem::ItemIsFocusable);
item->setFlag(QGraphicsItem::ItemIsSelectable);
}
view->show();
return app.exec();
}

Related

Qt5 : Displaying a Qpainter with layouts

I started using Qt5 a little while ago and I don't know how to set the position of my drawing in my window.
I have a Drawing class which is a QWidget and which contains my paintEvent() function and other functions; and a MainWindow class which contains some widgets. So in order to display my Qpainter, I have to include it in my layout, which is the main window layout, but the window doesn't adapt to the Qpainter at all; the dimensions of the buttons, sliders .. adapt so as to organize themselves and occupy all the space of my window, but they totally ignore my Qpainter and it partly disappears if I put at least 2 widgets.
Do you have any solutions to better manage the position of these elements?
main.cpp :
#include <QtGui>
#include <QApplication>
#include "mywidget.h"
int main( int argc, char **argv )
{
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
mywidget.h :
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QtGui>
#include <QWidget>
#include <QSlider>
#include <QScrollBar>
#include <QApplication>
#include <QGridLayout>
#include <QObject>
#include <QPoint>
#include <QLabel>
#include <QPolygon>
class Drawing : public QWidget
{
Q_OBJECT
public:
Drawing();
void paintEvent(QPaintEvent* e);
public slots:
void slide(int abscisse);
void rotate();
private:
QPoint o;
QPoint a;
QPoint b;
QPoint c;
QPoint d;
};
//--------------------------------------
class MainWindow : public QWidget
{
Q_OBJECT
public:
MainWindow();
private:
QSlider* m_slider1;
QSlider* m_slider2;
QGridLayout* m_layout;
Drawing* m_dessin;
};
#endif // MYWIDGET_H
mywidget.cpp :
#include "mywidget.h"
#include <iostream> //POUR LES TESTS
MainWindow::MainWindow() : QWidget()
{
setGeometry(330, 140, 840, 620);
m_slider1 = new QSlider(Qt::Horizontal, this);
m_slider1->setRange(150, 650);
m_slider1->setSliderPosition(400);
m_slider2 = new QSlider(Qt::Horizontal, this);
m_slider2->setSliderPosition(50);
m_layout = new QGridLayout;
m_layout->addWidget(new QLabel("Translation Horizontale"), 1, 0);
m_layout->addWidget(m_slider1, 2, 0);
m_layout->addWidget(new QLabel("Rotation"), 0, 1);
m_layout->addWidget(m_slider2, 1, 1);
m_dessin = new Drawing;
m_layout->addWidget(m_dessin, 0, 0);
setLayout(m_layout);
QObject::connect(m_slider1, SIGNAL(valueChanged(int)), m_dessin, SLOT(slide(int)));
QObject::connect(m_slider2, SIGNAL(valueChanged(int)), m_dessin, SLOT(rotate()));
}
//--------------------------------------------------------
Drawing::Drawing() : QWidget(), o(400, 150), a(o.x()-50 , o.y()-50), b(o.x()+50 , o.y()-50), c(o.x()+50 , o.y()+50), d(o.x()-50 , o.y()+50) {}
void Drawing::paintEvent(QPaintEvent *e) {
QPolygon poly;
poly << a << b << c << d;
QWidget::paintEvent(e); // effectue le comportement standard
QPainter painter(this); // construire
painter.setPen( QPen(Qt::white, 2) ); // personnaliser
painter.drawPolygon(poly); // dessiner
}
void Drawing::slide(int abscisse) {
if (a == QPoint(o.x()-50 , o.y()-50)) {
o.setX(abscisse);
a.setX(o.x()-50);
b.setX(o.x()+50);
c.setX(o.x()+50);
d.setX(o.x()-50);
}
else {
o.setX(abscisse);
a.setX(o.x());
b.setX(o.x()+75);
c.setX(o.x());
d.setX(o.x()-75);
}
update();
}
void Drawing::rotate() {
if (a == QPoint(o.x()-50 , o.y()-50)) {
a = QPoint(o.x() , o.y()+75);
b = QPoint(o.x()+75 , o.y());
c = QPoint(o.x() , o.y()-75);
d = QPoint(o.x()-75 , o.y());
}
else {
a = QPoint(o.x()-50 , o.y()-50);
b = QPoint(o.x()+50 , o.y()-50);
c = QPoint(o.x()+50 , o.y()+50);
d = QPoint(o.x()-50 , o.y()+50);
}
update();
}
Snapshots:
After having seen the snapshots of OP, I thought about what might been happen.
The layout of OP doesn't look that wrong.
I still believe that the layout plays only a minor role in OPs issue.
I tried to reproduce OPs issue with an even smaller MCVE of mine.
My testQGridLayout:
#include <QtWidgets>
class Drawing: public QFrame {
public:
Drawing(QWidget *pQParent = nullptr);
virtual ~Drawing() = default;
Drawing(const Drawing&) = delete;
Drawing& operator=(const Drawing&) = delete;
protected:
virtual void paintEvent(QPaintEvent *pQEvent) override;
};
Drawing::Drawing(QWidget* pQParent):
QFrame(pQParent)
{
setFrameStyle(Box | Plain);
}
void Drawing::paintEvent(QPaintEvent* pQEvent)
{
{ QPainter qPainter(this);
qPainter.drawText(QPoint(40, 40),
QString("Size: %1 x %2").arg(width()).arg(height()));
qPainter.setPen(Qt::red);
qPainter.drawRect(300, 100, 200, 200);
}
// call base class paint event to keep it working
QFrame::paintEvent(pQEvent);
}
class MainWindow: public QWidget {
public:
MainWindow(QWidget *pQParent = nullptr);
virtual ~MainWindow() = default;
MainWindow(const MainWindow&) = delete;
MainWindow& operator=(const MainWindow&) = delete;
private:
QGridLayout _qGrid;
Drawing _qDrawing;
QSlider _qSliderT;
QSlider _qSliderR;
};
MainWindow::MainWindow(QWidget *pQParent):
QWidget(pQParent),
_qSliderT(Qt::Horizontal),
_qSliderR(Qt::Horizontal)
{
resize(840, 620);
_qGrid.addWidget(&_qDrawing, 0, 0);
_qGrid.addWidget(new QLabel("Translation Horizontal"), 1, 0);
_qSliderT.setRange(150, 650);
_qSliderT.setSliderPosition(400);
_qGrid.addWidget(&_qSliderT, 2, 0);
_qGrid.addWidget(new QLabel("Rotation"), 1, 1);
_qSliderR.setSliderPosition(50);
_qGrid.addWidget(&_qSliderR, 2, 1);
setLayout(&_qGrid);
}
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
MainWindow qWinMain;
qWinMain.setWindowTitle("Test QGridLayout");
qWinMain.show();
// runtime loop
return app.exec();
}
Output:
Qt Version: 5.15.1
I made some changes to exclude what is a possible issue and what not.
I derived my Drawing from QFrame. Thus, it was easy to give it a visible border. As expected, my Drawing _qDrawing occupies only the space above the first slider (QSlider _qSliderT; in my case).
I added output of widget size to the Drawing::paintEvent() to see its size. Then I added the painting of a red rectangle. For that, I cared to cover a space which is partly inside the widget and partly below and right of it.
This is what I conclude:
As exposed in the OPs code, the layout should be the same.
OPs rectangle is always drawn at the same coordinates. Hence, it doesn't get visible until the Drawing grows large enough (with the main window).
The origin of the QPainter (i.e. QPoint(0, 0)) is the upper left corner of the widget. This can be changed by applying transformations but I couldn't see this in OPs code. (The effect of the sliders, I neglect for now.)
Though, there are still some things which are not clear to me:
The Drawing should clip the painting. Hence, I wonder, how OPs rectangle can appear over the rotate slider. Either, the OP used a span for the Drawing m_dessin, or the widget doesn't clip painting on the paint engine the OP uses. (The look is quite different than mine. Thus, it might be a different platform.)
The layout which can be seen in OPs snapshots doesn't match the exposed code. In OPs snapshot, the Drawing occupies all extra space resulting from growing the main window. This is only possible when QGridLayout::setRowStretch()/GridLayout::setColumnStretch() had been used (as recommended in my first comment). However, the exposed code doesn't contain them.
To check this out, I changed the layout in MainWindow::MainWindow():
MainWindow::MainWindow(QWidget *pQParent):
QWidget(pQParent),
_qSliderT(Qt::Horizontal),
_qSliderR(Qt::Horizontal)
{
resize(840, 620);
_qGrid.setRowStretch(0, 1);
_qGrid.setColumnStretch(0, 1);
_qGrid.addWidget(&_qDrawing, 0, 0, 1, 2);
_qGrid.addWidget(new QLabel("Translation Horizontal"), 1, 0);
_qSliderT.setRange(150, 650);
_qSliderT.setSliderPosition(400);
_qGrid.addWidget(&_qSliderT, 2, 0);
_qGrid.addWidget(new QLabel("Rotation"), 1, 1);
_qSliderR.setSliderPosition(50);
_qGrid.addWidget(&_qSliderR, 2, 1);
setLayout(&_qGrid);
}
Output:
Now, the layout seems to match the one of OPs snapshots.
Trying resize:
This looks exactly as it should:
the Drawing _qDrawing shrinks and grows with the main window size
the painting is clipped if the size of Drawing _qDrawing becomes too small to cover it.
Final Conclusion:
There is nothing wrong in OPs layout.
IMHO, OP is not yet fully clear about how coordinate systems apply in QPainter.
For this, I can warmly recommend an extra page of the Qt online doc., precisely dedicated to this topic:
Qt Doc.: Coordinate System
Continuation:
How to add a vertical slider:
class MainWindow: public QWidget {
public:
MainWindow(QWidget *pQParent = nullptr);
virtual ~MainWindow() = default;
MainWindow(const MainWindow&) = delete;
MainWindow& operator=(const MainWindow&) = delete;
private:
QGridLayout _qGrid;
Drawing _qDrawing;
QSlider _qSliderV;
QSlider _qSliderT;
QSlider _qSliderR;
};
MainWindow::MainWindow(QWidget *pQParent):
QWidget(pQParent),
_qSliderV(Qt::Vertical),
_qSliderT(Qt::Horizontal),
_qSliderR(Qt::Horizontal)
{
resize(840, 620);
_qGrid.setRowStretch(0, 1);
_qGrid.setColumnStretch(0, 1);
_qGrid.addWidget(&_qDrawing, 0, 0, 1, 2);
_qGrid.addWidget(&_qSliderV, 0, 2);
_qGrid.addWidget(new QLabel("Translation Horizontal"), 1, 0);
_qSliderT.setRange(150, 650);
_qSliderT.setSliderPosition(400);
_qGrid.addWidget(&_qSliderT, 2, 0);
_qGrid.addWidget(new QLabel("Rotation"), 1, 1, 1, 2);
_qSliderR.setSliderPosition(50);
_qGrid.addWidget(&_qSliderR, 2, 1, 1, 2);
setLayout(&_qGrid);
}
Output:
To achieve this specific layout, I placed the _qSliderV into column 2 and gave _qSliderR (and its label) a column span of 2 as well.
To illustrate this, I added a sketch of the resulting grid to the above snapshot:
Which behavior would you like for your Drawing widget?
By default it will be resized freely by the layout, you can change this by using QWidget::sizeHint() and QWidget::sizePolicy(). You'll find detailed information in Qt documentation about custom widgets and layouts.

How to draw something with QPainter when the button is pushed

I am working on my project from programming and I need to draw, for example, a circle every time the pushButton is pressed using QPainter. This is the first problem, and the second one here is that I need some information to be sent to the drawing function too, for example, int vector, and being able to draw so many circles, as there are elements in the vector with radii of the elements itself. I have found some code based on signals and slots.
The sender:
public:
Listener(QObject *p = 0) : QObject(p) {
QTimer * t = new QTimer(this);
t->setInterval(200);
connect(t, SIGNAL(timeout()), this, SLOT(sendData()));
t->start();
}
signals:
void dataAvaiable(int, int);
public slots:
void sendData() {
emit dataAvaiable(qrand() % 200, qrand() % 200);
}
The reciever:
void receiveData(int x, int y) {
QPainter painter(this);
QPen pen(Qt::white, 5);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.setPen(pen);
QPoint point(x, y);
painter.drawEllipse(x, y, 100, 100);
data.append(point);
}
The connection itself in main.cpp
QObject::connect(&l, SIGNAL(dataAvaiable(int,int)), &w, SLOT(receiveData(int,int)));
But the code doesn't suit for my exact task with buttons and doesn't even want to draw anythig, just any circle at all. Howewer, in debugger the code executes properly, and I am relatively new to Qt and C++ so I can't figure out by myself, where the problem is and how to solve my task.Can someone please do a minimal of code or simply explain to me, where exactly the problem is? Need to solve the problem as soon as possible. Thank you.
Upd: any possible solution with or without QPainter would be good now.
Qt Forum users gave me an answer.
Quote:
From the QPainter class description:
Warning: When the paintdevice is a widget, QPainter can only be used inside a
paintEvent() function or in a function called by paintEvent().
You can force calling paintEvent() by invoking update(), so you must connect the onclicked() signal of your button to the update() slot of the widget you're drawing on.
For your second problem, the data can be a member variable.
Here's an example:
// mywidget.h
#include <QVector>
#include <QPoint>
// other includes and the constructor...
protected:
virtual void paintEvent(QPaintEvent *event);
private slots:
void onButtonClicked();
private:
QPushButton* mButton;
QVector<QPoint> mCirclesData;
// mywidget.cpp
MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
mButton = new QPushButton(this);
// customise your button...
connect(mButton, &QPushButton::clicked, this, &MyWidget::onButtonClicked);
}
//...
void MyWidget::onButtonClicked(){
int x = qrand() % 200, y = x;
mCirclesData << QPoint(x,y);
update(); // force calling paintEvent
}
void MyWidget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
QPen pen(Qt::white, 5);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.setPen(pen);
painter.drawEllipse(mCirclesData.last().x(), mCirclesData.last().y(), 100, 100);
}
``

Programmatic scrolling with QGraphicsView and QGraphicsItem?

I would like to programmatically scroll a scene to the left / right, but I am not sure how to do that properly. Note that I do not want to have (visible) scroll bars.
I use a standard QGraphicsView + QGraphicsScene + QGraphicsItem setup. I have downsized it to the minimum, with one single QGraphicsItem (a QGraphicsRectItem) in the scene.
I have managed to achieve programmatic scrolling by setting my view like this:
// view setup
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
and then, in another part of the code:
// programmatic scrolling
QScrollBar* const sb = view->horizontalScrollBar();
sb->setRange(0, 1000); // some values for experimenting
sb->setValue(sb->value() + 100 or -100); // some increment for experimenting
This works, but... scrolling through invisible scrollbars doesn't feel right.
I tried this more straightforward approach:
// programmatic scrolling - doesn't quite work
view->viewport()->scroll(100 or -100, 0); // some increment for experimenting
This code does scroll, but when the rectangle goes off the left edge of the view, and I reverse the scrolling direction (increment changed from 100 to -100 in the call to scroll()), the uncovered part of the rectangle is not repainted. The reason is that QGraphicsRectItem::paint() is not called in that case (it is called when using the scrollbar method).
So, is there a way to get viewport()->scroll() work? Or some other simple way to achieve programmatic scrolling? Or is the artificial scrollbar method just the way to go?
Moving the view assumes that it's smaller than its scene. If they're the same size, it won't move.
QGraphicsView can be set to centerOn any position in scene coordinates. Use a timer to call centerOn to move the view one frame at a time.
Here's a working example: -
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QTimer>
class MyView : public QGraphicsView
{
private:
public:
MyView(QGraphicsScene* pScene)
: QGraphicsView(pScene, NULL)
{}
void AnimateBy(int x)
{
float updateFrequency = (1000/30.0); // ~30 frames per second
QPointF currScenePos = sceneRect().center();
int curX = currScenePos.x();
int endPos = curX + x;
int distanceToAnimate = (endPos - curX);
// speed = dist / time
float updatePosInterval = (float)distanceToAnimate / updateFrequency;
printf("updatePosInterval: %f \n", updatePosInterval);
static float newXPos = sceneRect().center().x();
QTimer* pTimer = new QTimer;
QObject::connect(pTimer, &QTimer::timeout, [=](){
newXPos += updatePosInterval;
centerOn(newXPos, sceneRect().center().y());
// check for end position or time, then....
if(newXPos >= endPos)
{
pTimer->stop();
pTimer->deleteLater();
}
});
pTimer->start(updateFrequency);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene(0, 0, 10000, 20000);
MyView* view = new MyView(&scene);
QGraphicsRectItem* pRect = new QGraphicsRectItem(0, 0, 100, 100);
pRect->setPos(scene.width()/2, scene.height()/2);
scene.addItem(pRect);
// timer to wait for the window to appear, before starting to move
QTimer* pTimer = new QTimer;
pTimer->setSingleShot(true);
QObject::connect(pTimer, &QTimer::timeout,[=](){
view->centerOn(pRect); // centre on the rectangle
view->AnimateBy(100);
pTimer->deleteLater();
});
pTimer->start(1000);
view->show();
return a.exec();
}
So, we create the animation by moving the view frame-by-frame using the call to centerOn.
For simplicity, the code just deals with moving in one axis. To move in 2 axis, use 2D vector maths to calculate the interval position.
Try to change the view transformation with the QGraphicsView::translate() or QGraphicsView::setTransform().
But keep in mind that you can't move the viewport "outside" the scene, so make sure that your scene rectangle is large enough.
If I got your question correctly, there is a dojo classes library with such class as PanWebView that allow QWebView to scroll smoothly with mouse without any scrollbars. Take a look at sources. It supports panning and can be suitable for mobile apps, but maybe it'll help you too.
PanWebView class looks like this
#include <QWebView>
#include <QWebFrame>
#include <QMouseEvent>
#include <QApplication>
class PanWebView : public QWebView
{
Q_OBJECT
private:
bool pressed;
bool scrolling;
QPoint position;
QPoint offset;
QList<QEvent*> ignored;
public:
PanWebView(QWidget *parent = 0): QWebView(parent), pressed(false), scrolling(false) {
QWebFrame *frame = page()->mainFrame();
frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
}
protected:
void mousePressEvent(QMouseEvent *mouseEvent) {
if (ignored.removeAll(mouseEvent))
return QWebView::mousePressEvent(mouseEvent);
if (!pressed && !scrolling && mouseEvent->modifiers() == Qt::NoModifier)
if (mouseEvent->buttons() == Qt::LeftButton) {
pressed = true;
scrolling = false;
position = mouseEvent->pos();
QWebFrame *frame = page()->mainFrame();
int x = frame->evaluateJavaScript("window.scrollX").toInt();
int y = frame->evaluateJavaScript("window.scrollY").toInt();
offset = QPoint(x, y);
QApplication::setOverrideCursor(Qt::OpenHandCursor);
return;
}
return QWebView::mousePressEvent(mouseEvent);
}
void mouseReleaseEvent(QMouseEvent *mouseEvent) {
if (ignored.removeAll(mouseEvent))
return QWebView::mouseReleaseEvent(mouseEvent);
if (scrolling) {
pressed = false;
scrolling = false;
QApplication::restoreOverrideCursor();
return;
}
if (pressed) {
pressed = false;
scrolling = false;
QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
position, Qt::LeftButton,
Qt::LeftButton, Qt::NoModifier);
QMouseEvent *event2 = new QMouseEvent(*mouseEvent);
ignored << event1;
ignored << event2;
QApplication::postEvent(this, event1);
QApplication::postEvent(this, event2);
QApplication::restoreOverrideCursor();
return;
}
return QWebView::mouseReleaseEvent(mouseEvent);
}
void mouseMoveEvent(QMouseEvent *mouseEvent) {
if (scrolling) {
QPoint delta = mouseEvent->pos() - position;
QPoint p = offset - delta;
QWebFrame *frame = page()->mainFrame();
frame- >evaluateJavaScript(QString("window.scrollTo(%1,%2);").arg(p.x()).arg(p.y()));
return;
}
if (pressed) {
pressed = false;
scrolling = true;
return;
}
return QWebView::mouseMoveEvent(mouseEvent);
}
};
And usage:
PanWebView web;
web.setUrl(QUrl("http://news.google.com"));
web.setWindowTitle("Web View - use mouse to drag and pan around");
web.show();
Also did you check this and this topics? I think it can be usefull.

QGraphicsRectItem member of a custom scene class added on mouse action gives error

I want to somehow paint a selection rectangle on scene, to show selected items (not he item bounding rectangle, but the bounding rectangle mapped to scene - and if multiple selection, the selection bounding rectangle).
I would like to try something like, on mouse press, to show the rectangle (and update based on current selection), and on mouse release, to hide it.
I am having trouble keeping the rectangle on the scene, and on mouse release it may be removing it, or maybe it was never there - and I get an error:
QGraphicsScene::removeItem: item 0x37828's scene (0x0) is different from this scene (0x1f57b68)
(The above error, and the fact that the item doesn't stay after mouse press, makes me think that it is not added properly but I don't understand why).
Here is a little sample code:
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
class MyScene : public QGraphicsScene
{
public:
MyScene(qreal x, qreal y, qreal w, qreal h) {
setSceneRect(x, y, w, h);
m_selectionRectangle = new QGraphicsRectItem(0,0,1,1);
m_selectionRectangle->setBrush(Qt::magenta);
m_selectionRectangle->setOpacity(0.2);
}
~MyScene() {
if(m_selectionRectangle)
delete m_selectionRectangle;
}
protected:
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsScene::mousePressEvent(event);
if(!selectedItems().isEmpty()) {
QRectF selectionRect = QRectF();
foreach(QGraphicsItem* item, selectedItems())
selectionRect |= item->mapToScene(item->boundingRect()).boundingRect();
m_selectionRectangle->setRect(selectionRect);
addItem(m_selectionRectangle);
}
}
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
QGraphicsScene::mouseReleaseEvent(event);
removeItem(m_selectionRectangle);
}
private:
QGraphicsRectItem* m_selectionRectangle;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MyScene* s = new MyScene(0, 0, 800, 600);
QGraphicsView view(s);
view.setDragMode(QGraphicsView::RubberBandDrag);
view.show();
QGraphicsRectItem* xxx = new QGraphicsRectItem(200, 200, 100, 100);
QGraphicsEllipseItem* yyy = new QGraphicsEllipseItem(300, 300, 200, 100);
s->addItem(xxx);
s->addItem(yyy);
xxx->setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsFocusable|QGraphicsItem::ItemIsSelectable);
yyy->setFlags(QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemIsFocusable|QGraphicsItem::ItemIsSelectable);
return app.exec();
}
What is the meaning of that error, what am I doing wrong in adding the selection rectangle, and why doesn't it stay there - and how can I fix it ?
The meaning of the error is literal: you're passing an item to removeItem that is not a child item of the scene you're trying to remove it from. It is nonsense to remove an item that is not in the scene to start with.
There is nothing that guarantees that the selection rectangle is on the scene when the mouse button is released, since there are paths through mousePressEvent that don't add the rectangle to the scene. I'm not even sure if you are guaranteed to get a press event preceding each release event at all.
You have to only remove the rectangle if it's on the scene (and virtual is not needed, but Q_DECL_OVERRIDE is!):
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE {
QGraphicsScene::mouseReleaseEvent(event);
if (m_selectionRectangle.scene()) removeItem(m_selectionRectangle);
}
Also, the destructor of your custom scene is unnecessary. Simply add the item by value:
class MyScene : public QGraphicsScene
{
QGraphicsRectItem m_selectionRectangle;
public:
MyScene(qreal x, qreal y, qreal w, qreal h) :
m_selectionRectangle(0, 0, 1 1)
{
setSceneRect(x, y, w, h);
m_selectionRectangle.setBrush(Qt::magenta);
m_selectionRectangle.setOpacity(0.2);
}
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE {
...
}
...
};

Can't get positions of QGraphicsItems in scene

I am trying to get the positions of the graphicsitems in the scene.
But their QPointF value always remains (0,0).
I am painting when mouse-click event occurs. On debugging scene->items(), I get
(QGraphicsItem(this =0x22edff0, parent =0x0, pos =QPointF(0, 0) , z = 0 , flags = ( ) ) )
for each graphics item in scene but with different memory address.
This is my mainwindow.cpp code:
#include "mainwindow.h"
#include <QDebug>
MainWindow::MainWindow()
{
scene = new QGraphicsScene;
view = new QGraphicsView;
view->setScene(scene);
button = new QPushButton("Item");
QGridLayout *layout = new QGridLayout;
layout->addWidget(button);
layout->addWidget(view);
setLayout(layout);
connect(button, SIGNAL(clicked()), this, SLOT(createItem()));
}
void MainWindow::createItem()
{
myEntity = new Item;
scene->addItem(myEntity);
count_items();
}
void MainWindow::count_items()
{
qDebug() << scene->items().count();
qDebug() << scene->items();
}
MainWindow::~MainWindow()
{}
This is my item.cpp code:
#include "item.h"
Item::Item()
{
ClickFlag = true;
PaintFlag = false;
}
Item::~Item(){}
QRectF Item::boundingRect() const
{
// outer most edges
return QRectF(0,0,1450,1400);
}
void Item::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
if(event->button()==Qt::LeftButton){
if(ClickFlag){
x = event->pos().x();
y = event->pos().y();
PaintFlag = true;
ClickFlag = false;
}
}
}
void Item::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
if(PaintFlag){
QPen paintPen;
paintPen.setWidth(4);
pt.setX(x);
pt.setY(y);
painter->setPen(paintPen);
painter->drawPoint(x,y);
update();
}
}
I can't seem to find the position of these items correctly.
This task is supposed to be implemented in another way. For example:
Use QGraphicsScene::addEllipse to add small ellipse (which will look like a point) to the scene. Save the pointer to it in a class variable. The ellipse itself should be at the center, e.g. (-1, -1, 2, 2).
Reimplement QGraphicsScene::mousePressEvent, detect mouse clicks and call setPos for the ellipse item (or add new ellipse each time and immediately call setPos if you need multiple points).
Use QGraphicsItem::pos to get previously set positions.
Reimplementing QGraphicsItem::paint is usually an over-complication. Qt have plenty of item classes for all common needs. Just build your scene from geometric primitives, pixmaps, etc.

Resources