363. How to prevent concurrent button clicks - Part 4 - support for queuing of button clicks?

Problem

This is in response to Benjamin Smith's post titled Preventing concurrent processing.

I've presented three solutions in the following articles:

If you've followed the above articles, you will find that although we have prevented concurrent button clicks, we have also suppressed all queuing of button clicks.

Suppose you would also like to support queuing of button clicks while preventing concurrent processing of button clicks as shown below. (Note: What this means is that if you press button1, followed by button2, and then button1 in rapid sucession, you will find that button2-click and button1-click will wait for their turn in the queue, while the first button1-click finishes its execution.)

How to prevent concurrent button clicks - Part 4 - support for queuing of button clicks?


Solution

  • If we receive a button click when there's currently a process running, we queue this click in the global variable $queue_clicks and "gobble" up this signal with GObject::emit_stop_by_name()
  • When a process finishes running, we first check if there are any outstanding button clicks in the queue. If there is, we retrieve the next-in-queue, and manually generate a 'clicked' signal with GtkButton::clicked().

Important Note: This only works for PHP-GTK2 compliled with gtk+ v2.10 and above. If you are using an older version, for linux, you may follow the step-by-step instructions to recompile php-gtk2 with gtk+ v2.10. For windows, please refer to How to install php gtk2 on windows? You may also want to take a look here to see some of the new exciting PHP-GTK2 Functionalities.


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   
50   
51   
52   
53   
54   
55   
56   
57   
58   
59   
60   
63   
64   
65   
66   
68   
69   
70   
71   
72   
73   
74   
75   
76   
77   
78   
79   
80   
81   
82   
84   
85   
86   
87   
88   
89   
90   
91   
92   
94   
95   
96   
97   
98   
99   
100   
102   
103   
104   
107   
108   
109   
110   
111   
112   
<?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("       Prevent concurrent button clicks\n".
"Part 4 - allow queueing of button clicks");
$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);
$vbox->pack_start(new GtkLabel(), 0);

$vbox->pack_start($status_queue = new GtkLabel(), 0);

$vbox->pack_start($hbox = new GtkHBox(), 0);
$hbox->pack_start($vbox1 = new GtkVBox(), 0);
$hbox->pack_start(new GtkVBox());
$hbox->pack_start($vbox2 = new GtkVBox(), 0);

$vbox1->pack_start($button[1] = new GtkButton('Button 1'), 0);
$vbox1->pack_start($status11 = new GtkLabel(), 0);
$vbox1->pack_start($status12 = new GtkLabel(), 0);
$vbox1->pack_start($progress1 = new GtkProgressBar(), 0);
$progress1->set_orientation(Gtk::PROGRESS_LEFT_TO_RIGHT);
$handler[1] = $button[1]->connect('clicked', 'on_button', 1,
    $progress1, $status11, $status12);
$num_clicks[1] = 0;

$vbox2->pack_start($button[2] = new GtkButton('Button 2'), 0);
$vbox2->pack_start($status21 = new GtkLabel(), 0);
$vbox2->pack_start($status22 = new GtkLabel(), 0);
$vbox2->pack_start($progress2 = new GtkProgressBar(), 0);
$progress2->set_orientation(Gtk::PROGRESS_LEFT_TO_RIGHT);
$handler[2] = $button[2]->connect('clicked', 'on_button', 2,
    $progress2, $status21, $status22);
$num_clicks[2] = 0;

$in_progress = 0;
$queue_clicks = array(); // note 1

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

function on_button($widget, $id, $progress, $status1, $status2) {
    global $handler, $button;
    global $in_progress;
    if ($in_progress) {
        global $queue_clicks, $status_queue;
        array_push($queue_clicks, $id); // note 2
        $status_queue->set_text('In queue: '.implode($queue_clicks, ' '));
        $widget->emit_stop_by_name ('clicked'); // note 3
        return true;
    }

    $in_progress = 1;

    global $num_clicks, $progress_count;
    ++$num_clicks[$id];
    $progress_count[$id][$num_clicks[$id]] = 0;
    $status1->set_text("Total number of clicks ($id): {$num_clicks[$id]}");
    Gtk::idle_add('update_progressbar', $progress, $id, $num_clicks[$id],
        $status1, $status2);
}

function update_progressbar($progress, $id, $num_clicks, $status1, $status2) {
    $max = 1000;
    global $progress_count;
    ++$progress_count[$id][$num_clicks];
    $count = $progress_count[$id][$num_clicks];
    $percent_complete = $count/$max;
    $percent_complete2 = number_format($count/$max*100, 0);
    $progress->set_fraction($percent_complete);
    $progress->set_text($percent_complete2.'% Complete');
    while (Gtk::events_pending()) {Gtk::main_iteration();}
    $status2->set_text("Now running click #$num_clicks");
    if ($count < $max) { // task completed?
        return true; // not yet!
    } else {
        global $num_clicks;
        --$num_clicks[$id];
        $status1->set_text("Total number of clicks: {$num_clicks[$id]}");
        while (Gtk::events_pending()) {Gtk::main_iteration();}

        global $in_progress;
        $in_progress = 0;

        global $queue_clicks, $button, $status_queue;
        if (count($queue_clicks)>0) { // note 4
            print_r($queue_clicks);
            $next_id = array_shift($queue_clicks); // note 5
            $status_queue->set_text('In queue: '.implode($queue_clicks, ' '));
            $button[$next_id]->clicked(); // note 6
        }

        return false;
    }
}

?>

Output

As shown above.
 

Explanation

The above example make use of the code from How to process concurrent button clicks?.

What's new here:

  1. We queue the button clicks in the global variable $queue_clicks.
  2. Queue this button click.
  3. Gobble up this signal.
  4. Check if there is any outstanding button clicks.
  5. Retrieve the next-in-queue button click.
  6. Manually generate the 'clicked' signal.

Related Links

Add comment


Security code
Refresh