I would like to make an application which includes rotation widget inside a circle. I started this with the AnalogClock example in qt. I wouldd like to rotate a quarter pie (quarter circle) instead of gauge. My problem is I cannot locate the pie in the center of my circle.
Below picture, I barely locate the pi in the center of circle but I would like to make the pi bigger than below picture.
enter image description here
I realized that all paintings locate inside of rectangle like below.
enter image description here
When I change the drawPie function parameters rectangle's location change and rotates based on on edge point. I can make the ractange bigger but this time drawPie center changes too like the last visual.
enter image description here
You can check the code below. It is modifed from the AnalogClock example. I would like to draw a quarter pie and make it the rotate endlessly. Any help will be appreciated. If you have a better opinion, I would like hear that too.
AnalogClock::AnalogClock(QWidget *parent) :
QWidget(parent),
ui(new Ui::AnalogClock)
{
ui->setupUi(this);
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, QOverload<>::of(&AnalogClock::update));
timer->start(1000);
setWindowTitle(tr("Analog Clock"));
resize(200, 200);
}
void AnalogClock::paintEvent(QPaintEvent *)
{
QColor minuteColor(0, 127, 127, 191);
int side = qMin(width(), height());
QTime time = QTime::currentTime();
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.translate(width() / 2, height() / 2);
painter.scale(side / 200.0, side / 200.0);
painter.setPen(Qt::NoPen);
qreal radius=10;
qreal startAngle=0;
qreal span=60;
for (int i = 0; i < 12; ++i) {
painter.drawLine(88, 0, 96, 0);
painter.rotate(30.0);
}
painter.setBrush(minuteColor);
painter.save();
painter.rotate(6.0 * time.second() );
//painter.drawConvexPolygon(minuteHand, 3);
QRect rect( -radius, -radius, radius*10, radius*10);
painter.drawPie( rect, startAngle*16, span*16 );
// painter.fillRect(rect,QBrush(Qt::green));
painter.restore();
painter.setPen(minuteColor);
for (int j = 0; j < 60; ++j) {
painter.drawLine(92, 0, 96, 0);
painter.rotate(6.0);
}
}
The drawPie() function takes a rectangle that defines the hypothetical full circle in which the pie (slice) will be painted. In this example, we want that circle to be the same as the full clock circle - same size, same center.
Now the code has normalized the coordinate system for us, so that the clock center point is at (0, 0), and the radius is 100. So we need a rectangle that has its center point in (0,0) and sides that are 2*radius long. That is:
QRect rect(-100, -100, 200, 200);
Changing the rect in the code sample to that fixes the basic issue.
Related
I'm getting confused by the way of drawImage and scaledToHeight (or any kind of scaling) is working. Could any of you help me to understand what's going on here?
So I have the fallowing code:
auto cellX = this->parentWidget()->width() / 100;
auto cellY = this->parentWidget()->height() / 100;
QImage icon(dir.absoluteFilePath(m_viewModel.icon));
QImage scaled = icon.scaledToHeight(cellY * 40, Qt::SmoothTransformation);
painter.drawImage(cellX * 20, cellY * 20, scaled);
Now if I understand it correctly this should work as the following:
QImage QImage::scaledToHeight(int height, Qt::TransformationMode mode
= Qt::FastTransformation) const
Returns a scaled copy of the image. The returned image is scaled to
the given height using the specified transformation mode.
This function automatically calculates the width of the image so that
the ratio of the image is preserved.
If the given height is 0 or negative, a null image is returned.
and also
void QPainter::drawImage(int x, int y, const QImage &image, int sx =
0, int sy = 0, int sw = -1, int sh = -1, Qt::ImageConversionFlags
flags = Qt::AutoColor)
This is an overloaded function.
Draws an image at (x, y) by copying a part of image into the paint
device.
(x, y) specifies the top-left point in the paint device that is to be
drawn onto. (sx, sy) specifies the top-left point in image that is to
be drawn. The default is (0, 0).
(sw, sh) specifies the size of the image that is to be drawn. The
default, (0, 0) (and negative) means all the way to the bottom-right
of the image.
So in other words, scaledToHeight is going to return a new image scaled according to that specific height and drawImage is going to draw that specific image started from point X, Y which I'm going to mention down to the end of the image (because it's -1 and -1 default)
QUESTION:
As you could see already my scaled image is strictly dependent on the position of drawImage, why is that? How can I scale and draw my image properly? Or in other words WHY if I will position my image at 0 0 or not will affect how my image looks like?
UPDATE:
I have my Widget Class which looks something like this:
class AC_SpeedLevelController : public QWidget {
Q_OBJECT
protected:
void paintEvent(QPaintEvent *event) override;
private:
AC_ButtonViewModel m_viewModel{};
public:
explicit AC_SpeedLevelController(QWidget *parent);
void setupStyle(const AC_ButtonViewModel &model) override;
};
My paintEvent is going to look like:
void AC_SpeedLevelController::paintEvent(QPaintEvent *event) {
QWidget::paintEvent(event);
QPainter painter(this);
QDir dir(qApp->applicationDirPath());
dir.cd("icons");
auto cellX = this->parentWidget()->width() / 100;
auto cellY = this->parentWidget()->height() / 100;
QImage icon(dir.absoluteFilePath(m_viewModel.icon));
QImage scaled = icon.scaledToHeight(cellY * 20, Qt::SmoothTransformation);
painter.drawImage(0, 0, scaled);
}
Good Morning every one.
I create a code in QT creator using c++ language,my problem is when I draw a rectangular shapes in scene, this shapes were very close something like this.This are the x{45,45,42,40,35,35,40} and the y {68,70,68,66,66,69,69} coordinates.
When I zoom the distance between the rectangles should not be changed.
this is the part of my code that I have created for drawing the shapes:
//steps for draw the store
QGraphicsScene * scene=new QGraphicsScene();
QString st=sl[0];//sl it is a liste contains the coordiantes.
listDX=st.split(',');
st=sl[1];
listDY=st.split(',');
for(int i=0;i<listDX.length();i++)
{
if(listDX[i]!=" " && listDY[i]!=" ")
{
QGraphicsRectItem *rect=new QGraphicsRectItem();
rect->setRect(listDX[i].toInt(),listDY[i].toInt(),20,20);
QRadialGradient gradient(-3, -3, 10);
gradient.setCenter(3, 3);
gradient.setFocalPoint(3, 3);
gradient.setColorAt(1, QColor(Qt::red).light(120));
gradient.setColorAt(0, QColor(Qt::darkRed).light(120));
rect->setBrush(gradient);
rect->setPen(QPen(Qt::black, 0));
//add the item to the scene
scene->addItem(rect);
}
Thanks for the help
I am making a small game in C++11 with Qt. However, I am having some issues with scaling.
The background of my map is an image. Each pixel of that image represents a tile, on which a protagonist can walk and enemies/healthpacks can be.
To set the size of a tile, I calculat the maximum amount like so (where imageRows & imageCols is amount of pixels on x- and y-axis of the background image):
QRect rec = QApplication::desktop()->screenGeometry();
int maxRows = rec.height() / imageRows;
int maxCols = rec.width() / imageCols;
if(maxRows < maxCols){
pixSize = maxRows;
} else{
pixSize = maxCols;
}
Now that I have the size of a tile, I add the background-image to the scene (in GameScene ctor, extends from QGraphicsScene):
auto background = new QGraphicsPixmapItem();
background->setPixmap(QPixmap(":/images/map.png").scaledToWidth(imageCols * pixSize));
this->addItem(background);
Then for adding enemies (they extend from a QGraphicsPixMapItem):
Enemy *enemy = new Enemy();
enemy->setPixmap(QPixmap(":/images/enemy.png").scaledToWidth(pixSize));
scene->addItem(enemy);
This all works fine, except that on large maps images get scaled once (to a height of lets say 2 pixels), and when zooming in on that item it does not get more clear, but stays a big pixel. Here is an example: the left one is on a small map where pixSize is pretty big, the second one has a pixSize of pretty small.
So how should I solve this? In general having a pixSize based on the screen resolution is not really useful, since the QGrapicsScene is resized to fit the QGraphicsView it is in, so in the end the view still determines how big the pixels show on the screen.
MyGraphicsView w;
w.setScene(gameScene);
w.fitInView(gameScene->sceneRect(), Qt::KeepAspectRatio);
I think you might want to look at the chip example from Qt (link to Qt5 but also works for Qt4).
The thing that might help you is in the chip.cpp file:
in the paint method:
const qreal lod = option->levelOfDetailFromTransform(painter->worldTransform());
where painter is simply a QPainter and option is of type QStyleOptionGraphicsItem. This quantity gives you back a measure of the current zoom level of your QGraphicsView and thus as in the example you can adjust what is being drawn at which level, e.g.
if (lod < 0.2) {
if (lod < 0.125) {
painter->fillRect(QRectF(0, 0, 110, 70), fillColor);
return;
}
QBrush b = painter->brush();
painter->setBrush(fillColor);
painter->drawRect(13, 13, 97, 57);
painter->setBrush(b);
return;
}
[...]
if (lod >= 2) {
QFont font("Times", 10);
font.setStyleStrategy(QFont::ForceOutline);
painter->setFont(font);
painter->save();
painter->scale(0.1, 0.1);
painter->drawText(170, 180, QString("Model: VSC-2000 (Very Small Chip) at %1x%2").arg(x).arg(y));
painter->drawText(170, 200, QString("Serial number: DLWR-WEER-123L-ZZ33-SDSJ"));
painter->drawText(170, 220, QString("Manufacturer: Chip Manufacturer"));
painter->restore();
}
Does this help?
I'm pretty new to QT and I can not understand why my arc's are drawed so bad.
I have 2 problems.
First one, which I think is just normal for such drawing, is:
If I draw with a QPainterPath a straight line will be drawed on every arc, from the end of the arc to the direction of point 0,0 but not completely to 0,0 instead it is just, i think, the half way to that point...
Second one:
If I use QPainterPath or painter.drawArc the "rings" are unsemetric if i change the pen width.
I have this code which will init my Arc's.
//Edit//
Sorry forgot to provide where w and h is created.
this->getMainWidget() returns just a QWidget where my elements are drawed.
the geometry and position of the toplevel widget and one from this->getMainWidget() are the same.
QRect mainWidgetGeo = geometry();
int w = mainWidgetGeo.width();
int h = mainWidgetGeo.height();
QPen secondPen(Qt::yellow);
secondPen.setWidth(50);
circleSeconds = new Circle(this->getMainWidget());
circleSeconds->setMaxValue(60);
circleSeconds->setValue(55);
circleSeconds->setSteps(60);
circleSeconds->setMouseTracking(true);
circleSeconds->setPen(secondPen);
circleSeconds->setGeometry(QRect(0, 0, w, h));
QPen minutePen(Qt::red);
minutePen.setWidth(100);
circleMinutes = new Circle(this->getMainWidget());
circleMinutes->setMaxValue(60);
circleMinutes->setValue(50);
circleMinutes->setSteps(60);
circleMinutes->setMouseTracking(true);
circleMinutes->setPen(minutePen);
circleMinutes->setGeometry(QRect(50, 50, w-100, h-100));
QPen hourPen(Qt::green);
hourPen.setWidth(50);
circleHours = new Circle(this->getMainWidget());
circleHours->setMaxValue(12);
circleHours->setValue(45);
circleHours->setSteps(12);
circleHours->setMouseTracking(true);
circleHours->setPen(hourPen);
circleHours->setGeometry(QRect(150, 150, w-300, h-300));
This will setup 3 Arc's.
First and third one have the same pen width of 50, the second one has 100.
For completion here is the Circle class:
#include <QtGui>
#include "Circle.h"
#include <QDebug>
Circle::Circle(QWidget *parent): QWidget(parent)
{
}
void Circle::setSteps(int i)
{
this->steps = i;
}
void Circle::setValue(int i)
{
this->value = i;
repaint();
}
void Circle::setMaxValue(int i)
{
this->maxValue = i;
}
void Circle::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(this->pen);
int stepSize = 360/this->steps;
float devideValue = ((100.0/this->maxValue)*this->value)/100.0;
int roundedSize = this->steps*devideValue;
int angel = -1.0*16.0*(stepSize*roundedSize);
qDebug() << "steps: " << steps;
qDebug() << "stepSize: " << stepSize;
qDebug() << "devideValue: " << devideValue;
qDebug() << "roundedSize: " << roundedSize;
qDebug() << "stepSize*roundedSize: " << (stepSize*roundedSize);
qDebug() << "angel: " << angel;
qDebug() << "angel: " << angel;
painter.drawArc(this->pen.width()/2, this->pen.width()/2, this->geometry().width()-(this->pen.width()), this->geometry().height()-(this->pen.width()), 0, angel);
/*QPainterPath circle_path;
circle_path.arcTo(this->pen.width()/2, this->pen.width()/2, this->geometry().width()-(this->pen.width()), this->geometry().height()-(this->pen.width()), 0, angel);
painter.drawPath(circle_path);*/
}
void Circle::setPen(QPen pen)
{
this->pen = pen;
}
Also I have noticed that, if the pen width differs from other arc's the "starting point 0" is different for each pen width...
Here are the output's to get a better understanding what goes wrong.
At this image the first problem with the line issue also present. (QPainterPath)
This is the output with painter.drawArc
//Edit//
The expected result should be something like this. Please note that the green circle spanAngle is different from the 2 images above because i did the result with photoshop and it was easier with those spanAngles :)
It should make it know clear what my problem is.
After testing with drawEllipse i recognize the same behaviour that the pen width is smaller at 45 clock as at 90 clock.
Can anybody help me to get rid of those issues? I'm also happy with different solutions to get such opened rings.
best regards,
PrDatur
There are 2 issues. The first is that starting point of arc depends on pen's width. It can be easily fixed by settings pen.setCapStyle(Qt::FlatCap); for each used pen.
The second issue is unfilled space between arcs. I can't understand why is it happening. It's somehow connected with Qt's QPen/QPainter system, but I can't find any way to fix it.
However, I found a workaround. Create appropriate QPainterPath containing borders of your figure, and then use QPainter::fillPath instead of stroking with a pen.
A side task is to use QPainterPath::moveArcTo to move and stroke a line. As far as I see, It's not supported. We'll need the following helper function that will be used with QPainterPath::lineTo method:
QPointF my_find_ellipse_coords(const QRectF &r, qreal angle) {
QPainterPath path;
path.arcMoveTo(r, angle);
return path.currentPosition();
}
In paintEvent function:
double angle = -1.0*(stepSize*roundedSize); // removed '*16' here
QPainterPath path;
QRectF outer_rect(0, 0, width(), height());
QRectF inner_rect(pen.width(), pen.width(),
width() - pen.width() * 2, height() - pen.width() * 2);
path.arcMoveTo(outer_rect, 0);
path.arcTo(outer_rect, 0, angle);
path.lineTo(my_find_ellipse_coords(inner_rect, angle));
path.arcTo(inner_rect, angle, -angle);
path.lineTo(my_find_ellipse_coords(outer_rect, 0));
path.closeSubpath();
painter.fillPath(path, QBrush(pen.color()));
There are some other minor issues in your code. For circleHours you have set the value bigger than maxValue. Also you should omit this-> when accessing class members.
In case of any issues with my code, examine complete file I was using to test it.
I've got a class: mySquare which inherits from QGraphicsRectItem
added only my constructor, painter and an animation:
ANIMATION:
void mySquare::animation(mySquare *k)
{
QTimeLine *timeLine = new QTimeLine();
timeLine->setLoopCount(1);
QGraphicsItemAnimation *animation = new QGraphicsItemAnimation();
animation->setItem(k);
animation->setTimeLine(timeLine);
int value = 30;
animation->setTranslationAt(0.3, value, value);
timeLine->start();
// (*)
// x += 30;
// y += 30;
}
PAINTER:
void Klocek::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *widget)
{
bokKwadratu = (min(widget->width(), widget->height()))/5;
setRect(x * 30, y * 30, 30 - 3, 30 - 3);
QRectF rect = boundingRect();
painter->setBrush(brush);
painter->setPen(pen);
QFont font;
font.setPixelSize(bokKwadratu/3);
painter->setFont(font);
painter->drawRect(rect);
painter->drawText(rect,Qt::AlignCenter, QString::number(wartosc));
}
CONSTRUCTOR:
mySquare::mySquare(qreal x, qreal y) : QGraphicsRectItem(x * 10, y * 10, 10, 10)
{
setAcceptHoverEvents(true);
this->x = x;
this->y = y;
pen.setColor(Qt::red);
pen.setWidth(2);
brush.setColor(Qt::blue);
brush.setStyle(Qt::SolidPattern);
}
after performing animation (translation) I need to change the object coordinates so they are compatible which the situation on the screen. In other words after the translation (30, 30) I want the coordinates of the rectangle to be change (x += 30, y += 30)
my problem is that when i try to do this ( (*) fragment in the code) the triangle is put far away from its position (just as if the translation was performed twice)
My question is how to translate it and change the coordinates without such complications.
To begin with, I think you're misunderstanding the use of the function setTranslationAt in the QGraphicsItem Animation.
An animation has a normalised value over time, so can start at 0.0 and end at 1.0 (or the reverse). Therefore, by calling
animation->setTranslationAt(0.3, value, value);
You've stated that at the point when the normalised value reaches 0.3, you want the x and y position to be set to 'value'. That's fine, but you also need to set other values for the animation to occur (especially at the val of 1.0!). If you use a for loop, you can iterate through values from 0.0 to 1.0 and set where you want the position of your item to be. Take a look at the example code in the Qt help files for QGraphicsItemAnimation. The QGraphicsItemAnimation uses interpolation to work out the position of your object between the known points that you've given it. If you're interested: -
http://en.wikipedia.org/wiki/Linear_interpolation
Secondly, the item's rect is the definition of the item in its local coordinate space. So if you wanted a rect with its axis in the centre, you'd define it with x,y,w,h of (-w/2, -h/2, w, h). As these are local coordinates, they then get mapped into world coordinates in the GraphicsScene, which is where you set its actual position in the world.
Once you've setup your QGraphicsItemRect's local coordinates and world position, you can then simply paint it with drawRect and should not be setting positions in the paint function.