Mouse event handling in rotated QQuickItem - qt

I have a simple QQuickPaintedItem which draws a circular sector at a given position, with a given sector size and azimuth. Here is the header:
#pragma once
#include <QtQuick/QQuickPaintedItem>
#include <QColor>
#include <QPainter>
class MarkerItem : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor)
Q_PROPERTY(int azimuth READ azimuth WRITE setAzimuth)
Q_PROPERTY(int sectorSize READ sectorSize WRITE setSectorSize)
Q_PROPERTY(QPointF anchorPoint READ anchorPoint WRITE setAnchorPoint NOTIFY anchorChanged)
Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged)
public:
MarkerItem(QQuickItem *parent = nullptr);
QColor color() const;
void setColor(const QColor &color);
int azimuth() const;
void setAzimuth(int angle);
int sectorSize() const;
void setSectorSize(int span);
QPointF anchorPoint() const;
void setAnchorPoint(const QPointF &value);
bool selected();
void setSelected(const bool bVal);
public slots:
void mousePressEvent(QMouseEvent *event) override;
protected:
void paint(QPainter *painter) override;
private:
QColor mcColor;
int miAzimuth;
int miSectorSize;
QPointF mcAnchorPoint;
bool mbSelected;
const int miSectorRadius = 40;
signals:
void anchorChanged(QPointF);
void selectedChanged(bool);
};
and the implementation:
#include "markeritem.h"
#include <QSGGeometryNode>
#include <QtMath>
MarkerItem::MarkerItem(QQuickItem *parent)
: QQuickPaintedItem(parent)
{
setAcceptedMouseButtons(Qt::LeftButton);
qDebug() << "MarkerItem";
setWidth(qTan(qDegreesToRadians(30.0f))*40);
setHeight(40);
mbSelected = false;
}
void MarkerItem::paint(QPainter *painter)
{
qDebug() << " > MarkerItem paint, azimuth " << miAzimuth;
QPen pen;
QBrush brush(mcColor);
if (mbSelected)
{
pen.setColor(Qt::magenta);
pen.setWidth(2);
brush.setColor(mcColor.lighter(150));
}
else
{
pen.setColor(Qt::black);
pen.setWidth(1);
}
painter->setPen(pen);
painter->setBrush(brush);
painter->setRenderHints(QPainter::Antialiasing, true);
const QRectF rect = boundingRect();
setAnchorPoint(QPointF(rect.left()+rect.width()/2.0,rect.bottom()));
// we define a helper rectangle what we use to draw a pie,
// as the drawPie() method expect a rect, and the pie will start from the
// center of that rect
QRectF cPieRect(anchorPoint().x()-rect.height(), anchorPoint().y()-rect.height(),
rect.height()*2,rect.height()*2);
painter->drawPie(cPieRect,(90-miSectorSize/2)*16, miSectorSize * 16);
// drawing the bounding rectangle in red for visual debugging
QPen pen2(Qt::red,1);
painter->setPen(pen2);
painter->setBrush(QBrush(Qt::transparent));
painter->drawRect(rect);
setTransformOriginPoint(anchorPoint());
setRotation(miAzimuth);
}
// setters/getters
QPointF MarkerItem::anchorPoint() const
{
return mcAnchorPoint;
}
void MarkerItem::setAnchorPoint(const QPointF &value)
{
mcAnchorPoint = value;
emit anchorChanged(value);
}
void
MarkerItem::mousePressEvent(QMouseEvent *event)
{
qDebug() << "MarkerItem MousePressEvent" << event;
setSelected(!mbSelected);
}
bool
MarkerItem::selected()
{
return mbSelected;
}
void
MarkerItem::setSelected(const bool bVal)
{
if (bVal == mbSelected)
{
return;
}
mbSelected = bVal;
emit selectedChanged(mbSelected);
update();
}
QColor MarkerItem::color() const
{
return mcColor;
}
void MarkerItem::setColor(const QColor &acColor)
{
mcColor = acColor;
}
int MarkerItem::azimuth() const
{
return miAzimuth;
}
void MarkerItem::setAzimuth(int angle)
{
miAzimuth = angle;
}
int MarkerItem::sectorSize() const
{
return miSectorSize;
}
void MarkerItem::setSectorSize(int angle)
{
miSectorSize = angle;
}
And using it like:
import Marker 1.0 // this is the Markeritem class
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
Item
{
id: root
visible: true
width: 1800
height: 900
MarkerItem
{
id: m0
x: 900
y:450
color: "green"
azimuth: 0
sectorSize: 30
}
MarkerItem
{
id: m1
x: 900
y:450
color: "green"
azimuth: 120
sectorSize: 30
}
}
The QQuickPaintedItem defines a bounding rectangle which fits to the circular section to draw, and using QPainter's drawPie() method to draw the needed shape, after which by calling setRotate(), it rotates the item into the requested azimuth. I also need to handle the mouse click event to select/unselect an item. What i experience is that when the azimuth of an item is 0 (so it's not rotated), the mouse events of the bounding rectangle are received by my handler, but when the item is rotated, my item doesn't receive the mouse event from the area of the drawn item, but receives some of the mouse events outside from the bounding rectangle, from an area which seems to be at the point reflection of the original area, where the center is the anchorpoint (the tip if the circular sector, which is also the center of the rotation), but it's hard to define the area from which the events are delivered to my handler.
My assumption would be that with the "setRotation()" call, also the mouse area is rotating. Is there something I've overlooked? Is there any method to handle mouse events from a rotated item?
The item is supposed to use in a Map item via a MapQuickItem, but the issue is also reproducible without a Map item, so the map related parts are mostly omitted here.

setRotation() rotates the item so it should not be in paint since that method is called every time the item is painted, for example before the mouse event, in conclusion setRotation() should only be called in setAzimuth()
void MarkerItem::paint(QPainter *painter)
{
qDebug() << " > MarkerItem paint, azimuth " << miAzimuth;
QPen pen;
QBrush brush(mcColor);
if (mbSelected)
{
pen.setColor(Qt::magenta);
pen.setWidth(2);
brush.setColor(mcColor.lighter(150));
}
else
{
pen.setColor(Qt::black);
pen.setWidth(1);
}
painter->setPen(pen);
painter->setBrush(brush);
painter->setRenderHints(QPainter::Antialiasing, true);
const QRectF rect = boundingRect();
setAnchorPoint(QPointF(rect.center().x(),rect.bottom()));
// we define a helper rectangle what we use to draw a pie,
// as the drawPie() method expect a rect, and the pie will start from the
// center of that rect
QRectF cPieRect(anchorPoint().x()-rect.height(), anchorPoint().y()-rect.height(),
rect.height()*2,rect.height()*2);
painter->drawPie(cPieRect,(90-miSectorSize/2)*16, miSectorSize * 16);
// drawing the bounding rectangle in red for visual debugging
QPen pen2(Qt::red,1);
painter->setPen(pen2);
painter->setBrush(QBrush(Qt::transparent));
painter->drawRect(rect);
}
...
void MarkerItem::setAzimuth(int angle)
{
miAzimuth = angle;
setTransformOriginPoint(anchorPoint());
setRotation(miAzimuth);
}

OK, so turned out that the problem was with incorrectly setting the transform origin. I set the anchor point to the bottom center of the bounding rect as:
setTransformOriginPoint(anchorPoint());
where the anchor point was set earlier to
setAnchorPoint(QPointF(rect.center().x(),rect.bottom()));
If i set the transform origin explicitly to the bottom center of the item as:
setTransformOrigin(QQuickItem::Bottom);
It works as it should.

Related

MouseMoveEvent stops being called

I'm using QCustomPlot (plot_ object) on QQuickPaintedItem (SinePlot class) so I can use it in QML. In mousePressEvent I collect initial point and in mouseMoveEvent I'm making calculations to add new points and updating cursor point:
void SinePlot::mousePressEvent(QMouseEvent* event)
{
prevPoint_ = event->globalPos();
}
void SinePlot::mouseMoveEvent(QMouseEvent* event)
{
QPointF tmp = event->globalPos();
qreal prop = (prevPoint_.x() - tmp.x()) / width();
if(prop > 0)
{
data_->shiftLeft(prop);
} else {
data_->shiftRight(prop);
}
plot_->xAxis->setRange(data_->minX, data_->maxX);
...
prevPoint_ = tmp;
update();
}
I have also trying to use pos() and localPos() but it does not make any difference, here is what I got:
As you can see mouseMoveEvent stops being called after some time(before releasing) and moving cursor does not call it.
Here is minimal reproducible example:
#ifndef SINEPLOT_H
#define SINEPLOT_H
#include "qcustomplot.h"
#include <QtQuick>
#include <QDebug>
class SinePlot : public QQuickPaintedItem
{
Q_OBJECT
public:
explicit SinePlot(QQuickItem* parent=nullptr)
{
setAcceptedMouseButtons(Qt::AllButtons);
plot_ = new QCustomPlot();
plot_->setInteractions(QCP::iRangeDrag);
plot_->addGraph();
}
virtual ~SinePlot()
{
delete plot_;
}
void paint(QPainter* painter)
{
QPicture picture;
QCPPainter qcpPainter;
qcpPainter.begin(&picture);
plot_->toPainter(&qcpPainter, width(), height());
qcpPainter.end();
picture.play(painter);
};
protected:
virtual void mousePressEvent(QMouseEvent* event) {};
virtual void mouseMoveEvent(QMouseEvent* event)
{
qDebug() << "mouse move";
};
private:
QCustomPlot* plot_;
};
#endif
In my case I got "mouse move" ~10 times.

Poor rendering quality in custom QQuickPaintedItem

I have a created a small drawing application in QML, I created a small subclass of QQuickPaintedItem. Then in QML, I used a MouseArea to feed the input to my class. From there I simply store the mouse positions in a vector and then paint the points as I receive onto a QImage using QPainter (I used a simple algorithm to draw a quadratic bezier curve using the last three points in my vector). Then I call QQuickPainted::update() and in my implementation of QQuickPaintedItem::paint() I draw the image. Now the program works ok, but the problem is that the rendering of the painting is quite poor (I am already using QPainter::AntiAliasing). Below there is a picture. As you can see the curves are not very sharp and I can see the "pixels" on oblique lines (when I try the same thing with OneNote everything is smooth and nice).
Here is the a full example from my github repository if you want to test it out (the code is below as well). Is there something I can do about this?
.
#ifndef DRAWINGCANVAS_H
#define DRAWINGCANVAS_H
#include <QObject>
#include <QQuickPaintedItem>
#include <QPixmap>
#include <QPainter>
struct Outline{
QPolygonF points;
void addPoint(QPointF p){
points.append(p);
}
void clear(){
points.clear();
}
};
// a custom QQuickPainted used as a canvas in QML
class DrawingCanvas : public QQuickPaintedItem
{
Q_OBJECT
Q_PROPERTY(bool drawing READ drawing WRITE setDrawing NOTIFY drawingChanged)
Q_PROPERTY(int penWidth READ penWidth WRITE setPenWidth NOTIFY penWidthChanged)
Q_PROPERTY(QString penColor READ penColor WRITE setPenColor NOTIFY penColorChanged)
public:
explicit DrawingCanvas(QQuickItem *parent = nullptr);
bool drawing() const;
Q_INVOKABLE void initiateBuffer();
Q_INVOKABLE void penPressed(QPointF pos);
Q_INVOKABLE void penMoved(QPointF pos);
Q_INVOKABLE void penReleased();
int penWidth() const;
void paint(QPainter *painter) override;
QString penColor() const;
public slots:
void setDrawing(bool drawing);
void setPenWidth(int penWidth);
void setPenColor(QString penColor);
signals:
void drawingChanged(bool drawing);
void penWidthChanged(int penWidth);
void penColorChanged(QString penColor);
private:
void drawOnBuffer(QPointF pos);
bool m_drawing;
QPixmap m_buffer;
int m_penWidth;
QString m_penColor;
QPointF m_lastPoint;
Outline m_currentOutline;
QRect m_updateRect;
QVector<Outline> m_outlines;
bool m_outlineEraser;
};
#endif // DRAWINGCANVAS_H
#include "drawingcanvas.h"
#include <QPainter>
DrawingCanvas::DrawingCanvas(QQuickItem *parent) : QQuickPaintedItem(parent)
{
m_penWidth = 4;
}
bool DrawingCanvas::drawing() const
{
return m_drawing;
}
void DrawingCanvas::penPressed(QPointF pos)
{
setDrawing(true);
m_currentOutline.addPoint(pos);
m_lastPoint = pos;
}
void DrawingCanvas::penMoved(QPointF pos)
{
if(drawing()){
m_currentOutline.addPoint(pos);
// draw the points on the buffer
drawOnBuffer(pos);
}
m_lastPoint = pos;
}
void DrawingCanvas::penReleased()
{
setDrawing(false);
m_outlines.append(m_currentOutline);
m_currentOutline.clear();
m_lastPoint = QPointF();
}
// draws the actual item
void DrawingCanvas::paint(QPainter *painter)
{
painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
QPen pen;
pen.setWidth(penWidth());
pen.setColor(penColor());
painter->setPen(pen);
painter->drawPixmap(m_updateRect, m_buffer, m_updateRect);
m_updateRect = QRect();
}
// draws on the image
void DrawingCanvas::drawOnBuffer(QPointF pos)
{
QPainter bufferPainter;
if(bufferPainter.begin(&m_buffer)){
QPen pen;
pen.setWidth(penWidth());
pen.setColor(penColor());
bufferPainter.setPen(pen);
bufferPainter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
int pointsLength = m_currentOutline.points.length();
QPainterPath path;
// this will help smoothing the curves
if(pointsLength > 2){
auto previousPoint = m_currentOutline.points.at(pointsLength - 3);
auto mid1 = (m_lastPoint + previousPoint)/2;
auto mid2 = (pos + m_lastPoint)/2;
path.moveTo(mid1);
path.quadTo(m_lastPoint, mid2);
bufferPainter.drawPath(path);
}
// update the canvas
int rad = (penWidth() / 2) + 2;
auto dirtyRect = path.boundingRect().toRect().normalized()
.adjusted(-rad, -rad, +rad, +rad);
// change the canvas dirty region
if(m_updateRect.isNull()){
m_updateRect = dirtyRect;
}
else{
m_updateRect = m_updateRect.united(dirtyRect);
}
update(dirtyRect);
m_lastPoint = pos;
}
}
QString DrawingCanvas::penColor() const
{
return m_penColor;
}
int DrawingCanvas::penWidth() const
{
return m_penWidth;
}
void DrawingCanvas::setDrawing(bool drawing)
{
if (m_drawing == drawing)
return;
m_drawing = drawing;
emit drawingChanged(m_drawing);
}
void DrawingCanvas::setPenWidth(int penWidth)
{
if (m_penWidth == penWidth)
return;
m_penWidth = penWidth;
emit penWidthChanged(m_penWidth);
}
void DrawingCanvas::setPenColor(QString penColor)
{
if (m_penColor == penColor)
return;
m_penColor = penColor;
emit penColorChanged(m_penColor);
}
// initiates the QImage buffer
void DrawingCanvas::initiateBuffer()
{
qDebug() << this << "Initiating buffer" << width() << height();
m_buffer = QPixmap(width(), height());
}
In QML:
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.12
import QtQuick.Dialogs 1.3
import Drawing 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Flickable {
id: scrollView
anchors.fill: parent
contentHeight: drawingCanvas.height
DrawingCanvas {
id: drawingCanvas
width: parent.width
height: 2000
penColor: "red"
onWidthChanged: drawingCanvas.initiateBuffer()
}
}
MouseArea {
anchors.fill: parent
anchors.rightMargin: 20
onPressed: drawingCanvas.penPressed(
Qt.point(mouseX, mouseY + scrollView.contentY))
onPositionChanged: drawingCanvas.penMoved(
Qt.point(mouseX, mouseY + scrollView.contentY))
onReleased: drawingCanvas.penReleased()
}
}
Your rendering issue doesn't seem to be due to the antialising qt option but more to the smoothing of your strokes. I recommend you to modify your custom bezier smoothing techniques or to use a dedicated lib for that [0] .
Secondly, you should create a dedicated QPen in your draw methods and "play" with the QPen and QBrush options [1] if you want the "OneNote drawing feeling". The major difference I saw between the two screenshots was the brush scale dynamics (at the beginning and the end of the strokes).
0: For example, https://github.com/oysteinmyrmo/bezier
1: https://doc.qt.io/qt-5/qpen.html

Properties and QGraphicsSimpleTextItem

I was wondering if we could use properties to animate in a class which inherits QGraphicsSimpleTextItem?
I'm drawing this button :
It is made up of :
A circle, which inherits QGraphicsObject and override the geometry property
An ellipse, which basicaly is the same but takes the circle as a parent
A text, which inherits QObject and QGraphicsSimpleTextItem
For the first two, the animations works. But concerning the last one, I have the followings errors :
QPropertyAnimation: you're trying to animate a non-existing property localisation of your QObject
QPropertyAnimation: you're trying to animate a non-existing property localisation of your QObject
QPropertyAnimation: you're trying to animate a non-existing property sizePolicy of your QObject
QPropertyAnimation: you're trying to animate a non-existing property sizePolicy of your QObject
Here is my class 'MyText' :
class MyTextOk : public QObject, public QGraphicsSimpleTextItem
{
Q_PROPERTY(QPointF localisation READ localisation WRITE setLocalisation)
Q_PROPERTY(QFont sizePolicy READ sizePolicy WRITE setSizePolicy)
public:
explicit MyTextOk(QGraphicsObject *parent = 0);
~MyTextOk();
QPointF localisation() const;
void setLocalisation(const QPointF &value);
QFont sizePolicy() const;
void setSizePolicy(const QFont &value);
private:
QRectF boundingRect() const;
protected :
QPointF point;
QFont font;
};
And my .ccp
QVariant myFontInterpolator(const QFont &start, const QFont &end, qreal progress)
{
if (progress<0.5)
{
int a = (1-progress)*50 + progress*45;
QFont rt(start);
rt.setPointSize(a);
return rt;
}
else
{
int a = (1-progress)*45 + progress*50;
QFont rt(start);
rt.setPointSize(a);
return rt;
}
Q_UNUSED(end)
}
MyTextOk::MyTextOk(QGraphicsObject *parent)
: QObject(parent), QGraphicsSimpleTextItem(parent)
{
point = QPointF(-40,-45);
this->setText("Ok");
this->setPos(point);
this->setBrush(QBrush(Qt::white));
font = QFont("Colibri",50);
this->setFont(font);
qRegisterAnimationInterpolator<QFont>(myFontInterpolator);
}
MyTextOk::~MyTextOk()
{
}
QPointF MyTextOk::localisation() const
{
return point;
}
void MyTextOk::setLocalisation(const QPointF &value)
{
if(point!=value)
{
point = value;
update();
}
}
QFont MyTextOk::sizePolicy() const
{
return font;
}
void MyTextOk::setSizePolicy(const QFont &value)
{
if(font!=value)
{
font=value;
update();
}
}
QRectF MyTextOk::boundingRect() const
{
return QRectF(0,0,0,0);
}
And in my MainWindow I animate :
void MainWindow::lancerAnimBoutonRond()
{
animationBoutonRondTaille = new QPropertyAnimation(roundButton, "geometry");
animationBoutonRondTaille->setDuration(300);
animationBoutonRondTaille->setKeyValueAt(0, QRectF(-90, -90, 180, 180));
animationBoutonRondTaille->setKeyValueAt(0.5, QRectF(-85,-85,170,170));
animationBoutonRondTaille->setKeyValueAt(1, QRectF(-90, -90, 180, 180));
animationBoutonRondTaille -> start();
animationBoutonRondEllipse = new QPropertyAnimation(whiteShadow, "geometry");
animationBoutonRondEllipse->setDuration(300);
animationBoutonRondEllipse->setKeyValueAt(0,QRectF(-70,-80,140,80));
animationBoutonRondEllipse->setKeyValueAt(0.5,QRectF(-65,-75,130,90));
animationBoutonRondEllipse->setKeyValueAt(1,QRectF(-70,-80,140,80));
animationBoutonRondEllipse->start(); // These two work
animationBoutonRondOk = new QPropertyAnimation(textOk,"localisation");
animationBoutonRondOk->setDuration(300);
animationBoutonRondOk->setKeyValueAt(0,QPointF(-40,-45));
animationBoutonRondOk->setKeyValueAt(0.5,QPointF(-35, -40));
animationBoutonRondOk->setKeyValueAt(1,QPointF(-40, -45));
animationBoutonRondOk->start(); //error : QPropertyAnimation: you're trying to animate a non-existing property localisation of your QObject
animationBoutonRondOkTaille = new QPropertyAnimation(textOk,"sizePolicy");
animationBoutonRondOkTaille->setDuration(300);
animationBoutonRondOkTaille->setStartValue(QFont("Colibri",50));
animationBoutonRondOkTaille->setEndValue(QFont("Colibri",50));
animationBoutonRondOkTaille->start(); //error : 'QPropertyAnimation: you're trying to animate a non-existing property sizePolicy of your QObject'
}
I don't know if I can name my "own" properties, but I can't (?) override the font and pos properties since I inherits QGraphicsSimpleTextItem and use setFont() and setPos()
You can find all the code here if you want to try.
Thank you for your time.
Problem solved.
Q_OBJECT macro in the MyTextOk class definition was missing. After placing it, the code runs fine.
You can find a working example of my button here.

How can we make a QRubberBand semi-transparent

I have already used
setOpacity();
setAttribute(Qt:WA_TranseculentBackground:)
even i have tied all the available solution nothing has effect.
this is my code
void Physician::mouseMoveEvent(QMouseEvent *e)
{
rubberBand->hide();
bottomRight = e->pos();
QRect rect = QRect(topLeft, bottomRight);
rubberBand->setGeometry(rect);//Area Bounding
QToolTip::showText(e->globalPos(), QString("%1,%2")
.arg(rubberBand->size().width())
.arg(rubberBand->size().height()), this);
}
void Physician::mousePressEvent(QMouseEvent *e)
{
rubberBand->hide();
if(e->x()<ui->videoShowLabel->x()||e->y()<ui->videoShowLabel->y())
{
selectWithInLabel.critical(0,"Error", "Select within the LABEL !");
selectWithInLabel.setFixedSize(500, 200);
}
else{
topLeft = e->pos();
myPoint = ui->videoShowLabel->mapFromGlobal(this->mapToGlobal(e->pos()));
}
}
void Physician::mouseReleaseEvent(QMouseEvent *e){
rubberBand->setWindowOpacity(0.5);
rubberBand->show();
}
void Physician::on_manualROIRadioButton_clicked()
{
rubberBand = new RubberBand(RubberBand::Rectangle, this);
}
What should i do to make rubberBand semiTransparent
I assume you sub classed QRubberBand (RubberBand).
After calling the setWindowopacity the paint event is generated (http://doc.qt.io/qt-5/qwidget.html#windowOpacity-prop)
So redefine the paint event in RubberBand class.
Inside the paint event call "initStyleOption" (given below)
http://doc.qt.io/qt-5/qrubberband.html#initStyleOption
By calling "initStyleOption" you can set the rubber band parameters for drawing.
The real issue with making the QRubberband semi-transparent is that mplayer is painting on a window without Qt having any knowledge of it. Hence Qt itself cannot act as a compositor to generate the required effect.
One possibility would be to make the QRubberBand a top level window. That way the compositing is the responsibility of the underlying graphics system rather than Qt.
With that in mind try the following. Firstly a utility base class to manage the geometry...
class abstract_rubber_band {
public:
virtual QRect get_geometry () const
{
return(QRect(m_parent->mapFromGlobal(widget().geometry().topLeft()), widget().size()));
}
virtual void set_geometry (const QRect &rect)
{
widget().setGeometry(map_rect(rect));
}
protected:
explicit abstract_rubber_band (QWidget *parent)
: m_parent(parent)
{}
/*
* #param point Coords relative to effective parent.
*/
QPoint map_point (QPoint point) const
{
if (point.x() < 0)
point.rx() = 0;
else if (point.x() >= m_parent->width())
point.rx() = m_parent->width() - 1;
if (point.y() < 0)
point.ry() = 0;
else if (point.y() >= m_parent->height())
point.ry() = m_parent->height() - 1;
point = m_parent->mapToGlobal(point);
return(point);
}
QRect map_rect (QRect rect) const
{
return(QRect(map_point(rect.topLeft()), map_point(rect.bottomRight())));
}
private:
QWidget &widget ()
{
return(dynamic_cast<QWidget &>(*this));
}
const QWidget &widget () const
{
return(dynamic_cast<const QWidget &>(*this));
}
QWidget *m_parent;
};
Now use the above as a base of the required rubber band class...
class rubber_band: public abstract_rubber_band,
public QRubberBand {
using super = QRubberBand;
public:
/*
* #param parent Note that this isn't actually used as the
* parent widget but rather the widget to which
* this rubber_band should be confined.
*/
explicit rubber_band (QWidget *parent)
: abstract_rubber_band(parent)
, super(QRubberBand::Rectangle)
{
setAttribute(Qt::WA_TranslucentBackground, true);
}
protected:
virtual void paintEvent (QPaintEvent *event) override
{
QPainter painter(this);
painter.fillRect(rect(), QColor::fromRgbF(0.5, 0.5, 1.0, 0.25));
QPen pen(Qt::green);
pen.setWidth(5);
painter.setPen(pen);
painter.setBrush(Qt::NoBrush);
painter.drawRect(rect().adjusted(0, 0, -1, -1));
/*
* Display the current geometry in the top left corner.
*/
QRect geom(get_geometry());
painter.drawText(rect().adjusted(5, 5, 0, 0),
Qt::AlignLeft | Qt::AlignTop,
QString("%1x%2+%3+%4").arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top()));
}
};
The above rubber_band class should almost be a drop in replacement for QRubberBand. The main difference is that rather than reading/writing its geometry with geometry/setGeometry you must use get_geometry/set_geometry -- those will perform the mapping to/from global coordinates.
In your particular case create the rubber_band with...
rubberBand = new rubber_band(ui->videoShowLabel);

QGraphicsItem setShear ? (applicable for single or multiple items)

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();
}

Resources