I have redefined my own QGraphicsItem to show a LineString. I redefined this because I need to create my own boundingbox and painter redefined the abstract method.
Now I have this code:
QRectF myQGraphicsLine::boundingRect() const
{
double lx = qMin(myLine->getX(0), myLine->getX(1));
double rx = qMax(myLine->getX(0), myLine->getX(1));
double ty = qMin(-myLine->getY(0), -myLine->getY(1));
double by = qMax(-myLine->getY(0), -myLine->getY(1));
return QRectF(lx-size/2,ty, rx -lx + size, by-ty).;
}
void myQGraphicsLine::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
{
pen.setColor(Qt::red);
QLine line(myLine->getX(0), -myLine->getY(0), myLine->getX(1), -myLine->getY(1));
pen.setWidth(size);
painter->setPen(pen);
painter->setBrush(brush);
painter->drawLine(line);
}
This all work fine, but I have a little problem with the boundingRec.
If the line follows the x- or y-axis I get this result:
And in other position I get this:
and I need this:
Does anyone know any way to rotate the boundinRec? Thanks a lot!
I fixed the problem redefining QGraphicsItem::shape() method.
For use this, I created a QPoligonF with my line shape, in my case I used this function:
void myQGraphicsLine::createSelectionPolygon()
{
QPolygonF nPolygon;
QLineF line(myLine->getX(0), -myLine->getY(0), myLine->getX(1), -myLine->getY(1));
qreal radAngle = line.angle()* M_PI / 180; //This the angle of my line vs X axe
qreal dx = size/2 * sin(radAngle);
qreal dy = size/2 * cos(radAngle);
QPointF offset1 = QPointF(dx, dy);
QPointF offset2 = QPointF(-dx, -dy);
nPolygon << line.p1() + offset1
<< line.p1() + offset2
<< line.p2() + offset2
<< line.p2() + offset1;
selectionPolygon = nPolygon;
update();
}
The paint(),boundingRect() and shape() methods stayed like this:
QRectF myQGraphicsLine::boundingRect() const
{
return selectionPolygon.boundingRect();
}
void myQGraphicsLine::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
{
pen.setColor(Qt::red);
QLineF line(myLine->getX(0), -myLine->getY(0), myLine->getX(1), -myLine->getY(1));
pen.setWidth(size);
painter->setPen(pen);
painter->setBrush(brush);
painter->drawLine(line);
}
QPainterPath myQGraphicsLine::shape() const
{
QPainterPath path;
path.addPolygon(selectionPolygon);
return path;
}
Thanks all for your answers!!
A QRect or QRectf cannot be rotated. Its sides are always parallel to the vertical and horizontal axes. Think of it as a point (x, y) giving the upper left corner of the rectangle plus 'width' and 'height' members. No matter what transformation you perform on the QRect it will always interpret 'width' as a horizontal distance and 'height' as vertical distance.
You can extract the four corners of the QRect as points and then rotate those points, but there will be no way to convert the rotated points back to a QRect unless they just happen to be parallel to the sides of the viewport/window.
Related
I am doing a program to draw an arc and move/rotate it according to the position of the mouse cursor :
This image illustrates the scene when the program is first run.
The following images illustrates what is displayed after a mouse click. The mouse click happened at the top right point of the line.
After getting the coordinates of the intersection point (between the line and the circle), I want to set the position of the center of the arc to the intersection point.
But, as you can see, the arc is not where I wish it was. Strangely, when I draw a rectangle whose topLeft point is at the intersection point, it works :
I guess the problem has to be with scene/parent/item coordinates... But I can't find where :/
Here is a sample of the code : (DrawingScene inherits QGraphicsScene)
void DrawingScene::drawStates(){
m_ellipse = new QGraphicsEllipseItem(80.0, 80.0,120.0,120.0);
addItem(m_ellipse);
m_arc = new GraphicArcItem(62.0, 62.0,50.0,50.0);
m_arc->setParentItem(m_ellipse);
m_arc->setStartAngle(0);
m_arc->setSpanAngle(270 * 16);
QLineF line_vertical(140.0,80.0,140.0,200.0);
addLine(line_vertical);
QLineF line_horizontal(80.0,140.0,200.0,140.0);
addLine(line_horizontal);
QLineF line(QPointF(62.0,62.0), QPointF(140.0,140.0));
m_lineToCenter = new QGraphicsLineItem(line);
addItem(m_lineToCenter);
}
void DrawingScene::mousePressEvent(QGraphicsSceneMouseEvent *event){
p1 = event->scenePos();
QLineF lineToCenter(QPointF(140.0,140.0), p1);
m_lineToCenter->setLine(lineToCenter);
QLineF horizontalLine(QPointF(140.0,140.0),QPointF(200.0,140.0));
double angleBetweenLines = horizontalLine.angleTo(lineToCenter);
double x = 60.0 * cos(angleBetweenLines * 3.14 / 180.0);
double y = -60.0 * sin(angleBetweenLines * 3.14 / 180.0);
QPointF intersectionPoint(x,y);
QPointF topLeft = intersectionPoint + QPointF(140.0,140.0);
addRect(QRectF(topLeft, QSizeF(60.0,60.0)));
m_arc->setPos(topLeft);
}
Any help would be more than welcome :)
edit :
Working code for moving the arc :
p1 = event->scenePos();
QLineF lineToCenter(QPointF(140.0,140.0), p1);//center of circle to mouse position
double angleBetweenPositions = lineToCenter.angleTo(m_lineToCenter->line());
m_lineToCenter->setLine(lineToCenter);
QLineF horizontalLine(QPointF(140.0,140.0),QPointF(200.0,140.0));
double angleBetweenLines = horizontalLine.angleTo(lineToCenter);
double x = 60.0 * cos(angleBetweenLines * 3.14 / 180.0);
double y = -60.0 * sin(angleBetweenLines * 3.14 / 180.0);
QPointF newPoint(x,y);
QPointF ellipse_center = m_ellipse->rect().center();
QPointF intersection_point = intersection_point + ellipse_center;
GraphicArcItem *arc2 = new GraphicArcItem(intersection_point.rx()- 25.0,
intersection_point.ry() - 25.0,50.0,50.0);
addItem(arc2);
m_arc->setPos(intersection_point.rx()-85.0, intersection_point.ry() - 85.0);//why 85 ??
Code for the rotation :
m_arc->setCurrentRotation(m_arc->getCurrentRotation() + angleBetweenPositions);
m_arc->setTransformOriginPoint(m_arc->getCenter());
m_arc->setRotation(m_arc->getCurrentRotation());
Edit : Here are the key parts of the code solving the problem :
/*Return the center point of the arc in the parent coordinates*/
QPointF GraphicArcItem::getCenter(){
int xCenter = rect().x() + rect().width()/2;
int yCenter = rect().y() + rect().height()/2;
QPointF center = /*mapToParent(*/QPointF(xCenter,yCenter)/*)*/;
return center;
}
p1 = event->scenePos();
QPointF ellipse_center = m_ellipse->rect().center();
QLineF lineToCenter(ellipse_center, p1);//center of circle to mouse position
double angleBetweenPositions = lineToCenter.angleTo(m_lineToCenter->line());
QLineF horizontalLine(ellipse_center,QPointF(200.0,140.0));
double angleBetweenLines = horizontalLine.angleTo(lineToCenter);
double x = 60.0 * cos(angleBetweenLines * 3.14 / 180.0);
double y = -60.0 * sin(angleBetweenLines * 3.14 / 180.0);
QPointF newPoint(x,y);
QPointF intersection_point = newPoint + ellipse_center;
m_arc->setPos(intersection_point.rx() - 85.0, intersection_point.ry() - 85.0);
m_arc->setCurrentRotation(angleBetweenPositions);
QPointF rotation_center = m_arc->mapFromItem(m_arc, m_arc->getCenter());
m_arc->setTransformOriginPoint(rotation_center);
m_arc->setRotation(m_arc->getCurrentRotation());
The rectangle and the arc have different parents (the scene is the parent of the rectangle and m_ellipse of the arc, hence the references of their coordinates are different. To test it just add a new arc/circle (different from m_arc and without using setParentItem(m_ellipse);) to the scene - it should have the correct screen position. To achieve the desired result I would suggest you to play with mapTo<something>/mapFrom<something> methods. I presume that mapToParent will do the trick, but you should check it anyway.
In the project I am working now we already have the left arrow(which can be seen left bottom corner) drawn with QPaint the code is like :
const qreal x1 = left ? (rect.left() + 19) : (rect.right() - 19);
const qreal x2 = left ? (rect.left() + 12) : (rect.right() - 12);
const qreal y2 = rect.y() + rect.height() / 2.0;
const qreal y1 = y2 - 8;
const qreal y3 = y2 + 8;
painter->setPen(QPen(InternalStuff::Instance()->GetColor(ILMCTheme::ColorIconInfoBarArrow), 3, Qt::SolidLine, Qt::RoundCap));
const QVector<QPointF> points = QVector<QPointF>() << QPointF(x1, y1) << QPointF(x2, y2) << QPointF(x2, y2) << QPointF(x1, y3);
painter->setRenderHint(QPainter::Antialiasing);
painter->drawLines(points);
Now a new mock up is send to me and I have been asked to implement a focus icon which can be seen in top left corner.
Two requests are a bit different for example focus icon is filled with white color.
Now I am thinking creating a square and fill it with white than draw two lines in the middle of it. Is it a good way to go? Did anyone done something like this before and has samples?
If anyone need something like that the code below created a similar shape to the one at the top left of the picture in the question
QRectF newRec = rect;
painter->save();
newRec.setSize( newRec.size() / 20 * 11 );
painter->setBrush(Qt::white);
QColor colorRect( LMCTheme::Instance()->GetColor(InternalStuff::ColorIconInfoBarArrow));
painter->setPen(QPen( colorRect, 3, Qt::SolidLine, Qt::RoundCap));
painter->translate( rect.width()/2, rect.height()/10 );
painter->rotate(45);
painter->setRenderHint(QPainter::Antialiasing);
painter->drawRect(newRec);
painter->restore();
QColor color(LMCTheme::Instance()->GetColor(InternalStuff::ColorIconInfoBarBackground));
painter->setRenderHint(QPainter::Antialiasing);
painter->setCompositionMode(QPainter::CompositionMode_Clear);
painter->setPen(QPen(color, 3, Qt::SolidLine, Qt::RoundCap));
painter->drawLine( QLine(QPoint(rect.width()/2, rect.height()/10 ), QPoint(rect.width()/2, rect.height() - rect.height()/10 ) ) );
painter->drawLine( QLine(QPoint(rect.width()/10, rect.height()/2), QPoint(rect.width()*9/10, rect.height()/2)) );
I am using Qt 5.6, I want to draw a number of text labels around a circle and rotate the text labels to orientate the text according to its position around the circle, so 12 o'clock would have a 0 degree rotation, 3 o'clock would be rotated 90 degrees, 6 o'clock would be rotated 180 degrees etc.
I am aligning the text around its centre position:
void drawText(QPainter* pobjPainter, qreal fltX, qreal fltY
,int intFlags, const QString* pobjText) {
const qreal fltSize = 32767.0;
QPointF ptfCorner(fltX, fltY - fltSize);
if ( (intFlags & Qt::AlignHCenter) == Qt::AlignHCenter ) {
ptfCorner.rx() -= fltSize / 2.0;
} else if ( (intFlags & Qt::AlignRight) == Qt::AlignRight ) {
ptfCorner.rx() -= fltSize;
}
if ( (intFlags & Qt::AlignVCenter) == Qt::AlignVCenter ) {
ptfCorner.ry() += fltSize / 2.0;
} else if ( (intFlags & Qt::AlignTop) == Qt::AlignTop ) {
ptfCorner.ry() += fltSize;
}
QRectF rctPos(ptfCorner, QSizeF(fltSize, fltSize));
pobjPainter->drawText(rctPos, intFlags, *pobjText);
}
I would like to apply a rotation to the text.
I would like to reproduce something similar to what is shown:
http://www.informit.com/articles/article.aspx?p=1405545&seqNum=2
It seems that the rotate function rotates the entire painter canvas, so the coordinates must take into account the rotation which is really giving me a hard time. I want to position the text around the ellipse and then rotate it, how do I know what the coordinates should be?
Sticking with the clock example you could try something like...
virtual void paintEvent (QPaintEvent *event) override
{
QPainter painter(this);
double radius = std::min(width(), height()) / 3;
for (int i = 0; i < 12; ++i) {
int numeral = i + 1;
double radians = numeral * 2.0 * 3.141592654 / 12;
/*
* Calculate the position of the text centre as it would be required
* in the absence of a transform.
*/
QPoint pos = rect().center() + QPoint(radius * std::sin(radians), -radius * std::cos(radians));
/*
* Set up the transform.
*/
QTransform t;
t.translate(pos.x(), pos.y());
t.rotateRadians(radians);
painter.setTransform(t);
/*
* Specify a huge bounding rectangle centred at the origin. The
* transform should take care of position and orientation.
*/
painter.drawText(QRect(-(INT_MAX / 2), -(INT_MAX / 2), INT_MAX, INT_MAX), Qt::AlignCenter, QString("%1").arg(numeral));
}
}
I've got a "canvas" that the user can draw pixels, etc. onto. It works well, but my zoom functionality currently uses the same origin regardless of the position of the mouse. I'd like to implement functionality like that of Google Maps' zoom behaviour:
That is, the zoom's origin should always be the position of the mouse cursor.
What I currently have is not exactly right...
My attempts have mostly been stabs in the dark, but I've also tried using the code from this answer without success.
main.cpp:
#include <QGuiApplication>
#include <QtQuick>
class Canvas : public QQuickPaintedItem
{
Q_OBJECT
public:
Canvas() :
mTileWidth(25),
mTileHeight(25),
mTilesAcross(10),
mTilesDown(10),
mOffset(QPoint(400, 400)),
mZoomLevel(1)
{
}
void paint(QPainter *painter) override {
painter->translate(mOffset);
const int zoomedTileWidth = mTilesAcross * mZoomLevel;
const int zoomedTileHeight = mTilesDown * mZoomLevel;
const int zoomedMapWidth = qMin(mTilesAcross * zoomedTileWidth, qFloor(width()));
const int zoomedMapHeight = qMin(mTilesDown * zoomedTileHeight, qFloor(height()));
painter->fillRect(0, 0, zoomedMapWidth, zoomedMapHeight, QColor(Qt::gray));
for (int y = 0; y < mTilesDown; ++y) {
for (int x = 0; x < mTilesAcross; ++x) {
const QRect rect(x * zoomedTileWidth, y * zoomedTileHeight, zoomedTileWidth, zoomedTileHeight);
painter->drawText(rect, QString::fromLatin1("%1, %2").arg(x).arg(y));
}
}
}
protected:
void wheelEvent(QWheelEvent *event) override {
const int oldZoomLevel = mZoomLevel;
mZoomLevel = qMax(1, qMin(mZoomLevel + (event->angleDelta().y() > 0 ? 1 : -1), 30));
const QPoint cursorPosRelativeToOffset = event->pos() - mOffset;
if (mZoomLevel != oldZoomLevel) {
mOffset.rx() -= cursorPosRelativeToOffset.x();
mOffset.ry() -= cursorPosRelativeToOffset.y();
// Attempts based on https://stackoverflow.com/a/14085161/904422
// mOffset.setX((event->pos().x() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.x()));
// mOffset.setY((event->pos().y() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.y()));
// mOffset.setX((cursorPosRelativeToOffset.x() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.x()));
// mOffset.setY((cursorPosRelativeToOffset.y() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.y()));
update();
}
}
void keyReleaseEvent(QKeyEvent *event) override {
static const int panDistance = 50;
switch (event->key()) {
case Qt::Key_Left:
mOffset.rx() -= panDistance;
update();
break;
case Qt::Key_Right:
mOffset.rx() += panDistance;
update();
break;
case Qt::Key_Up:
mOffset.ry() -= panDistance;
update();
break;
case Qt::Key_Down:
mOffset.ry() += panDistance;
update();
break;
}
}
private:
const int mTileWidth;
const int mTileHeight;
const int mTilesAcross;
const int mTilesDown;
QPoint mOffset;
int mZoomLevel;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<Canvas>("App", 1, 0, "Canvas");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.5
import QtQuick.Window 2.2
import App 1.0 as App
Window {
visible: true
width: 1200
height: 900
title: qsTr("Hello World")
Shortcut {
sequence: "Ctrl+Q"
onActivated: Qt.quit()
}
App.Canvas {
focus: true
anchors.fill: parent
}
}
What am I doing wrong in the wheelEvent() function?
You have a rectangle R = [x_0, x_0 + w] x [y_0, y_0 + h] with absolute coordinates. When you map it to a widget (another rectangle), you apply some transformation T to an area W of R. This transformation is linear with offset:
Values of a_x, b_x, a_y, b_y are calculated to satisfy some simple conditions, you have already done it.
You also have a cursor (x_c, y_c) in R. It's coordinates in W are T(x_c, y_c). Now you want to apply another transformation ,
changing scale coefficients a_x, a_y to known a_x', a_y' with following condition: you want your cursor to point at the same coordinates (x_c, y_c) in R. I.e. T'(x_c, y_c) = T(x_c, y_c) — the same point in relative coordinates points to the same position in absolute coordinates. We derive a system for unknown offsets b_x', b_y' with known rest values. It gives
Last work is to find (x_c, y_c) from widget cursor position (x_p, y_p) = T(x_c, y_c):
and to substitute it:
In your terms it is
mOffset = event->pos() - float(mZoomLevel) / float(oldZoomLevel) *
(event->pos() - mOffset);
If you want to zoom like Google maps then your origin must be at top-left corner of the image(lets say (x,y) = (0,0) and (width, height) = (100,100)) with initial zoomLevel 100.
If you want to zoom at point(40,20) with zoom Factor of 5% then,
the displacement can be calculated as-
newX = 40 - 40*(100.0/105)
newY = 20 - 20*(100.0/105)
newWidth = width - (100.0/105)
newHeight = height - (100.0/105)
then set newX, newY as your origin and change width, height to newWidth and newHeight.
By this implementation you'll be able to zoom at a particular point where the cursor is. But this implementation will not work when you move the cursor at some other positions.
I am also looking for that implementation.
i m working on a project that consist on creating an uml tool with qt and for now i have a problem with drawingtext on an arrow so this is my code :
void Arrow::paint(QPainter *painter, const QStyleOptionGraphicsItem * ,QWidget *)
{
if (myStartItem->collidesWithItem(myEndItem))
return;
QPen myPen = pen();
myPen.setColor(myColor);
qreal arrowSize = 20;
painter->setPen(myPen);
painter->setBrush(myColor);
QLineF centerLine(myStartItem->pos(), myEndItem->pos());
QPolygonF endPolygon = myEndItem->polygon();
QPointF p1 = endPolygon.first() + myEndItem->pos();
QPointF p2;
QPointF intersectPoint;
QLineF polyLine;
for (int i = 1; i < endPolygon.count(); ++i)
{
p2 = endPolygon.at(i) + myEndItem->pos();
polyLine = QLineF(p1, p2);
QLineF::IntersectType intersectType =
polyLine.intersect(centerLine, &intersectPoint);
if (intersectType == QLineF::BoundedIntersection)
break;
p1 = p2;
}
setLine(QLineF(intersectPoint, myStartItem->pos()));
double angle = ::acos(line().dx() / line().length());
if (line().dy() >= 0)
angle = (Pi * 2) - angle;
QPointF arrowP1 = line().p1() + QPointF(sin(angle + Pi / 3) * arrowSize,
cos(angle + Pi / 3) * arrowSize);
QPointF arrowP2 = line().p1() + QPointF(sin(angle + Pi - Pi / 3) * arrowSize,
cos(angle + Pi - Pi / 3) * arrowSize);
arrowHead.clear();
arrowHead << line().p1() << arrowP1 << arrowP2;
painter->drawLine(line());
//painter->drawPolygon(arrowHead);
if (isSelected())
{
painter->setPen(QPen(myColor, 1, Qt::DashLine));
QLineF myLine = line();
myLine.translate(0, 4.0);
painter->drawLine(myLine);
myLine.translate(0,-8.0);
painter->drawLine(myLine);
QPoint point = QPoint( 10, 20 );
painter->drawText( point, "You can draw text from a point..." );
}
}
and nothing happens i can draw the arrow but the text does not appear on the arrow what should i do ? please i need some help
IMO you did that wrong.
You should compose your graphics item using QGraphicsPathItem, QGraphicsPolygonItem, QGraphicsRectItem, and QGraphicsSimpleTextItem instead drawing everything yourself.
Just provide some root item responsible for managing children (lines text and polygons). It will be easier to do this properly.
Secondly your paint method is faulty. You should restore initial state of the painter!
And finally I'm pretty sure your problem is caused by incorrect implementation of boundingRect. It is quite common mistake when performing such complex drawing in paint method.