(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.

_______________________________________________
gtk-perl-list mailing list
[email protected]
http://mail.gnome.org/mailman/listinfo/gtk-perl-list

Reply via email to