/* -*- 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"
#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,
FINISHED
} type;
gfloat percentage;
gchar *message;
closure_t *clur;
/* Password stuff */
gchar **reply;
gboolean secret;
gboolean *success;
}
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 <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.
*/
#elif defined( G_THREADS_IMPL_SOLARIS )
#include <thread.h>
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_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 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);
}