465. How to set up a countdown timer - Part 2 - a more accurate timer?

Problem

This is in response to Nitro7's post titled "Countdown Timer".

In Part 1, I've presented a solution using Gtk::timeout_add().

However, I find that sometimes when the application or system is too busy, the timeout event doesn't always trigger at exactly one second interval. (Remember all the events always need to line up in an events queue.) As a result, the timer is not too accurate after running for a while.

In this Part 2, I present a more accurate version of the countdown timer as shown below:

How to set up a countdown timer - Part 2 - a more accurate timer?


Solution

  • We make use of the same technique as described in Part 1.
  • The only difference is that when the timer is first started, we take note of the initial time.
  • Thereafter, in the signal handler of the timeout event, we calculate the exact time elapsed since the initial time.
  • Note that in the example below, I still maintain the variable $time_left we had used in Part 1. Take a look at the command window. You can see the difference between the actual time elapsed and $time_left. You have to have a busy system, and you have to let it run for some time, before you can see any noticeable difference.

Sample Code

1   
2   
3   
4   
5   
6   
7   
8   
9   
10   
11   
12   
13   
14   
15   
16   
17   
18   
19   
20   
21   
22   
23   
24   
25   
26   
27   
28   
29   
30   
31   
32   
33   
34   
35   
36   
37   
38   
39   
40   
41   
42   
43   
44   
45   
46   
47   
48   
49   
52   
53   
54   
55   
56   
57   
58   
59   
60   
61   
62   
63   
64   
65   
66   
67   
68   
69   
70   
72   
73   
76   
77   
78   
79   
80   
81   
82   
83   
84   
87   
88   
89   
90   
91   
92   
94   
95   
96   
97   
99   
100   
101   
102   
103   
104   
105   
106   
107   
108   
109   
110   
111   
112   
113   
115   
116   
117   
118   
119   
120   
121   
122   
<?php
$window = new GtkWindow();
$window->set_title($argv[0]);
$window->set_size_request(400, 175);
$window->connect_simple('destroy', array('Gtk','main_quit'));
$window->add($vbox = new GtkVBox());

// display title
$title = new GtkLabel("      Set up a countdown timer\n".
"Part 2 - A more accurate timer");
$title->modify_font(new PangoFontDescription("Times New Roman Italic 10"));
$title->modify_fg(Gtk::STATE_NORMAL, GdkColor::parse("#0000ff"));
$title->set_size_request(-1, 40);
$vbox->pack_start($title, 0, 0);
$vbox->pack_start(new GtkLabel(), 0, 0);

$vbox->pack_start(new GtkLabel('Click the button to start the timer'), 0, 0);
$vbox->pack_start(new GtkLabel('When the time is up, an alert message is displayed.'), 0, 0);
$vbox->pack_start(new GtkLabel(), 0, 0);

$vbox->pack_start($hbox = new GtkHBox(), 0, 0);
$hbox->pack_start($button = new GtkButton('Start timer'), 1, 0);
$vbox->pack_start(new GtkLabel());
$button->connect('clicked', 'start_timer');

$window->show_all();
Gtk::main();

// display an alert dialog box
function alert($msg) {
    $dialog = new GtkDialog('Alert', null, Gtk::DIALOG_MODAL);
    $dialog->set_position(Gtk::WIN_POS_CENTER_ALWAYS);
    $top_area = $dialog->vbox;
    $top_area->pack_start($hbox = new GtkHBox());
    $stock = GtkImage::new_from_stock(Gtk::STOCK_DIALOG_WARNING,
        Gtk::ICON_SIZE_DIALOG);
    $hbox->pack_start($stock, 0, 0);
    $hbox->pack_start(new GtkLabel($msg));
    $dialog->add_button(Gtk::STOCK_OK, Gtk::RESPONSE_OK);
    $dialog->set_has_separator(false);
    $dialog->show_all();
    $dialog->run();
    $dialog->destroy();
}

function start_timer($button) {
    $do_long_task = new DoLongTask();
    $do_long_task->total_time = 1.5 * 60; // note 1
    $do_long_task->time_left = 1.5 * 60; // note 2
    $do_long_task->process_task();
    $do_long_task->timeout_ID = Gtk::timeout_add(1000,
        array(&$do_long_task, 'process_task'));
}

class DoLongTask {

    var $progress;
    var $dialog;
    var $time_left; // note 2
    var $subtask_count = 0;
    var $max_task_count = 10;
    var $timeout_ID;
    var $start_time;

    function DoLongTask() {
        // setup a dialog containing progress bar
        $dialog = new GtkDialog('Timer',
            null, Gtk::DIALOG_MODAL); // create a new dialog
        $top_area = $dialog->vbox;
        $dialog->set_size_request(200, 60);
        $this->progress = new GtkLabel();
        $this->progress->modify_font(new PangoFontDescription("Times New Roman Italic 20"));
        $top_area->pack_start($this->progress);
        $dialog->set_has_separator(false);
        $dialog->show_all(); // show the dialog
        $this->dialog = $dialog; // keep a copy of the dialog ID

        $dialog->connect('delete-event',
            array( &$this, "on_delete_event"));

        $this->start_time = microtime(1); // note 3
    }

    // this is where you process your task
    function process_task() {
        $elapsed_time = microtime(1)-$this->start_time; // note 4
        $remaining_time = $this->total_time - $elapsed_time;
        $this->progress->set_text(date('i:s', round($remaining_time)));
        echo "remaining_time = $remaining_time ($this->time_left)\n";
        --$this->time_left;
        while (Gtk::events_pending()) {Gtk::main_iteration();}

        if ($remaining_time>=0) {
            return true; // not yet!
        } else {
            alert("time's up!");
            $this->dialog->destroy(); // yes, all done. close the dialog
            Gtk::timeout_remove($this->timeout_ID);
            return false;
        }
    }

    // function that is called when user closes the timer
    function on_delete_event($widget, $event) {
        $this->dialog->destroy();
        Gtk::idle_remove($this->timeout_ID);
        // any other clean-up that you may want to do
        return true;
    }
}

?>

Output

As shown above.
 

Explanation

The above code is based on How to set up a countdown timer - Part 1?

What's new here:

  1. Set the time in seconds. Here I set it at 1.5 minutes, or 90 seconds.
  2. We maintain these variables we have used in Part 1 so that you can see a difference between the two version. (Note: Please see the command window where it shows the differencces.)
  3. Take note of the initial time.
  4. Calculate the actual time elapsed.

Related Links

Add comment


Security code
Refresh