Coordinate transformation

In this example, we will learn how to use coordinate transformation and a timer to create a real-time clock display.

How to do it…

To create our first graphical clock display, let's follow these steps:

  1. First, create a new Qt Widgets Application project. Then, open up mainwindow.ui and remove the menu bar, tool bar, and status bar.
  2. After that, open up mainwindow.h and include the following headers:
    #include <QTime>
    #include <QTimer>
    #include <QPainter>
  3. Then, declare the paintEvent() function, like so:
    public:
      explicit MainWindow(QWidget *parent = 0);
      ~MainWindow();
    
    virtual void paintEvent(QPaintEvent *event);
    
  4. In mainwindow.cpp, create three arrays to store the shapes of the hour hand, minute hand, and second hand, where each of the arrays contains three sets of coordinates:
    void MainWindow::paintEvent(QPaintEvent *event)
    {
      static const QPoint hourHand[3] =
      {
        QPoint(4, 4),
        QPoint(-4, 4),
        QPoint(0, -40)
      };
    
      static const QPoint minuteHand[3] =
      {
        QPoint(4, 4),
        QPoint(-4, 4),
        QPoint(0, -70)
      };
    
      static const QPoint secondHand[3] =
      {
        QPoint(2, 2),
        QPoint(-2, 2),
        QPoint(0, -90)
      };
    }
  5. After that, add the following code below the arrays to create the painter and move it to the center of the main window. Also, we adjust the size of the painter so that it fits nicely in the main window, even when the window is being resized:
    int side = qMin(width(), height());
    
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.translate(width() / 2, height() / 2);
    painter.scale(side / 250.0, side / 250.0);
  6. Once you are done with that, we will start drawing the dials by using a for loop. Each dial is rotated by an increment of 6 degrees, so 60 dials would complete a full circle. Also, the dial at every 5 minutes will look slightly longer:
    for (int i = 0; i < 60; ++i)
    {
      if ((i % 5) != 0)
        painter.drawLine(92, 0, 96, 0);
      else
        painter.drawLine(86, 0, 96, 0);
      painter.rotate(6.0);
    }
  7. Then, we proceed with drawing the hands of the clock. Each hand's rotation is calculated according to the current time and its respective unit over 360 degrees:
    QTime time = QTime::currentTime();
    
    // Draw hour hand
    painter.save();
    painter.rotate((time.hour() * 360) / 12);
    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::black);
    painter.drawConvexPolygon(hourHand, 3);
    painter.restore();
    
    // Draw minute hand
    painter.save();
    painter.rotate((time.minute() * 360) / 60);
    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::black);
    painter.drawConvexPolygon(minuteHand, 3);
    painter.restore();
    
    // Draw second hand
    painter.save();
    painter.rotate((time.second() * 360) / 60);
    painter.setPen(Qt::NoPen);
    painter.setBrush(Qt::black);
    painter.drawConvexPolygon(secondHand, 3);
    painter.restore();
  8. Last but not least, create a timer to refresh the graphics every second so that the program will work like a real clock!
    MainWindow::MainWindow(QWidget *parent) :
      QMainWindow(parent), ui(new Ui::MainWindow)
    {
      ui->setupUi(this);
    
      QTimer* timer = new QTimer(this);
      timer->start(1000);
      connect(timer, SIGNAL(timeout()), this, SLOT(update()));
    }
  9. Compile and run the program now and you should see something like this:
    How to do it…

How it works...

Each of the arrays contain three QPoint data, which form the shape of an elongated triangle. The arrays are then passed to the painter and rendered as a convex polygon using the drawConvexPolygon() function.

Before drawing each of the clock hands, we use painter.save() to save the state of the QPainter object and then proceed with drawing the hand using coordinate transformation. Once we're done with the drawing, we restore the painter to its previous state by calling painter.restore(). This function will undo all the transformations before painter.restore() so that the next clock hand will not inherit the transformations of the previous one. Without using painter.save() and painter.restore(), we will have to manually change back the position, rotation, and scale before drawing the next hand.

A good example of not using painter.save() and painter.restore() is when drawing the dials. Since each dial's rotation is an increment of 6 degrees from the previous one, we don't need to save the painter's state at all. We just have to call painter.rotate(6.0) in a loop and each dial will inherit the previous dial's rotation. We also use a modulus operator (%) to check whether the unit represented by the dial can be divided by 5. If it can, then we draw it slightly longer.

Without using a timer to constantly call the update() slot, the clock will not function properly. This is because paintEvent() will not be called by Qt when there is no change to the state of the parent widget, which in this case is the main window. Therefore, we need to manually tell Qt that we need to refresh the graphics by calling update()every second.

We used the painter.setRenderHint(QPainter::Antialiasing) function to enable anti-aliasing when rendering the clock. Without anti-aliasing, the graphics will look very jagged and pixelated:

How it works...

There's more…

The QPainter class uses the coordinate system to determine the position and size of the graphics before rendering them on screen. This information can be altered to make the graphics appear at a different position, rotation, and size. This process of altering the coordinate information of a graphic is what we called coordinate transformation. There are several types of transformation, among them are translation, rotation, scaling and shearing:

There's more…

Qt uses a coordinate system that has its origin at the top-left corner, meaning the x values increase to the right and the y values increase downwards. This coordinate system might be different from the coordinate system used by the physical device, such as a computer screen. Qt handles this automatically by using the QPaintDevice class, which maps Qt's logical coordinates to the physical coordinates.

QPainter provides four transform operations to perform different types of transformation:

  • QPainter::translate(): Offset the graphic's position by a given set of units
  • QPainter::rotate(): Rotate the graphics around the origin in a clockwise direction
  • QPainter::scale(): Offset the graphic's size by a given factor
  • QPainter::shear(): Twist the graphic's coordinate system around the origin
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.235.79