3

I have a diary program in which user can create tasks and then add breaks to them. Each Task object has a QTime start_time, QTime end_time and vector of Breaks. Each Break has a QTime start_time and QTime end_time members, just like a Task. I want to visualize the progress on a current task by using custom QProgressBar to show the "timeline". It should be a green line separated by red chunks that represent breaks and a triangle above it to indicate current progress. Here's my top quality drawing:
enter image description here

Requirements: The triangle should smoothly move towards the end every minute or so and not hop. It also has to change its color depending if it on a red chunk or green. The line has to be resizable, but that shouldn't affect tasks or breaks time variables. User can't add multiple breaks with consequent time.
Now my question is, is this even possible? If yes, then how?
I tried to make a task without breaks to draw just a green line and a triangle without red chunks, but I immediately faced a problem with resizing. If line width increases then the triangle's "step" by minute should increase too. I tried to implement that, but didn't find much success.
Here's the code:

//class CustomProgressBar: public QProgressBar
void CustomProgressBar::paintEvent(QPaintEvent* event)
{
    setMaximum(this->width());
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);

    QPoint start_point;
    start_point.setX(0);
    start_point.setY(13);
    QPoint end_point;
    end_point.setY(13);
    end_point.setX(this->width()); //has to be resizable

    //"TimeLine"
    painter.setPen(QPen(Qt::green, 2, Qt::SolidLine, Qt::RoundCap));
    painter.drawLine(start_point, end_point);

    //Triangle
    int progress = this->value();
    QPoint triangle_start_point;
    triangle_start_point.setX(this->value() + this->width() / 15  + 1);
    triangle_start_point.setY(0);
    QPoint triangle_bot_point;
    triangle_bot_point.setX(this->value() + this->width() / 15 + 6);
    triangle_bot_point.setY(10);
    QPoint triangle_top_point;
    triangle_top_point.setX(this->value() + this->width() / 15 + 11);
    triangle_top_point.setY(0);
    QPainterPath path;
    path.moveTo(triangle_start_point);
    path.lineTo(triangle_bot_point);
    path.lineTo(triangle_top_point);
    path.lineTo(triangle_start_point);
    painter.setPen (Qt :: NoPen);
    painter.fillPath(path, QBrush(QColor (Qt::green)));
}
Chechen Itza
  • 111
  • 8

1 Answers1

2

I adjusted your paint event and extended some simple format to show breaks and runs, look at it.

QList<QPoint> CustomProgessBar::breaks()
{
    QList<QPoint> times;
    times.append(QPoint(0, 20));
    times.append(QPoint(20, 50));
    times.append(QPoint(50, 80));
    times.append(QPoint(80, 100));
    return times;
}

void CustomProgessBar::paintEvent(QPaintEvent *e)
{
    Q_UNUSED(e)
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);

    QPoint start_point;
    start_point.setX(0);
    start_point.setY(13);
    QPoint end_point;
    end_point.setY(13);
    end_point.setX(this->width());

    //"TimeLine"
    for (int i = 0; i < breaks().length(); ++i) {
        start_point.setX((int)((float)this->width() / 100 * breaks().at(i).x()));
        end_point.setX((int)((float)this->width() / 100 * breaks().at(i).y()));
        if (i % 2 == 0) {
            painter.setPen(QPen(Qt::red, 2, Qt::SolidLine, Qt::RoundCap));
        } else {
            painter.setPen(QPen(Qt::green, 2, Qt::SolidLine, Qt::RoundCap));
        }
        painter.drawLine(start_point, end_point);
    }

    //Triangle
    QPoint triangle_start_point;
    triangle_start_point.setX((int)((float)this->width() / this->maximum() * this->value()) - 5);
    triangle_start_point.setY(0);
    QPoint triangle_bot_point;
    triangle_bot_point.setX((int)((float)this->width() / this->maximum() * this->value()) + 0);
    triangle_bot_point.setY(10);
    QPoint triangle_top_point;
    triangle_top_point.setX((int)((float)this->width() / this->maximum() * this->value()) + 5);
    triangle_top_point.setY(0);
    QPainterPath path;
    path.moveTo(triangle_start_point);
    path.lineTo(triangle_bot_point);
    path.lineTo(triangle_top_point);
    path.lineTo(triangle_start_point);
    painter.setPen (Qt :: NoPen);

    for (int i = 0; i < breaks().length(); ++i) {
        int x = (int)((float)triangle_bot_point.x() * 100 / this->width());
        if (x >= breaks().at(i).x() && x <= breaks().at(i).y() && i % 2 == 0)
            painter.fillPath(path, QBrush(QColor (Qt::red)));
        if (x >= breaks().at(i).x() && x <= breaks().at(i).y() && i % 2 == 1)
            painter.fillPath(path, QBrush(QColor (Qt::green)));
    }
}

The mentioned jumps occur due to integer arithmetics. Casting to float and back fixes this behaviour.


Also do not try to link the start and stop time to tightly to the start and stop point on the graph. In the provided example I have an intermediate step which makes percentage values.

maxik
  • 1,053
  • 13
  • 34
  • Even though this works, it doesn't meet all the requirements. I'll definitely try to use "timeline" and triangle coloring parts, but for now I can't accept this answer. If I'll come up with a working implementation that uses your code, I'll post it in the edit and accept your answer. – Chechen Itza Jul 30 '16 at 10:11
  • @7Y3RPXK3ETDCNRDD Line is resizable, coloring works, no hops. Which requirements are not met? Rather giving you a complete solution with all classes nicely worked out the answer solved your problems which let you progress further. I do not get your point. – maxik Jul 30 '16 at 12:05
  • Your code solves some of the problems, but not all of them. Triangle indicates the progress from `task.start_time` to `task.end_time`, that is, it should move constant amount of times but its "step"(amount of pixels it passes each progression) should scale with line width. I also wrote that `Break`s have `start_time`, `end_time` and there is a vector of `Break`s. So red chunks can vary in size and amount. Maybe i haven't been explicit enough in my post. If that's the case then I'm sorry for confusion. But I still can't accept your answer, because the problem is not yet fully solved. – Chechen Itza Jul 30 '16 at 13:18
  • @7Y3RPXK3ETDCNRDD Look carefully at the method `breaks` which returns a `QList` containing **both**, breaks and progression. Each element has start and end which may vary individually. So this one is done. The `paint` method uses some modulo 2 operation for coloring, so every second entry is a break. This is only some simple convetion for having both states. You may also easily entangle another break in the queue, be aware that the chosen values match the maximum amount of the progress bar. And again, the triangle moves not directly one pixel but exactly one *value* of the `QProgressBar`. Done – maxik Jul 30 '16 at 17:47