/* -*- 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 #include #include #include "camel/camel-object.h" #include "mail.h" #include "mail-threads.h" #define DEBUG(p) g_print p /* 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_queue, 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 { gpointer in_data; gboolean free_in_data; gpointer op_data; const mail_operation_spec *spec; CamelException *ex; gchar *infinitive; gchar *gerund; } 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, FORWARD_EVENT, FINISHED } type; gfloat percentage; gchar *message; closure_t *clur; /* Password stuff */ gchar **reply; gboolean secret; gboolean *success; /* Event stuff */ CamelObjectEventHookFunc event_hook; CamelObject *event_obj; gpointer event_event_data; gpointer event_user_data; } com_msg_t; /** * @dispatch_thread_started: gboolean that tells us whether * the dispatch thread has been launched. **/ static gboolean dispatch_thread_started = FALSE; /** * @queue_len : the number of operations pending * and being executed. * * 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 gint queue_len = 0; /** * @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; /** * @main_compipe: The pipe through which the dispatcher communicates * with the main thread for GTK+ calls * * @chan_reader: the GIOChannel that reads our pipe * * @MAIN_READER: the fd in our main pipe that.... reads! * @MAIN_WRITER: the fd in our main pipe that.... writes! */ #define MAIN_READER main_compipe[0] #define MAIN_WRITER main_compipe[1] #define DISPATCH_READER dispatch_compipe[0] #define DISPATCH_WRITER dispatch_compipe[1] static int main_compipe[2] = { -1, -1 }; static int dispatch_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; /** * @ready_for_op: A lock that the main thread only releases * when it is ready for the dispatch thread to do its thing * * @ready_cond: A condition for this ... condition * * @ready_may_proceed: a gboolean telling the dispatch thread * when it may proceed. **/ G_LOCK_DEFINE_STATIC (ready_for_op); static GCond *ready_cond = NULL; static gboolean ready_may_proceed = FALSE; /** * Static prototypes **/ static void create_queue_window (void); static void destroy_queue_window (void); static void *dispatch (void * data); static void check_dispatcher (void); static void check_compipes (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 (GtkObject * obj); 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 void get_password_deleted (GtkWidget *widget, gpointer user_data); static gboolean progress_timeout (gpointer data); static void timeout_toggle (gboolean active); static gboolean display_timeout (gpointer data); static closure_t *new_closure (const mail_operation_spec * spec, gpointer input, gboolean free_in_data); static void free_closure (closure_t *clur); /* Pthread code */ /* FIXME: support other thread types!!!! */ #ifdef G_THREADS_IMPL_POSIX #include /** * @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. */ #elif defined( G_THREADS_IMPL_SOLARIS ) #include static thread_t dispatch_thread; #else /* no supported thread impl */ void f (void) { Error_No_supported_thread_implementation_recognized (); choke on this; } #endif /** * mail_operation_queue: * @spec: describes the operation to be performed * @input: input data for the operation. * * 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_queue (const mail_operation_spec * spec, gpointer input, gboolean free_in_data) { closure_t *clur; g_assert (spec); clur = new_closure (spec, input, free_in_data); if (spec->setup) (spec->setup) (clur->in_data, clur->op_data, clur->ex); if (camel_exception_is_set (clur->ex)) { if (clur->ex->id != CAMEL_EXCEPTION_USER_CANCEL) { GtkWidget *err_dialog; gchar *msg; msg = g_strdup_printf ("Error while preparing to %s:\n" "%s", clur->infinitive, camel_exception_get_description (clur->ex)); err_dialog = gnome_error_dialog (msg); g_free (msg); gnome_dialog_set_close (GNOME_DIALOG (err_dialog), TRUE); /*gnome_dialog_run_and_close (GNOME_DIALOG (err_dialog)); */ /*gtk_widget_destroy (err_dialog); */ gtk_widget_show (GTK_WIDGET (err_dialog)); g_warning ("Setup failed for `%s': %s", clur->infinitive, camel_exception_get_description (clur-> ex)); } free_closure (clur); return FALSE; } if (queue_len == 0) { check_cond (); check_compipes (); check_dispatcher (); create_queue_window (); /*gtk_widget_show_all (queue_window); */ gtk_timeout_add (1000, display_timeout, NULL); } else { GtkWidget *label; /* We already have an operation running. Well, * queue ourselves up. (visually) */ /* Show us in the pending window. */ label = gtk_label_new (clur->infinitive); gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5); gtk_box_pack_start (GTK_BOX (queue_window_pending), label, FALSE, TRUE, 2); gtk_widget_show (label); /* 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 (queue_window_pending); } write (DISPATCH_WRITER, clur, sizeof (closure_t)); queue_len++; 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 (MAIN_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 (MAIN_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 (MAIN_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 (MAIN_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; msg.type = PASSWORD; msg.secret = secret; msg.message = prompt; msg.reply = dest; msg.success = &result; (*dest) = NULL; G_LOCK (modal_lock); write (MAIN_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; 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 (MAIN_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_op_forward_event: * * Communicate a camel event over to the main thread. **/ void mail_op_forward_event (CamelObjectEventHookFunc func, CamelObject *o, gpointer event_data, gpointer user_data) { com_msg_t msg; msg.type = FORWARD_EVENT; msg.event_hook = func; msg.event_obj = o; msg.event_event_data = event_data; msg.event_user_data = user_data; write (MAIN_WRITER, &msg, sizeof (msg)); } /** * mail_operation_wait_for_finish: * * Waits for the currently executing async operations * to finish executing */ void mail_operation_wait_for_finish (void) { while (queue_len) gtk_main_iteration (); /* Sigh. Otherwise we deadlock upon exit. */ GDK_THREADS_LEAVE (); } /** * mail_operations_are_executing: * * Returns TRUE if operations are being executed asynchronously * when called, FALSE if not. **/ gboolean mail_operations_are_executing (void) { return (queue_len > 0); } /** * mail_operations_terminate: * * Let the operations finish then terminate the dispatch thread **/ void mail_operations_terminate (void) { closure_t clur; mail_operation_wait_for_finish(); memset (&clur, 0, sizeof (closure_t)); clur.spec = NULL; write (DISPATCH_WRITER, &clur, sizeof (closure_t)); } /* ** Static functions **************************************************** */ static void check_dispatcher (void) { int res; if (dispatch_thread_started) return; #if defined( G_THREADS_IMPL_POSIX ) res = pthread_create (&dispatch_thread, NULL, (void *) &dispatch, NULL); #elif defined( G_THREADS_IMPL_SOLARIS ) res = thr_create (NULL, 0, (void *) &dispatch, NULL, 0, &dispatch_thread); #else /* no known impl */ Error_No_thread_create_implementation (); choke on this; #endif if (res != 0) { g_warning ("Error launching dispatch thread!"); /* FIXME: more error handling */ } else dispatch_thread_started = TRUE; } static void print_hide (GtkWidget * wid) { g_message ("$$$ hide signal emitted"); } static void print_unmap (GtkWidget * wid) { g_message ("$$$ unmap signal emitted"); } static void print_map (GtkWidget * wid) { g_message ("$$$ map signal emitted"); } static void print_show (GtkWidget * wid) { g_message ("$$$ show signal emitted"); } /** * create_queue_window: * * Creates the queue_window widget that displays the progress of the * current operation. */ static void queue_window_delete_event_cb (GtkWindow *window, void *data) { /* Do nothing. Just prevent GTK+ from destroying the window. */ } 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); gtk_signal_connect (GTK_OBJECT (queue_window), "delete_event", GTK_SIGNAL_FUNC (queue_window_delete_event_cb), NULL); 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); gtk_widget_show (GTK_WIDGET (progress_bar)); gtk_widget_show (GTK_WIDGET (progress_lb)); gtk_widget_show (GTK_WIDGET (pending_lb)); gtk_widget_show (GTK_WIDGET (pending_vb)); gtk_widget_show (GTK_WIDGET (vbox)); gtk_signal_connect (GTK_OBJECT (queue_window), "hide", print_hide, NULL); gtk_signal_connect (GTK_OBJECT (queue_window), "unmap", print_unmap, NULL); gtk_signal_connect (GTK_OBJECT (queue_window), "show", print_show, NULL); gtk_signal_connect (GTK_OBJECT (queue_window), "map", print_map, NULL); } static void destroy_queue_window (void) { g_return_if_fail (queue_window); timeout_toggle (FALSE); gtk_widget_destroy (queue_window); queue_window = NULL; queue_window_progress = NULL; queue_window_pending = NULL; queue_window_message = NULL; } /** * check_compipes: * * Check and see if our pipe has been opened and open * it if necessary. **/ static void check_compipes (void) { if (MAIN_READER < 0) { if (pipe (main_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 (MAIN_READER); g_io_add_watch (chan_reader, G_IO_IN, read_msg, NULL); } if (DISPATCH_READER < 0) { if (pipe (dispatch_compipe) < 0) { g_warning ("Call to pipe(2) failed!"); /* FIXME: better error handling. How do we react? */ return; } } } /** * 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 (); if (ready_cond == NULL) ready_cond = g_cond_new (); } /** * dispatch: * @clur: The operation to execute and its parameters * * Start a thread that executes the closure and exit * it when done. */ static void * dispatch (void *unused) { size_t len; closure_t *clur; com_msg_t msg; /* Let the compipes be created */ sleep (1); while (1) { clur = g_new (closure_t, 1); len = read (DISPATCH_READER, clur, sizeof (closure_t)); if (len <= 0) break; if (len != sizeof (closure_t)) { g_warning ("dispatcher: Didn't read full message!"); continue; } if (clur->spec == NULL) break; msg.type = STARTING; msg.message = g_strdup (clur->gerund); write (MAIN_WRITER, &msg, sizeof (msg)); mail_op_hide_progressbar (); (clur->spec->callback) (clur->in_data, clur->op_data, clur->ex); if (camel_exception_is_set (clur->ex)) { if (clur->ex->id != CAMEL_EXCEPTION_USER_CANCEL) { g_warning ("Callback failed for `%s': %s", clur->infinitive, camel_exception_get_description (clur-> ex)); mail_op_error ("Error while `%s':\n" "%s", clur->gerund, camel_exception_get_description (clur-> ex)); } } msg.type = FINISHED; msg.clur = clur; G_LOCK (ready_for_op); write (MAIN_WRITER, &msg, sizeof (msg)); ready_may_proceed = FALSE; while (ready_may_proceed == FALSE) g_cond_wait (ready_cond, g_static_mutex_get_mutex (&G_LOCK_NAME (ready_for_op))); G_UNLOCK (ready_for_op); } #ifdef G_THREADS_IMPL_POSIX pthread_exit (0); #elif defined( G_THREADS_IMPL_SOLARIS ) thr_exit (NULL); #else /* no known impl */ Error_No_thread_exit_implemented (); choke on this; #endif 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; 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: DEBUG (("*** Message -- STARTING %s\n", msg->message)); 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->message); g_free (msg); break; case PERCENTAGE: DEBUG (("*** Message -- PERCENTAGE\n")); gtk_progress_bar_update (GTK_PROGRESS_BAR (queue_window_progress), msg->percentage); g_free (msg); break; case HIDE_PBAR: DEBUG (("*** Message -- HIDE_PBAR\n")); gtk_progress_set_activity_mode (GTK_PROGRESS (queue_window_progress), TRUE); timeout_toggle (TRUE); g_free (msg); break; case SHOW_PBAR: DEBUG (("*** Message -- SHOW_PBAR\n")); timeout_toggle (FALSE); gtk_progress_set_activity_mode (GTK_PROGRESS (queue_window_progress), FALSE); g_free (msg); break; case MESSAGE: DEBUG (("*** Message -- MESSAGE\n")); gtk_label_set_text (GTK_LABEL (queue_window_message), msg->message); g_free (msg->message); g_free (msg); break; case PASSWORD: DEBUG (("*** Message -- PASSWORD\n")); g_assert (msg->reply); g_assert (msg->success); get_password (msg); /* don't free msg! done later */ break; case ERROR: DEBUG (("*** Message -- ERROR\n")); show_error (msg); g_free (msg); break; /* Don't fall through; dispatch_func does the FINISHED * call for us */ case FORWARD_EVENT: DEBUG (("*** Message -- FORWARD_EVENT %p", msg->event_hook)); g_assert (msg->event_hook); (msg->event_hook) (msg->event_obj, msg->event_event_data, msg->event_user_data); break; case FINISHED: DEBUG ( ("*** Message -- FINISH %s\n", msg->clur->gerund)); if (msg->clur->spec->cleanup) (msg->clur->spec->cleanup) (msg->clur->in_data, msg->clur->op_data, msg->clur->ex); G_LOCK (ready_for_op); ready_may_proceed = TRUE; g_cond_signal (ready_cond); G_UNLOCK (ready_for_op); if (camel_exception_is_set (msg->clur->ex) && msg->clur->ex->id != CAMEL_EXCEPTION_USER_CANCEL) { g_warning ("Error on cleanup of `%s': %s", msg->clur->infinitive, camel_exception_get_description (msg-> clur-> ex)); } free_closure (msg->clur); queue_len--; if (queue_len == 0) { g_print ("\tNo more ops -- hide %p.\n", queue_window); /* All done! */ /* gtk_widget_hide seems to have problems sometimes * here... perhaps because we're in a gsource handler, * not a GTK event handler? Anyway, we defer the hiding * til an idle. */ /*gtk_idle_add (hide_queue_window, NULL);*/ /*gtk_widget_hide (queue_window); */ destroy_queue_window (); } else { g_print ("\tOperation(s) left.\n"); /* There's another operation left : * Clear it out of the 'pending' vbox */ remove_next_pending (); } 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); if (!children) { g_warning ("Mistake in queue window!"); return; } /* 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; gchar *old_message; err_dialog = gnome_error_dialog (msg->message); gnome_dialog_set_close (GNOME_DIALOG (err_dialog), TRUE); gtk_signal_connect (GTK_OBJECT (err_dialog), "close", (GtkSignalFunc) show_error_clicked, NULL); gtk_signal_connect (GTK_OBJECT (err_dialog), "clicked", (GtkSignalFunc) show_error_clicked, NULL); g_free (msg->message); /* Save the old message, but display a new one right now */ gtk_label_get (GTK_LABEL (queue_window_message), &old_message); gtk_object_set_data (GTK_OBJECT (err_dialog), "old_message", g_strdup (old_message)); gtk_label_set_text (GTK_LABEL (queue_window_message), _("Waiting for user to close error dialog")); G_LOCK (modal_lock); timeout_toggle (FALSE); modal_may_proceed = FALSE; gtk_widget_show_all (GTK_WIDGET (err_dialog)); gnome_win_hints_set_layer (err_dialog, WIN_LAYER_ONTOP); gnome_win_hints_set_state (err_dialog, WIN_STATE_ARRANGE_IGNORE); gnome_win_hints_set_hints (err_dialog, WIN_HINTS_SKIP_FOCUS | WIN_HINTS_SKIP_WINLIST | WIN_HINTS_SKIP_TASKBAR); } /** * 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 (GtkObject * obj) { gchar *old_message; gtk_signal_disconnect_by_func (GTK_OBJECT (obj), show_error_clicked, NULL); /* Restore the old message */ old_message = gtk_object_get_data (obj, "old_message"); gtk_label_set_text (GTK_LABEL (queue_window_message), old_message); g_free (old_message); 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; gchar *old_message; 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); gtk_signal_connect (GTK_OBJECT (dialog), "close", get_password_deleted, msg); /* Save the old message, but display a new one right now */ gtk_label_get (GTK_LABEL (queue_window_message), &old_message); gtk_object_set_data (GTK_OBJECT (dialog), "old_message", g_strdup(old_message)); gtk_label_set_text (GTK_LABEL (queue_window_message), _("Waiting for user to enter data")); 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_all (GTK_WIDGET (dialog)); gnome_win_hints_set_layer (dialog, WIN_LAYER_ONTOP); gnome_win_hints_set_state (dialog, WIN_STATE_ARRANGE_IGNORE); gnome_win_hints_set_hints (dialog, WIN_HINTS_SKIP_FOCUS | WIN_HINTS_SKIP_WINLIST | WIN_HINTS_SKIP_TASKBAR); } } 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_deleted (GtkWidget *widget, gpointer user_data) { get_password_clicked (GNOME_DIALOG (widget), 1, user_data); } static void get_password_clicked (GnomeDialog * dialog, gint button, gpointer user_data) { com_msg_t *msg = (com_msg_t *) user_data; gchar *old_message; gtk_signal_disconnect_by_func (GTK_OBJECT (dialog), get_password_deleted, user_data); /* Restore the old message */ old_message = gtk_object_get_data (GTK_OBJECT (dialog), "old_message"); gtk_label_set_text (GTK_LABEL (queue_window_message), old_message); g_free (old_message); 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; if (queue_window == NULL) { gtk_timeout_remove (progress_timeout_handle); progress_timeout_handle = -1; return FALSE; } 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 (!queue_window) return; if ((GTK_PROGRESS (queue_window_progress))->activity_mode == 0) return; if (active) { /* We do this in case queue_window_progress gets reset */ if (progress_timeout_handle < 0) { progress_timeout_handle = gtk_timeout_add (80, progress_timeout, queue_window_progress); } else { gtk_timeout_remove (progress_timeout_handle); 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; } } } /* This can theoretically run into problems where if a short operation starts * and finishes, then another short operation starts and finishes a second * later, we will see the window prematurely. My response: oh noooooo! * * Solution: keep the timeout's handle and remove the timeout upon reception * of FINISH, and zero out the handle in this function. Whatever. */ static gboolean display_timeout (gpointer data) { if (queue_len > 0 && queue_window) { gtk_widget_show (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); if (queue_len == 1) gtk_widget_hide (queue_window_pending); } return FALSE; } static closure_t * new_closure (const mail_operation_spec * spec, gpointer input, gboolean free_in_data) { closure_t *clur; clur = g_new0 (closure_t, 1); clur->spec = spec; clur->in_data = input; clur->free_in_data = free_in_data; clur->ex = camel_exception_new (); clur->op_data = g_malloc (spec->datasize); camel_exception_init (clur->ex); clur->infinitive = (spec->describe) (input, FALSE); clur->gerund = (spec->describe) (input, TRUE); return clur; } static void free_closure (closure_t *clur) { clur->spec = NULL; if (clur->free_in_data) g_free (clur->in_data); clur->in_data = NULL; g_free (clur->op_data); clur->op_data = NULL; camel_exception_free (clur->ex); clur->ex = NULL; g_free (clur->infinitive); g_free (clur->gerund); g_free (clur); }