aboutsummaryrefslogblamecommitdiffstats
path: root/mail/mail-threads.c
blob: 1df871719a396b673d8c4e521a40d25946e9dcbe (plain) (tree)






























                                                                           






                                                                             




                              

                                     
                      


                                   






                                       
                                                                                                                    

                          



                                 



                            
































                                                      







                                                          
                                                      

                                                     








                                                               


                                               









                                   















                                                        






                                         
                               

                                                                                          
                                         
                                       

                                                            
                                                                                         

                                                  

                  


                                           




                                                           

                                                          
    
 

                                 




                                                           








                                                                           

                                                                                   

                                                









                                                                             


        

                                                                            




                                     

                                  
                               
                                                   

                                                   



                                                                    





















                                                                      




                                                                        







                                                                           
                                                     





















































































                                                                                         















                                                                             

                     





















                                                                                                  











                                                                         


                     




                                                   


                                  
                                             




                                                                                                  
















                                                     











                                                               
























                                                                              
                                              




                                                                         
                                             




                                                                   
                                          

                                                         
                                             








                                                                            
                                             
























                                                                    
 
 




                                                             
 



                                          


































                                                                                      
                                       

                                             
                                       

                            

                                                  

                                             
                                   







                                   

                                             







                                                                                         
                       


                        
 



                                                                  

                                   
                                           
                                                                      

                                                                                                

         






                                                                
                      
                                                                                      
                                                                                          
                              

                        
                                                                                                      
                              

                       


                                                                                              
                              

                       


                                                                                               
                              


                                                                      

                                                   
                              




                                         
                                                

                      
                                  
                              
                      
 


                                                                      
 
                      


                                                     


















                                                                
                              





                                                                             
                            



























                                                                                   
 

   















                                                                                                            
                                
                                  












                                                              
                               




                                    







                                                         



                                                                       

                                                                                         







                                                                              




                                            
                                        
                                                        
         
 
 








                                                           
 




                                                                                        

                                                                       

                                       
 
                      
                                 
                               


                                    











































                                                                                                                 
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 * Author :
 *  Peter Williams (peterw@helixcode.com)
 *
 *  Copyright 2000, Helix Code, Inc. (http://www.helixcode.com)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
 *
 */

#include <config.h>

#include <string.h>
#include <glib.h>
#include "mail.h"
#include "mail-threads.h"

/* FIXME TODO: Do we need operations that don't get a progress window because
 * they're quick, but we still want camel to be locked? We need some kind
 * of flag to mail_operation_try, but then we also need some kind of monitor
 * to open the window if it takes more than a second or something. That would
 * probably entail another thread....
 */

/**
 * A function and its userdata
 **/

typedef struct closure_s {
    void (*callback)( gpointer );
    void (*cleanup)( gpointer );
    gpointer data;
    
    gchar *prettyname;
    /* gboolean gets_window; */
} closure_t;

/**
 * A command issued through the compipe
 **/

typedef struct com_msg_s {
    enum com_msg_type_e { STARTING, PERCENTAGE, HIDE_PBAR, SHOW_PBAR, MESSAGE, PASSWORD, ERROR, FINISHED } type;
    gfloat percentage;
    gchar *message;

    void (*func)( gpointer );
    gpointer userdata;

    /* Password stuff */
    gchar **reply;
    gboolean secret;
    gboolean *success;
} com_msg_t;

/** 
 * @mail_operation_in_progress: When true, there's
 * another thread executing a major ev-mail operation:
 * fetch_mail, etc.
 *
 * Because camel is not thread-safe we work
 * with the restriction that more than one mailbox
 * cannot be accessed at once. Thus we cannot
 * concurrently check mail and move messages, etc.
 **/

static gboolean mail_operation_in_progress;

/**
 * @queue_window: The little window on the screen that
 * shows the progress of the current operation and the
 * operations that are queued to proceed after it.
 *
 * @queue_window_pending: The vbox that contains the
 * list of pending operations.
 *
 * @queue_window_message: The label that contains the
 * operation's message to the user
 **/

static GtkWidget *queue_window = NULL;
static GtkWidget *queue_window_pending = NULL;
static GtkWidget *queue_window_message = NULL;
static GtkWidget *queue_window_progress = NULL;

/**
 * @progress_timeout_handle: the handle to our timer
 * function so that we can remove it when the progress bar
 * mode changes.
 **/

static int progress_timeout_handle = -1;

/**
 * @op_queue: The list of operations the are scheduled
 * to proceed after the currently executing one. When
 * only one operation is going, this is NULL.
 **/

static GSList *op_queue = NULL;

/**
 * @compipe: The pipe through which the dispatcher communicates
 * with the main thread for GTK+ calls
 *
 * @chan_reader: the GIOChannel that reads our pipe
 *
 * @READER: the fd in our pipe that.... reads!
 * @WRITER: the fd in our pipe that.... writes!
 */

#define READER compipe[0]
#define WRITER compipe[1]

static int compipe[2] = { -1, -1 };

GIOChannel *chan_reader = NULL;

/**
 * @modal_cond: a condition maintained so that the
 * calling thread (the dispatch thread) blocks correctly
 * until the user has responded to some kind of modal
 * dialog boxy thing.
 *
 * @modal_lock: a mutex for said condition
 *
 * @modal_may_proceed: a gboolean telling us whether
 * the dispatch thread may proceed its operations.
 */

G_LOCK_DEFINE_STATIC( modal_lock );
static GCond *modal_cond = NULL;
static gboolean modal_may_proceed = FALSE;

/**
 * Static prototypes
 **/

static void create_queue_window( void );
static void dispatch( closure_t *clur );
static void *dispatch_func( void *data );
static void check_compipe( void );
static void check_cond( void );
static gboolean read_msg( GIOChannel *source, GIOCondition condition, gpointer userdata );
static void remove_next_pending( void );
static void show_error( com_msg_t *msg );
static void show_error_clicked( void );
static void get_password( com_msg_t *msg );
static void get_password_cb( gchar *string, gpointer data );
static void get_password_clicked( GnomeDialog *dialog, gint button, gpointer user_data );
static gboolean progress_timeout( gpointer data );
static void timeout_toggle( gboolean active );

/* Pthread code */
/* FIXME: support other thread types!!!! */

#ifdef G_THREADS_IMPL_POSIX

#include <pthread.h>

/**
 * @dispatch_thread: the pthread_t (when using pthreads, of
 * course) representing our dispatcher routine. Never used
 * except to make pthread_create happy
 **/

static pthread_t dispatch_thread;

/* FIXME: do we need to set any attributes for our thread? 
 * If so, we need to create a pthread_attr structure and
 * fill it in somewhere. But the defaults should be good
 * enough.
 */

#else /* defined USE_PTHREADS */
choke on this: no thread type defined
#endif

/**
 * mail_operation_try:
 * @description: A user-friendly string describing the operation.
 * @callback: the function to call in another thread to start the operation
 * @cleanup: the function to call in the main thread when the callback is finished.
 *    NULL is allowed.
 * @user_data: extra data passed to the callback
 *
 * Runs a mail operation asynchronously. If no other operation is running,
 * we start another thread and call the callback in that thread. The function
 * can then use the mail_op_ functions to perform limited UI returns, while
 * the main UI is completely unlocked.
 *
 * If an async operation is going on when this function is called again, 
 * it waits for the currently executing operation to finish, then
 * executes the callback function in another thread.
 *
 * Returns TRUE on success, FALSE on some sort of queueing error.
 **/

gboolean
mail_operation_try( const gchar *description, void (*callback)( gpointer ), 
            void (*cleanup)( gpointer ), gpointer user_data )
{
    closure_t *clur;
    g_assert( callback );

    clur = g_new( closure_t, 1 );
    clur->callback = callback;
    clur->cleanup = cleanup;
    clur->data = user_data;
    clur->prettyname = g_strdup( description );

    if( mail_operation_in_progress == FALSE ) {
        /* No operations are going on, none are pending. So
         * we check to see if we're initialized (create the
         * window and the pipes), and send off the operation
         * on its merry way.
         */

        mail_operation_in_progress = TRUE;

        check_compipe();
        create_queue_window();
        gtk_widget_show_all( queue_window );
        gnome_win_hints_set_layer( queue_window, 
                       WIN_LAYER_ONTOP );
        gnome_win_hints_set_state( queue_window, 
                       WIN_STATE_ARRANGE_IGNORE );
        gnome_win_hints_set_hints( queue_window, 
                       WIN_HINTS_SKIP_FOCUS |
                       WIN_HINTS_SKIP_WINLIST |
                       WIN_HINTS_SKIP_TASKBAR );
        gtk_widget_hide( queue_window_pending );

        dispatch( clur );
    } else {
        GtkWidget *label;

        /* Zut. We already have an operation running. Well,
         * queue ourselves up.
         *
         * Yes, g_slist_prepend is faster down here.. But we pop
         * operations off the beginning of the list later and
         * that's a lot faster.
         */

        op_queue = g_slist_append( op_queue, clur );

        /* Show us in the pending window. */
        label = gtk_label_new( description );
        gtk_misc_set_alignment( GTK_MISC( label ), 1.0, 0.5 );
        gtk_box_pack_start( GTK_BOX( queue_window_pending ), label,
                    FALSE, TRUE, 2 );

        /* If we want the next op to be on the bottom, uncomment this */
        /* 1 = first on list always (0-based) */
        /* gtk_box_reorder_child( GTK_BOX( queue_window_pending ), label, 1 ); */
        gtk_widget_show_all( queue_window_pending );
    }

    return TRUE;
}

/**
 * mail_op_set_percentage:
 * @percentage: the percentage that will be displayed in the progress bar
 *
 * Set the percentage of the progress bar for the currently executing operation.
 * Threadsafe for, nay, intended to be called by, the dispatching thread.
 **/

void mail_op_set_percentage( gfloat percentage )
{
    com_msg_t msg;

    msg.type = PERCENTAGE;
    msg.percentage = percentage;
    write( WRITER, &msg, sizeof( msg ) );
}

/**
 * mail_op_hide_progressbar:
 *
 * Hide the progress bar in the status box
 * Threadsafe for, nay, intended to be called by, the dispatching thread.
 **/

/* FIXME: I'd much rather have one of those Netscape-style progress
 * bars that just zips back and forth, but gtkprogressbar can't do
 * that, right? 
 */

void mail_op_hide_progressbar( void )
{
    com_msg_t msg;

    msg.type = HIDE_PBAR;
    write( WRITER, &msg, sizeof( msg ) );
}

/**
 * mail_op_show_progressbar:
 *
 * Show the progress bar in the status box
 * Threadsafe for, nay, intended to be called by, the dispatching thread.
 **/

void mail_op_show_progressbar( void )
{
    com_msg_t msg;

    msg.type = SHOW_PBAR;
    write( WRITER, &msg, sizeof( msg ) );
}

/**
 * mail_op_set_message:
 * @fmt: printf-style format string for the message
 * @...: arguments to the format string
 *
 * Set the message displayed above the progress bar for the currently
 * executing operation.
 * Threadsafe for, nay, intended to be called by, the dispatching thread.
 **/

void mail_op_set_message( gchar *fmt, ... )
{
    com_msg_t msg;
    va_list val;

    va_start( val, fmt );
    msg.type = MESSAGE;
    msg.message = g_strdup_vprintf( fmt, val );
    va_end( val );

    write( WRITER, &msg, sizeof( msg ) );
}

/**
 * mail_op_get_password:
 * @prompt: the question put to the user
 * @secret: whether the dialog box shold print stars when the user types
 * @dest: where to store the reply
 *
 * Asks the user for a password (or string entry in general). Waits for
 * the user's response. On success, returns TRUE and @dest contains the
 * response. On failure, returns FALSE and @dest contains the error
 * message.
 **/

gboolean mail_op_get_password( gchar *prompt, gboolean secret, gchar **dest )
{
    com_msg_t msg;
    gboolean result;

    check_cond();

    msg.type = PASSWORD;
    msg.secret = secret;
    msg.message = prompt;
    msg.reply = dest;
    msg.success = &result;
    
    (*dest) = NULL;

    G_LOCK( modal_lock );

    write( WRITER, &msg, sizeof( msg ) );
    modal_may_proceed = FALSE;

    while( modal_may_proceed == FALSE )
        g_cond_wait( modal_cond, g_static_mutex_get_mutex( &G_LOCK_NAME( modal_lock ) ) );

    G_UNLOCK( modal_lock );

    return result;
}

/**
 * mail_op_error:
 * @fmt: printf-style format string for the error
 * @...: arguments to the format string
 *
 * Opens an error dialog for the currently executing operation.
 * Threadsafe for, nay, intended to be called by, the dispatching thread.
 **/

void mail_op_error( gchar *fmt, ... )
{
    com_msg_t msg;
    va_list val;

    check_cond();

    va_start( val, fmt );
    msg.type = ERROR;
    msg.message = g_strdup_vprintf( fmt, val );
    va_end( val );

    G_LOCK( modal_lock );

    modal_may_proceed = FALSE;
    write( WRITER, &msg, sizeof( msg ) );

    while( modal_may_proceed == FALSE )
        g_cond_wait( modal_cond, g_static_mutex_get_mutex( &G_LOCK_NAME( modal_lock ) ) );

    G_UNLOCK( modal_lock );
}

/**
 * mail_operation_wait_for_finish:
 *
 * Waits for the currently executing async operations
 * to finish executing
 */

void mail_operation_wait_for_finish( void )
{
    while( mail_operation_in_progress ) {
        while( gtk_events_pending() )
            gtk_main_iteration();
    }
}

/**
 * mail_operations_are_executing:
 *
 * Returns TRUE if operations are being executed asynchronously
 * when called, FALSE if not.
 */

gboolean mail_operations_are_executing( void )
{
    return mail_operation_in_progress;
}

/* ** Static functions **************************************************** */

/**
 * create_queue_window:
 *
 * Creates the queue_window widget that displays the progress of the
 * current operation.
 */

static void
create_queue_window( void )
{
    GtkWidget *vbox;
    GtkWidget *pending_vb, *pending_lb;
    GtkWidget *progress_lb, *progress_bar;

    /* Check to see if we've only hidden it */
    if( queue_window != NULL )
        return;

    queue_window = gtk_window_new( GTK_WINDOW_DIALOG );
    gtk_container_set_border_width( GTK_CONTAINER( queue_window ), 8 );

    vbox = gtk_vbox_new( FALSE, 4 );

    pending_vb = gtk_vbox_new( FALSE, 2 );
    queue_window_pending = pending_vb;

    pending_lb = gtk_label_new( _("Currently pending operations:") );
    gtk_misc_set_alignment( GTK_MISC( pending_lb ), 0.0, 0.0 );
    gtk_box_pack_start( GTK_BOX( pending_vb ), pending_lb,
                FALSE, TRUE, 0 );

    gtk_box_pack_start( GTK_BOX( vbox ), pending_vb,
                TRUE, TRUE, 4 );

    /* FIXME: 'operation' is not the warmest cuddliest word. */
    progress_lb = gtk_label_new( "" );
    queue_window_message = progress_lb;
    gtk_box_pack_start( GTK_BOX( vbox ), progress_lb,
                FALSE, TRUE, 4 );

    progress_bar = gtk_progress_bar_new();
    queue_window_progress = progress_bar;
    /* FIXME: is this fit for l10n? */
    gtk_progress_bar_set_orientation( GTK_PROGRESS_BAR( progress_bar ), 
                      GTK_PROGRESS_LEFT_TO_RIGHT );
    gtk_progress_bar_set_bar_style( GTK_PROGRESS_BAR( progress_bar ), 
                    GTK_PROGRESS_CONTINUOUS );
    gtk_box_pack_start( GTK_BOX( vbox ), progress_bar,
                FALSE, TRUE, 4 );

    gtk_container_add( GTK_CONTAINER( queue_window ), vbox );
}

/**
 * check_compipe:
 *
 * Check and see if our pipe has been opened and open
 * it if necessary.
 **/

static void check_compipe( void )
{
    if( READER > 0 )
        return;

    if( pipe( compipe ) < 0 ) { 
        g_warning( "Call to pipe(2) failed!" );
        
        /* FIXME: better error handling. How do we react? */
        return;
    }

    chan_reader = g_io_channel_unix_new( READER );
    g_io_add_watch( chan_reader, G_IO_IN, read_msg, NULL );
}

/**
 * check_cond:
 *
 * See if our condition is initialized and do so if necessary
 **/

static void check_cond( void )
{
    if( modal_cond == NULL )
        modal_cond = g_cond_new();
}

/**
 * dispatch:
 * @clur: The function to execute and its userdata
 *
 * Start a thread that executes the closure and exit
 * it when done.
 */

static void dispatch( closure_t *clur )
{
    int res;

    res = pthread_create( &dispatch_thread, NULL, (void *) &dispatch_func, clur );

    if( res != 0 ) {
        g_warning( "Error launching dispatch thread!" );
        /* FIXME: more error handling */
    }
}

/**
 * dispatch_func:
 * @data: the closure to run
 *
 * Runs the closure and exits the thread.
 */

static void *dispatch_func( void *data )
{
    com_msg_t msg;
    closure_t *clur = (closure_t *) data;

    msg.type = STARTING;
    msg.message = clur->prettyname;
    write( WRITER, &msg, sizeof( msg ) );

    (clur->callback)( clur->data );

    msg.type = FINISHED;
    msg.func = clur->cleanup; /* NULL is ok */
    msg.userdata = clur->data;
    write( WRITER, &msg, sizeof( msg ) );

    g_free( clur->prettyname );
    g_free( data );

    pthread_exit( 0 );
    return NULL; /*NOTREACHED*/
}

/**
 * read_msg:
 * @source: the channel that has data to read
 * @condition: the reason we were called
 * @userdata: unused
 *
 * A message has been recieved on our pipe; perform the appropriate 
 * action.
 **/

static gboolean read_msg( GIOChannel *source, GIOCondition condition, gpointer userdata )
{
    com_msg_t *msg;
    closure_t *clur;
    GSList *temp;
    guint size;

    msg = g_new0( com_msg_t, 1 );

    g_io_channel_read( source, (gchar *) msg, 
               sizeof( com_msg_t ) / sizeof( gchar ), 
               &size );

    if( size != sizeof( com_msg_t ) ) {
        g_warning( _("Incomplete message written on pipe!") );
        msg->type = ERROR;
        msg->message = g_strdup( _("Error reading commands from dispatching thread.") );
    }

    /* This is very important, though I'm not quite sure why
     * it is as we are in the main thread right now.
     */

    GDK_THREADS_ENTER();

    switch( msg->type ) {
    case STARTING:
        gtk_label_set_text( GTK_LABEL( queue_window_message ), msg->message );
        gtk_progress_bar_update( GTK_PROGRESS_BAR( queue_window_progress ), 0.0 );
        g_free( msg );
        break;
    case PERCENTAGE:
        gtk_progress_bar_update( GTK_PROGRESS_BAR( queue_window_progress ), msg->percentage );
        g_free( msg );
        break;
    case HIDE_PBAR:
        gtk_progress_set_activity_mode( GTK_PROGRESS( queue_window_progress ), TRUE );
        timeout_toggle( TRUE );

        g_free( msg );
        break;
    case SHOW_PBAR:
        timeout_toggle( FALSE );
        gtk_progress_set_activity_mode( GTK_PROGRESS( queue_window_progress ), FALSE );

        g_free( msg );
        break;
    case MESSAGE:
        gtk_label_set_text( GTK_LABEL( queue_window_message ),
                    msg->message );
        g_free( msg->message );
        g_free( msg );
        break;
    case PASSWORD:
        g_assert( msg->reply );
        g_assert( msg->success );
        get_password( msg );
        /* don't free msg! done later */
        break;
    case ERROR:
        show_error( msg );
        g_free( msg );
        break;

        /* Don't fall through; dispatch_func does the FINISHED
         * call for us 
         */

    case FINISHED:
        if( msg->func )
            (msg->func)( msg->userdata );

        if( op_queue == NULL ) {
            /* All done! */
            gtk_widget_hide( queue_window );
            mail_operation_in_progress = FALSE;
        } else {
            /* There's another operation left */
            
            /* Pop it off the front */
            clur = op_queue->data;
            temp = g_slist_next( op_queue );
            g_slist_free_1( op_queue );
            op_queue = temp;

            /* Clear it out of the 'pending' vbox */
            remove_next_pending();

            /* Run run run little process */
            dispatch( clur );
        }
        g_free( msg );
        break;
    default:
        g_warning( _("Corrupted message from dispatching thread?") );
        break;
    }

    GDK_THREADS_LEAVE();
    return TRUE;
}

/**
 * remove_next_pending:
 *
 * Remove an item from the list of pending items. If
 * that's the last one, additionally hide the little
 * 'pending' message.
 **/

static void remove_next_pending( void )
{
    GList *children;

    children = gtk_container_children( GTK_CONTAINER( queue_window_pending ) );

    /* Skip past the header label */
    children = g_list_first( children );
    children = g_list_next( children );

    /* Nuke the one on top */
    gtk_container_remove( GTK_CONTAINER( queue_window_pending ),
                  GTK_WIDGET( children->data ) );

    /* Hide it? */
    if( g_list_next( children ) == NULL )
        gtk_widget_hide( queue_window_pending );
}

/**
 * show_error:
 *
 * Show the error dialog and wait for user OK
 **/

static void show_error( com_msg_t *msg )
{
    GtkWidget *err_dialog;

    err_dialog = gnome_error_dialog( msg->message );
    gnome_dialog_set_close( GNOME_DIALOG(err_dialog), TRUE );
    gtk_signal_connect( GTK_OBJECT( err_dialog ), "clicked", (GtkSignalFunc) show_error_clicked, NULL );
    g_free( msg->message );

    G_LOCK( modal_lock );

    timeout_toggle( FALSE );
    modal_may_proceed = FALSE;
    gtk_widget_show( GTK_WIDGET( err_dialog ) );
}

/**
 * show_error_clicked:
 *
 * Called when the user makes hits okay to the error dialog --
 * the dispatch thread is allowed to continue.
 **/

static void show_error_clicked( void )
{
    modal_may_proceed = TRUE;
    timeout_toggle( TRUE );
    g_cond_signal( modal_cond );
    G_UNLOCK( modal_lock );
}

/**
 * get_password:
 *
 * Ask for a password and put the answer in *(msg->reply)
 **/

static void get_password( com_msg_t *msg )
{
    GtkWidget *dialog;

    dialog = gnome_request_dialog( msg->secret, msg->message, NULL,
                       0, get_password_cb, msg,
                       NULL );
    gnome_dialog_set_close( GNOME_DIALOG(dialog), TRUE );
    gtk_signal_connect( GTK_OBJECT( dialog ), "clicked", get_password_clicked, msg );

    G_LOCK( modal_lock );

    modal_may_proceed = FALSE;

    if( dialog == NULL ) {
        *(msg->success) = FALSE;
        *(msg->reply) = g_strdup( _("Could not create dialog box.") );
        modal_may_proceed = TRUE;
        g_cond_signal( modal_cond );
        G_UNLOCK( modal_lock );
    } else {
        *(msg->reply) = NULL;
        timeout_toggle( FALSE );
        gtk_widget_show( GTK_WIDGET( dialog ) );
    }
}

static void get_password_cb( gchar *string, gpointer data )
{
    com_msg_t *msg = (com_msg_t *) data;

        if (string)
                *(msg->reply) = g_strdup( string );
        else
                *(msg->reply) = NULL;
}

static void get_password_clicked( GnomeDialog *dialog, gint button, gpointer user_data )
{
    com_msg_t *msg = (com_msg_t *) user_data;

    if( button == 1 || *(msg->reply) == NULL ) {
        *(msg->success) = FALSE;
        *(msg->reply) = g_strdup( _("User cancelled query.") );
    } else
        *(msg->success) = TRUE;

    g_free( msg );
    modal_may_proceed = TRUE;
    timeout_toggle( TRUE );
    g_cond_signal( modal_cond );
    G_UNLOCK( modal_lock );
}

/* NOT totally copied from gtk+/gtk/testgtk.c, really! */

static gboolean
progress_timeout (gpointer data)
{
    gfloat new_val;
    GtkAdjustment *adj;

    adj = GTK_PROGRESS (data)->adjustment;
  
    new_val = adj->value + 1;
    if (new_val > adj->upper)
        new_val = adj->lower;

    gtk_progress_set_value (GTK_PROGRESS (data), new_val);

    return TRUE;
}

/**
 * timeout_toggle:
 *
 * Turn on and off our timeout to zip the progressbar along,
 * protecting against recursion (Ie, call with TRUE twice
 * in a row.
 **/

static void
timeout_toggle( gboolean active )
{
    if( (GTK_PROGRESS( queue_window_progress ))->activity_mode == 0 )
        return;

    if( active ) {
        if( progress_timeout_handle < 0 )
            progress_timeout_handle = gtk_timeout_add( 80, progress_timeout, queue_window_progress );
    } else {
        if( progress_timeout_handle >= 0 ) {
            gtk_timeout_remove( progress_timeout_handle );
            progress_timeout_handle = -1;
        }
    }
}