Re: Making Windows Busy



(Am i suffering deja vu, or has this message already been through the list? I can't find it in the archives...)


On Jan 25, 2008, at 6:08 AM, Anthony Edward Cooper wrote:

I'm writing a GTK2 Perl application and I am trying to write a routine
that will make a window busy, i.e. display the busy cursor and stop all
input events (keyboard and mouse clicks, but not move movement) from
going to the window).

This is using Perl version 5.8.5 on WhiteBox 4 respin 1 (clone of RHAS4U3).

I include the following:

[snip]
set_locale Gtk2;

This is unnecessary and deprecated with gtk+ 2.x. It's all handled under the hood.


I use the following routine to update the display when inside a callback
that is busy doing some processing:

sub gtk2_update()
{
   return if (Gtk2->main_level() == 0);
   while (Gtk2->events_pending())
   {
       Gtk2->main_iteration();
   }
}

That gets the job done, but it's preferable to restructure your code into a work queue processing small chunks in idle callbacks. Or farm the work off to a child process and communicate via pipes.

When you're using the "chew up events every so often" model, figuring out how to tell you've been asked to cancel is much more difficult and application-specific. So, all the examples below will use the traditional mainloop model, and it's an exercise for you to warp it to your app. ;-)


sub make_busy($$)
{
   my($instance, $busy) = @_;

   # Create and store the cursors if we haven't done so already.

   if (! exists($instance->{busy_cursor}))
   {
$instance->{normal_cursor} = Gtk2::Gdk::Cursor->new("left- ptr");
       $instance->{busy_cursor} = Gtk2::Gdk::Cursor->new("watch");

You really only need the watch cursor. To restore a window's default cursor, you can pass undef.

   }

   # Do it. Make the application bar grab the input when the window is
busy,
# that way we gobble up keyboard and mouse events that could muck up the
   # application state.

   if ($busy)
   {
$instance->{window}->window()->set_cursor($instance- >{busy_cursor});
       Gtk2->grab_add($instance->{appbar});

This is a somewhat nasty way to prevent keyboard and mouse input. Grabs are notoriously hard to get right, and there is no visual indication to the user that the window is inactive. See below for other options.


   }
   else
   {
       $instance->{window}->window()->
           set_cursor($instance->{normal_cursor});

See above.  This could be simply

    $instance->{window}->window()->set_cursor (undef);

       Gtk2->grab_remove($instance->{appbar});
   }
}

[snip]

I have tried to interrupt the flow of events by writing my own main
event loop but that didn't work very well. I'm sure there is an easy
answer as it is a common thing to want to do but I can't find anything
on this. Any ideas?


The normal way to do this is to set the toplevel window insensitive, e.g.:

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
#!/usr/bin/perl -w
use strict;
use Gtk2 -init;
use Glib qw(TRUE FALSE);

my $window = Gtk2::Window->new;
my $button = Gtk2::Button->new ("Click me");
$button->signal_connect (clicked => sub {
        # set the busy cursor.
        $window->window->set_cursor (Gtk2::Gdk::Cursor->new ('watch'));
        # disable user input, and make the window *look* disabled.
        $window->set_sensitive (FALSE);
        # inhibit the delete event so the window can't be closed.
        my $id = $window->signal_connect (delete_event => sub { return 1 });

        # Wait here for two seconds.
        Glib::Timeout->add (2000, sub { Gtk2->main_quit; return 0 });
        Gtk2->main;

        # re-enable input.
        $window->set_sensitive (TRUE);
        # restore the normal cursor.
        $window->window->set_cursor (undef);
        # uninhibit the delete event.
        $window->signal_handler_disconnect ($id);
});
$window->set_border_width (25);
$window->add ($button);
$window->show_all;
$window->signal_connect (destroy => sub { Gtk2->main_quit });
Gtk2->main;
__END__


Now, if your gui is more complicated, or you need a "cancel" button, then simply desensitizing the whole window won't work (because then the user couldn't click the cancel button).

There are options, of course:

1. Keep track of which widgets need to be desensitized and do only those.

2. Use a modal dialog with a progress bar. A modal child window will block all user interaction with the parent, and can have a progress bar with a cancel button. Note that i use a Timeout and Gtk2::Dialog::run(), which will block in the main loop; if you can't structure your code that way, you'll have to unroll Gtk2::Dialog::run(), which is another topic altogether.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#!/usr/bin/perl -w

use strict;
use Gtk2 -init;
use Glib qw(TRUE FALSE);

my $window = Gtk2::Window->new;
my $button = Gtk2::Button->new ("Click me");
$button->signal_connect (clicked => \&do_stuff);
$window->set_border_width (25);
$window->add ($button);
$window->show_all;
$window->signal_connect (destroy => sub { Gtk2->main_quit });
Gtk2->main;


sub do_stuff {
        my $widget = shift;
        my $toplevel = $widget->get_toplevel;

        my $dialog = Gtk2::Dialog->new ('Doing Stuff', $toplevel, [],
                                        'gtk-stop', 'cancel');
        my $progress = Gtk2::ProgressBar->new;
        $dialog->vbox->pack_start ($progress, FALSE, FALSE, 0);
        $progress->show;
        $progress->set_fraction (0);
        my $total = 4; # seconds
        my $step = 0.01; # seconds
        my $period = $total * $step;
        my $id;
        $id = Glib::Timeout->add ($period * 1000, sub {
                my $fraction = $progress->get_fraction;
                $fraction += $step;
                if ($fraction <= 1.0) {
                        $progress->set_fraction ($fraction);
                        return TRUE;
                } else {
                        $dialog->response ('ok');
                        $id = 0;
                        return FALSE;
                }
        });

        my $response = $dialog->run;

        if ($id) {
                # was stopped by user, be sure to remove timeout
                Glib::Source->remove ($id);
        } else {
                # finished on its own
        }
        $dialog->destroy;
}
__END__




--
Santa ate all the cookies!
  -- Zella's first, indignant words on Christmas morning, '07.




[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]