2
\$\begingroup\$

I'm implementing a graphical representation of an "analog clock" with moving hands (seconds, minutes and hours), using mainly2 FLTK facilities and the function Sleep(miliseconds):

main.cpp:

#include "iostream"
#include "GUI.h"
#include "Window.h"
using namespace Graph_lib;

#include "AnalogClock.h"

int main () 
try {

    Analog_clock ac (Point (700,30), "Analog clock"); 
    return gui_main ();

} catch (exception &e) {

    cerr << e.what () << endl;
    getchar();

}

AnalogClock.h:

#pragma once
#define PI 3.14159265

/*
class Analog_clock
It creates a GUI representing an analog clock
with three indicators: hour, minute, second 
and a fancy dial.
*/
class Analog_clock: public Window {
public:
    Analog_clock (Point xy, const string &label);

private:
    // background image
    Image clock_dial;

    // data representing second, minute and hour
    Line* second_indicator;
    Line* minute_indicator;
    Line* hour_indicator;

    // helper functions
    Point rotate (Point initial, Point pivot, double angle);
    void set_clock ();
    void run_clock ();

    // action functions
    void increment_second ();
    void increment_minute ();
    void increment_hour ();

    // callback functions
    /*
        typedef void* Address;

        template<class W> W& reference_to (Address pw) {
            return *static_cast<W*>(pw);
        }
    */

    static void cb_seconds (Address, Address pw) { reference_to<Analog_clock>(pw).increment_second (); }
    static void cb_minutes (Address, Address pw) { reference_to<Analog_clock>(pw).increment_minute (); }
    static void cb_hours (Address, Address pw) { reference_to<Analog_clock>(pw).increment_hour (); }
};


//------------------------------------------------------------------------------------------------------------------------
// class member implementations
/*
class constructor: Analog_clock()
It initializes a window containing
an image (dial) and three lines
(indicators), together with a 
function that runs the clock utilizing
the machine clock.
*/ 
Analog_clock::Analog_clock (Point xy, const string &label)
    : Window (xy, 480, 460 , label),
    clock_dial (Point (0, 0), "Chapter16Exercise6.gif"),
    second_indicator (nullptr),
    minute_indicator (nullptr),
    hour_indicator (nullptr)
{
    attach (clock_dial);

    set_clock ();

    run_clock ();
}

// helper function
/*
Member function: rotate ();
Use: -
It is used to rotate the clock indicators
around the center point at an angle 
corresponding to second, minute and hour.
*/
Point Analog_clock::rotate (Point initial, Point pivot, double angle) {
    return Point((cos(angle) * (initial.x - pivot.x)) - (sin(angle) * (initial.y - pivot.y)) + pivot.x,
                 (sin(angle) * (initial.x - pivot.x)) + (cos(angle) * (initial.y - pivot.y)) + pivot.y);
}

/*
Member function: set_clock ()
Use: -
It initializes the data members
representing clock indicators to
initial value: pointing at 12 o'clock.
*/
void Analog_clock::set_clock () {
    Point clock_center (x_max () / 2. - 2, y_max () / 2.);

    // set seconds
    const int second_indicator_length = 150;
    Point twelve_o_clock_s (x_max () / 2. - 2,  y_max () / 2. - second_indicator_length);
    second_indicator = new Line (clock_center, twelve_o_clock_s); 
    second_indicator->set_style (Line_style (Line_style::solid, 2));
    second_indicator->set_color (Color::red);

    // set minutes
    const int minute_indicator_length = 150;
    Point twelve_o_clock_m (x_max () / 2. - 2,  y_max() / 2. - minute_indicator_length);
    minute_indicator = new Line (clock_center, twelve_o_clock_m);
    minute_indicator->set_style (Line_style (Line_style::solid, 8));


    // set hours
    const int hour_indicator_length = 50;
    Point twelve_o_clock (x_max () / 2. - 2,  y_max () / 2. - hour_indicator_length);
    hour_indicator = new Line (clock_center, twelve_o_clock);
    hour_indicator->set_style (Line_style (Line_style::solid, 8));

    // attach in the right order
    attach (*minute_indicator);
    attach (*hour_indicator);
    attach (*second_indicator);
}

/*
Member function: run_clock ()
Use: -
It updates the clock time by
invoking the functions responsible
for the rotation of the indicators
at a specific interval defined with
the help of the functions: clock ()
and sleep ().
*/
void Analog_clock::run_clock () {
    // get real time and set the clock
    // ...

    // run the clock
    while (true) {
        for (auto i = 0; i < 60; i++) {
            for (auto i = 0; i < 60; i++) {
                cb_seconds (0, this); 
                Sleep (1000);    
            }
            cb_minutes (0, this);    
        }
        cb_hours (0, this);
    }
}

// action functions
/*
Member function: increment_second ()
Use: -
It increments the second indicator by
rotating the line that represents it
by an angle of 6 degrees.
*/
void Analog_clock::increment_second () {

    Point center = second_indicator->point (0);
    Point old_time = second_indicator->point (1);

    // rotate 6 degrees (6 degrees x 60 seconds = 360 one rotation)
    double angle_radians = ((6.) * PI) / 180.;
    Point new_time = rotate (old_time, center, angle_radians);

     // delete old indicator, create new one and attach it
     detach (*second_indicator);
     second_indicator = new Line (center, new_time);
     second_indicator->set_style (Line_style (Line_style::solid, 2));
     second_indicator->set_color (Color::red);
     attach (*second_indicator);

    // redraw ();
    draw();
}

/*
Member function: increment_minute ()
Use: -
It increments the minute indicator by
rotating the line that represents it
by an angle of 6 degrees.
*/
void Analog_clock::increment_minute () {
    Point center = minute_indicator->point (0);
    Point old_time = minute_indicator->point (1);

    // rotate 6 degrees (6 degrees x 60 seconds = 360 one rotation)
    double angle_radians = ((6.) * PI) / 180.;
    Point new_time = rotate (old_time, center, angle_radians);

    // delete old indicator, create new one and attach it
    detach (*minute_indicator);
    minute_indicator = new Line (center, new_time);
    minute_indicator->set_style (Line_style (Line_style::solid, 8));

    attach (*minute_indicator);

    // redraw ();
    draw();
}

/*
Member function: increment_hour ()
Use: -
It increments the hour indicator by
rotating the line that represents it
by an angle of 30 degrees.
*/
void Analog_clock::increment_hour () {
    Point center = hour_indicator->point (0);
    Point old_time = hour_indicator->point (1);

    // rotate 6 degrees (6 degrees x 60 seconds = 360 one rotation)
    double angle_radians = ((30.) * PI) / 180.;
    Point new_time = rotate (old_time, center, angle_radians);

    // delete old indicator, create new one and attach it
    detach (*hour_indicator);
    hour_indicator = new Line (center, new_time);
    hour_indicator->set_style (Line_style (Line_style::solid, 8));

    attach (*hour_indicator);

    // redraw ();
    draw ();
}

Result:

after 33 seconds:

enter image description here

after 1 min 24 seconds:

enter image description here

Questions:

  1. Is the above code looking good enough, are there any mistakes that could be eliminated?

  2. How to make the clock update more smoothly; without "blinking" on every second?

  3. If I use the commented functions redraw () within increment_second () / minute () / hour () the clock shows, within the specified window, only once after the loops within run_clock () are over. On the other hand, in the current implementation, i.e. using functions draw () the clock updates every second by not in the specified window and it interferes with the rest of the open windows.


1. Exercise 6 Chapter 16 from B. Stroustrup's: "C++ Programming Language: Principles and Practice."

2. All the needed files for compilation are here. The FLTK can be found here.

\$\endgroup\$
2
\$\begingroup\$

you shouldn't be using an infinite loop and sleep in a gui, instead use the timer functionality of FLTK using Fl::add_timeout and Fl::repeat_timeout

void timer_callback(void* window){
    reinterpret_cast<Analog_clock*>(window)->increment_second();
    Fl::repeat_timeout(1, timer_callback, window);
}

void Analog_clock::run_clock () {
    Fl::add_timeout(1, timer_callback, this);
}

Analog_clock::increment_second(){

    //will also handle seconds overflow so minute and hour hand can be updated
}

Many gui libraries use a single thread and callbacks to manage events and draws, if you block in one of the callbacks then it can't handle any other event while it is blocking. This was the reason redraw didn't work.

repeat_timeout is like add_timeout except that it adds t (the time until callback should happen) to the time the current callback (should have) happened instead of now(). This way the time between timer_callback getting called and it calling repeat_timeout is not a factor.

\$\endgroup\$
  • \$\begingroup\$ If I've understood right, the first function timer_callback() is some kind of wrapper for the repeat_timeout(), which is a "recursive" function calling its wrapper after it makes one second pause. Function add_timeout () starts the "recursive" process, right ? \$\endgroup\$ – Ziezi Nov 18 '15 at 15:33
  • \$\begingroup\$ It worked nicely, I just made void timer_callback(void* window), static and integrated it into the class. \$\endgroup\$ – Ziezi Nov 19 '15 at 11:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.