How to draw on QLabel with every QTimer emission - c++

My goal is to draw on QLabel with every QTimer emission.
Here's how i'm trying to achieve this:
so, timer is created by triggering an action.
I want measures, parameters of which are updated as user inputs data to the dialog, to be drawn with every timer emission.
void ImageViewer::on_measuresAct_triggered()
{
dialog = new Measures;
dialog->show();
Ymax = Origin = Xmax = QPoint(30,30);
measuresflag = true;
pixtemp = imageLabel->pixmap();
timer = new QTimer;
connect(timer, SIGNAL(timeout()), this, SLOT(drawontimer()));
timer->start(100);
if(!dialog->isVisible())
timer->stop();
}
Here's the slot that is to draw the measures.
void ImageViewer::drawontimer()
{
pixmap = pixtemp;
qDebug()<<"Hey there";
pix = (*pixmap);
QPainter paint(&pix);
QPen MeasurePen (Qt::magenta);
MeasurePen.setWidth(5);
QBrush MeasureBrush (Qt::magenta,Qt::SolidPattern);
paint.setPen(MeasurePen);
paint.setBrush(MeasureBrush);
paint.drawLine(Ymax,Origin);
paint.drawLine(Origin, Xmax);
QString originpoint = "(" + dialog->ui->olinex->text() + ", " + dialog->ui->oliney->text() + ")";
QString xmaxpoint = "(" + dialog->ui->xline->text() + ", " + dialog->ui->oliney->text() + ")";
QString ymaxpoint = "(" + dialog->ui->yline->text() + ", " + dialog->ui->olinex->text() + ")";
paint.setPen(QPen(Qt::green));
paint.setFont(QFont("Arial", 15, QFont::Bold));
paint.drawText(Origin.x() + 8, Origin.y() + 18, originpoint);
paint.drawText(Xmax.x() - 10, Xmax.y() - 8, xmaxpoint);
paint.drawText(Ymax.x() + 8, Ymax.y() + 8, ymaxpoint);
QPolygon poly1, poly2;
poly1 << Xmax << QPoint(Xmax.x() - 12, Xmax.y() - 6)
<< QPoint(Xmax.x() - 12, Xmax.y() + 6)<< Xmax;
poly2 << Ymax << QPoint(Ymax.x() + 6, Ymax.y() + 12) << QPoint(Ymax.x() - 6, Ymax.y() + 12) << Ymax;
// style(), width(), brush(), capStyle() and joinStyle().
QPen ArrowPen(Qt::magenta, 1);
paint.setPen(ArrowPen);
// Brush
QBrush brush;
brush.setColor(Qt::magenta);
brush.setStyle(Qt::SolidPattern);
// Fill polygon
QPainterPath path1, path2;
path1.addPolygon(poly1);
path2.addPolygon(poly2);
// Draw polygon
paint.drawPolygon(poly1);
paint.fillPath(path1, brush);
paint.drawPolygon(poly2);
paint.fillPath(path2, brush);
imageLabel->setPixmap(pix);
}
But my program crashes. I figured out where my problem is by using "Hey there" message output with QDebug. It's in
pix = (*pixmap);
line of my code. If i comment this line program doesn't crash, nothing is drawn neither.
Declaration in .h file:
const QPixmap* pixmap;
QPixmap pix;
const QPixmap * pixtemp;
"Hey there" message is written 2 times most of the time, but 1 time out of 10 it's written 3 or 4 times. That confuses even more.
So, the problem is how i draw on a label (i suppose). Is there more convinient or "right" way to draw on label, which won't lead to crash of my program.
Thanks in advance!

Do not try to draw into QLabel's pixmap or mangling with the pointers. Instead:
Create a new pixmap p on stack (local variable, no pointer) using the copy constructor
Check if your pixmap is valid (p.isNull())
Draw onto the pixmap p
Call setPixmap(p) on your label
A general note on Qt properties: Qt properties are not to be modified directly. The access to Qt properties always works through getting and setting. So, if you want to manipulate a property, you initialize a local variable with the getter, then modify your local variable, then use the setter.
A note about drawing into widgets: If you don't want to work on a copy, you can draw on a widget directly by deriving and implementing the paint() method. Furthermore, you can call update() on the widget to trigger a repaint (which will call your paint() method). This will be more efficient than using a label with a pixmap.

Related

Rendering in Vertical Layout fails to occur

I am trying to create a timeline app for visualization of certain time-sensitive requirements information and have begun experimentation with Qt since I am new to it. The problem I am having is that the rendered object will show up if I render it just basically on the main background, but when I tried to add the TimeLine objects to the vertical layout I have on my UI they refuse to render.
Here is my main window code for creating the objects:
void MainWindow::drawTimelines()
{
for(int i=0; i <= 3; i++)
{
TimeLine *tl = new TimeLine(this, this);
// ui->verticalLayout->addWidget(tl, 0, Qt::AlignLeft);
tl->lower();
QPoint * dest = new QPoint(this->width() - (this->width() / 12), i * this->height() / 4 + this->height() / 4);
QPoint * src = new QPoint(this->width() / 12, i * this->height() / 4 + this->height() / 4);
// tl->setGeometry(0, 0, this->width(), 100);
tl->setGeometry(src->x(), src->y(), this->width(), 100);
tl->updateGeometry();
QPoint *startPt = new QPoint(src->x(), 50);
QPoint *endPt = new QPoint(dest->x(), 50);
// QPoint *src = new QPoint(this->width() /8, 50);
// QPoint *dest = new QPoint(this->width() - (this->width() / 8), 50);
tl->setSrcPt(startPt);
tl->setDestPt(endPt);
tl->setNumSegments(3);
timeLineList.push_back(tl);
timeLineList.at(i)->show();
}
update();
}
Here is the maint function present in the TimeLine object itself:
void TimeLine::paintEvent(QPaintEvent * event)
{
QRectF frame(QPointF(sourcePoint->x(), sourcePoint->y()), geometry().size());
QPainter painter(this);
painter.setPen(QPen(Qt::FlatCap));
painter.drawRoundedRect(frame, 10.0, 10.0);
int translateAmount = sourcePoint->y() - window->getPainterY();
painter.translate(0, translateAmount);
// window->setPainterY(translateAmount);
painter.drawLine(sourcePoint->x(), 25, destPoint->x(), 25);
for(int i = 0; i <= numSegments; i++){
int xPoint = ((destPoint->x() - sourcePoint->x()) * i / numSegments) + sourcePoint->x();
int yPoint = 25;
painter.drawLine(xPoint, yPoint + 20, xPoint, yPoint - 20);
}
QWidget::paintEvent(event);
}
For reference, the verticalLayout is meant to have one
The TimeLines (and bounded rect boxes) do not render when I have the
// ui->verticalLayout->addWidget(tl, 0, Qt::AlignLeft);
uncommented. As you can see from a bunch of the other commented lines, I have tried numerous other things to try and render these TimeLines. I have tried:
changing reference points of the geometry to be (0,0) for the new segment of the vertical layout
changing the size of the geometry
both translating and not translating the painter
changing line thickness, type of line, etc.
even tried rendering something else simple in the vertical layout
The part that confuses me is that even the bounded rect made based on the geometry of the TimeLine frame gets cut off on the side and top of the timeline (it only shows top left corner and the top and left side-lines) even when rendered on the normal screen.

Qt5 C++ resize font based on label width

I have written what I think is a pretty good font resize algorithm that I use on labels in a QGridLayout. The algorithm works rather well on its own. However, the first time I run the algorithm, the layout hasn't been painted yet, and as such, the grid widgets which own the labels haven't been sized to the layout, which means my width() call is wrong, though I have assigned the label text prior to the resize (which doesn't matter). My question is, what is the best way to know the bounding rectangles have been created to fit the layout so my calculation is on the actual label width?
Note, this is Qt5.7, so I know that fm.width() is obsolete, but that's what's available in Raspbian.
My app is reading Sonos metadata and showing it on a 7" RPi display. I'm resizing the metadata that is too long to fit in the layout grids. So, I have a QStackedLayout with a set of layouts to show (clock when Sonos isn't playing, metadata when it is), and I frequently poll the a local Sonos server to find out if there is metadata to show. This works well. What doesn't happen is that on the first time the Sonos metadata layout is shown, the QGridLayout hasn't actually been laid out yet (I believe), so the labels are too big. At some point after the first time I fill in the metadata, the grid layout gets "shown" and the labels are then the correct size. The problem is, by then, it's all done setting metadata and the labels look funny in some cases.
int pointSize = FontSize::Default;
for (auto label : m_labels) {
QFont f = label->font();
if (label->accessibleName() == "title")
pointSize = FontSize::Title;
QFontMetrics fm(f);
if (fm.width(label->text()) > label->width()) {
float factor = (float)label->width() / (float)fm.width(label->text());
if (factor <= .6) {
factor = .6;
}
f.setPointSizeF((f.pointSize() * factor) * .9);
qDebug() << __FUNCTION__ << ": label width:" << label->width();
qDebug() << __FUNCTION__ << ": font width:" << fm.width(label->text());
qDebug() << __FUNCTION__ << ": Calculated font scaling factor to be" << (float)(factor * .9);
qDebug() << __FUNCTION__ << ": Reset font size for text\"" << label->text() << "\" to" << f.pointSize();
}
else
f.setPointSize(pointSize);
label->setFont(f);
}
On an 800x480 display, this results in the first event label width being wrong
calculateLabelFontSize : label width: 640
calculateLabelFontSize : font width: 2051
The label width the next time it's called would end up being correct with a width of 584. However, because I don't always call it a second time, and because sometimes, the Sonos metadata hiccups and causes the display to revert, it may always be wrong, or it may just be right the first time no matter what.
I've considered trying to overload my gui app paintEvent(QPaintEvent *event), but the QPaintEvent class doesn't give me a way to determine which widget called for the event, so I can't just ignore the event until I want it. At least, not that I have determined, so if that's possible, please let me know.
I've tried overloading showEvent, but that doesn't run except when the frame is first shown, so no help there.
I've considered subclassing QGridLayout to simply overload the paintEvent() and use that, but I do have an issue with that solution. There is a progress bar in this layout, which means that the paintEvent is going to fire every half second as I get metadata from the Sonos server. Since I don't know if the paintEvent is firing for the time update, or for the text being set, I get a lot of resize attempts.
I've also simply run the algorithm a second time on the next event, but it's a really weird looking graphical hiccup when the fonts realign, and I don't like it.
Last up, I may just subclass QLabel, simply to overload paintEvent, and use that custom label just for the labels that would be resizable. However, is the label in the layout painted before or after the container it lives in inside the layout? I'll be testing this today I think, but I'm not convinced it would work.
I figure this has a much easier solution though. I just can't figure it out.
I borrowed code from https://github.com/jonaias/DynamicFontSizeWidgets which provided the resize algorithm. It's nice, but iterative. I have been tinkering with a math based solution to calculate instead of just trying to fit by resizing until it works. However, that solution still doesn't work well enough to use. This isn't slow, and does what is implied. It resizes the font until it fits.
This all is done in paintEvent() because using resizeEvent() may result in either an infinite resize loop or the layout may resize itself and suddenly everything looks shifted/squished/stretched. Doing it in paintEvent means it's only called once every time you update the text, but not otherwise.
I may come back to this to try to find a math based solution. I still think this isn't the correct way, but I found something that worked well enough for now.
NewLabel.h
#ifndef NEWLABEL_H
#define NEWLABEL_H
#include <QtCore/QtCore>
#include <QtWidgets/QtWidgets>
#include <cmath>
class NewLabel : public QLabel
{
Q_OBJECT
public:
explicit NewLabel(const QString &text, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
explicit NewLabel(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
virtual ~NewLabel() {}
void setDefaultPointSize(int p)
{
m_defaultPointSize = static_cast<float>(p);
}
protected:
void paintEvent(QPaintEvent *e) override;
private:
float getWidgetMaximumFontSize(QWidget *widget, QString text);
float m_defaultPointSize;
};
#endif
NewLabel.cpp
#define FONT_PRECISION (0.5)
NewLabel::NewLabel(const QString &text, QWidget *parent, Qt::WindowFlags f) : QLabel(text, parent, f)
{
m_defaultPointSize = 12;
}
NewLabel::NewLabel(QWidget *parent, Qt::WindowFlags f) :
QLabel(parent, f)
{
m_defaultPointSize = 12;
}
void NewLabel::paintEvent(QPaintEvent *e)
{
QFont newFont = font();
float fontSize = getWidgetMaximumFontSize(this, this->text());
if (fontSize < m_defaultPointSize) {
newFont.setPointSizeF(fontSize);
setFont(newFont);
}
QLabel::paintEvent(e);
}
float NewLabel::getWidgetMaximumFontSize(QWidget *widget, QString text)
{
QFont font = widget->font();
const QRect widgetRect = widget->contentsRect();
const float widgetWidth = widgetRect.width();
const float widgetHeight = widgetRect.height();
QRectF newFontSizeRect;
float currentSize = font.pointSizeF();
float step = currentSize/2.0;
/* If too small, increase step */
if (step<=FONT_PRECISION){
step = FONT_PRECISION*4.0;
}
float lastTestedSize = currentSize;
float currentHeight = 0;
float currentWidth = 0;
if (text==""){
return currentSize;
}
/* Only stop when step is small enough and new size is smaller than QWidget */
while(step>FONT_PRECISION || (currentHeight > widgetHeight) || (currentWidth > widgetWidth)){
/* Keep last tested value */
lastTestedSize = currentSize;
/* Test label with its font */
font.setPointSizeF(currentSize);
/* Use font metrics to test */
QFontMetricsF fm(font);
/* Check if widget is QLabel */
QLabel *label = qobject_cast<QLabel*>(widget);
if (label) {
newFontSizeRect = fm.boundingRect(widgetRect, (label->wordWrap()?Qt::TextWordWrap:0) | label->alignment(), text);
}
else{
newFontSizeRect = fm.boundingRect(widgetRect, 0, text);
}
currentHeight = newFontSizeRect.height();
currentWidth = newFontSizeRect.width();
/* If new font size is too big, decrease it */
if ((currentHeight > widgetHeight) || (currentWidth > widgetWidth)){
//qDebug() << "-- contentsRect()" << label->contentsRect() << "rect"<< label->rect() << " newFontSizeRect" << newFontSizeRect << "Tight" << text << currentSize;
currentSize -=step;
/* if step is small enough, keep it constant, so it converge to biggest font size */
if (step>FONT_PRECISION){
step/=2.0;
}
/* Do not allow negative size */
if (currentSize<=0){
break;
}
}
/* If new font size is smaller than maximum possible size, increase it */
else{
//qDebug() << "++ contentsRect()" << label->contentsRect() << "rect"<< label->rect() << " newFontSizeRect" << newFontSizeRect << "Tight" << text << currentSize;
currentSize +=step;
}
}
return lastTestedSize;
}

Reimplementing QStyledItemDelegate::paint - How to obtain subelement coordinates?

A QTreeView is rendered with the help of a custom QStyledItemDelegate::paint method. The intention is to add graphical elements to the nodes, e.g. to draw (and fill) a box around the item texts. The tree items may have check boxes, or not.
The Ruby code below achieves the goal, except that I cannot obtain the coordinates of the text element. An empirical offset (x=29; y=4) serves as a workaround. The super method draws the text on top of the box.
How can I obtain the coordinates of the text element?
Is this the right approach at all, or do I have to use drawText and drawControl instead of calling the superclass paint method? In that case, how do you control the layout of the sub elements?
(This question is not Ruby specific. Answers containing C++ are welcome.)
class ItemDelegate < Qt::StyledItemDelegate
def paint(painter, option, index)
text = index.data.toString
bg_color = Qt::Color.new(Qt::yellow)
fg_color = Qt::Color.new(Qt::black)
offset = Qt::Point.new(29,4)
painter.save
painter.translate(option.rect.topLeft + offset)
recti = Qt::Rect.new(0, 0, option.rect.width, option.rect.height)
rectf = Qt::RectF.new(recti)
margin = 4
bounding = painter.boundingRect(rectf, Qt::AlignLeft, text)
tbox = Qt::RectF.new(Qt::PointF.new(-margin,0), bounding.size)
tbox.width += 2*margin
painter.fillRect(tbox, bg_color)
painter.drawRect(tbox)
painter.restore
super
end
end
Edit: Please find a self-contained example here in this Gist.
I had the same problem in C++. Unfortunately, the workaround on option.rect.* properties seems to be the only way to find the text coords.
Here the paint method of my delegate:
void ThumbnailDelegate::paint(QPainter *p_painter, const QStyleOptionViewItem &p_option, const QModelIndex &p_index) const
{
if(p_index.isValid())
{
const QAbstractItemModel* l_model = p_index.model();
QPen l_text_pen(Qt::darkGray);
QBrush l_brush(Qt::black, Qt::SolidPattern);
/** background rect **/
QPen l_pen;
l_pen.setStyle(Qt::SolidLine);
l_pen.setWidth(4);
l_pen.setBrush(Qt::lightGray);
l_pen.setCapStyle(Qt::RoundCap);
l_pen.setJoinStyle(Qt::RoundJoin);
p_painter->setPen(l_pen);
QRect l_border_rect;
l_border_rect.setX(p_option.rect.x() + 5);
l_border_rect.setY(p_option.rect.y() + 5);
l_border_rect.setWidth(p_option.rect.width() - 16);
l_border_rect.setHeight(p_option.rect.height() - 16);
QPainterPath l_rounded_rect;
l_rounded_rect.addRect(QRectF(l_border_rect));
p_painter->setClipPath(l_rounded_rect);
/** background color for hovered items **/
p_painter->fillPath(l_rounded_rect, l_brush);
p_painter->drawPath(l_rounded_rect);
/** image **/
QPixmap l_pixmap = bytearrayToPixmap(l_model->data(p_index, ImageRole).toByteArray()).scaled(150, 150, Qt::KeepAspectRatio);
QRect l_img_rect = l_border_rect;
int l_img_x = (l_img_rect.width()/2 - l_pixmap.width()/2)+l_img_rect.x();
l_img_rect.setX(l_img_x);
l_img_rect.setY(l_img_rect.y() + 12);
l_img_rect.setWidth(l_pixmap.width());
l_img_rect.setHeight(l_pixmap.height());
p_painter->drawPixmap(l_img_rect, l_pixmap);
/** label **/
QRect l_txt_rect = p_option.rect;
l_txt_rect.setX(l_border_rect.x()+5);
l_txt_rect.setY(l_border_rect.y() + l_border_rect.height() -20);
l_txt_rect.setHeight(20);
l_txt_rect.setWidth(l_txt_rect.width()-20);
QFont l_font;
l_font.setBold(true);
l_font.setPixelSize(12);
p_painter->setFont(l_font);
p_painter->setPen(l_text_pen);
QString l_text = l_model->data(p_index, TextRole).toString();
p_painter->drawText(l_txt_rect, Qt::ElideRight|Qt::AlignHCenter, l_text);
}
else
{
qWarning() << "ThumbnailDelegate::paint() Invalid index!";
}
}
I am not skilled on Ruby but, as you can see, I am using drawPath, drawPixmap and drawText.
Here is the result:
I think it is better to avoid invoking paint from the superclass, since it should be done automatically by Qt and you may break something on the UI lifecycle.

How to improve memory-consumption when drawing polygons, (poly-)lines and points in Qt?

I'm currently struggling with populating a qgraphicsscene with many (> 1Million) objects (Polygons, Lines, Points). What I've observed is, that when creating randomly 100000 Polygons, I'm already ending up withe 130MB memory consumption. (The simple example below is based on a default Qt-Project using the View.cpp of the chip-demo example)
QtGrafikTestBasic::QtGrafikTestBasic(QWidget *parent): QMainWindow(parent)
{
ui.setupUi(this);
QSplitter *h1Splitter = new QSplitter;
QSplitter *vSplitter = new QSplitter;
vSplitter->addWidget(h1Splitter);
View* view = new View("asdf");
h1Splitter->addWidget(view);
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(vSplitter);
setLayout(layout);
setCentralWidget(view);
QBrush *brush = new QBrush();
brush->setColor(Qt::blue);
brush->setStyle(Qt::SolidPattern);
QPen *pen = new QPen();
pen->setWidth(0);
QGraphicsScene *scene = new QGraphicsScene;
srand ( time(NULL) );
int m_PolyWidth = 10;
for (int i = 0 ; i < 100000; i++)
{
double lBaseX = rand() % ((int)floor(width()) - m_PolyWidth);
double lBaseY = rand() % ((int)floor(height()) - m_PolyWidth);
QPolygonF polygon;
polygon << QPointF(lBaseX, lBaseY);
polygon << QPointF(lBaseX + m_PolyWidth, lBaseY);
polygon << QPointF(lBaseX + m_PolyWidth, lBaseY + m_PolyWidth);
polygon << QPointF(lBaseX, lBaseY + m_PolyWidth);
scene->addPolygon(polygon, *pen, *brush);
}
view->view()->setScene(scene);
}
So what am I doint wrong here / where can I improve? I've read some posts of creating an own class like the chip-example, so I simply used the chip example but also there I've encountered the problem that as soon as I change the part which uniformly distributes the chips from item->setPos(QPointF(i, j)); to a random distribution item->setPos(QPointF(lBaseX, lBaseY));, the memory consumption explodes also here...
So, what is the most performant and least memory-consuming way of drawing polygons, (poly-)lines and points in Qt?
When you call addPolygon on the graphics scene, it creates a QGraphicsPolygonItem and adds that to the scene.
Each item you add has various properties that include position, orientation, brush, pen and allows the scene to handle collision detection. Also, each item is going to have its paint function called, which is quite an overhead.
The first thing to do here is to see what performance you'd gain with using a QPainterPath. Create a QPainterPath and add the polygons to that: -
QPainterPath* painterPath = new QPainterPath;
for (int i = 0 ; i < 100000; i++)
{
double lBaseX = rand() % ((int)floor(width()) - m_PolyWidth);
double lBaseY = rand() % ((int)floor(height()) - m_PolyWidth);
QPolygonF polygon;
polygon << QPointF(lBaseX, lBaseY);
polygon << QPointF(lBaseX + m_PolyWidth, lBaseY);
polygon << QPointF(lBaseX + m_PolyWidth, lBaseY + m_PolyWidth);
polygon << QPointF(lBaseX, lBaseY + m_PolyWidth);
painterPath->addPolygon(polygon);
}
If you add the painterPath to a class derived from QGraphicsItem, you can then get the painterPath to draw in the item's paint function: -
painter.drawPath(painterPath);
This will be considerably quicker. Just ensure you're not adding the polygons to the painter path inside the paint function. The paint function should do nothing, but render the item.

DrawArc are not drawen correctly (qt/c++)

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.