summaryrefslogtreecommitdiff
path: root/libcaja-private/caja-progress-info.c
diff options
context:
space:
mode:
Diffstat (limited to 'libcaja-private/caja-progress-info.c')
-rw-r--r--libcaja-private/caja-progress-info.c930
1 files changed, 930 insertions, 0 deletions
diff --git a/libcaja-private/caja-progress-info.c b/libcaja-private/caja-progress-info.c
new file mode 100644
index 00000000..4945e22b
--- /dev/null
+++ b/libcaja-private/caja-progress-info.c
@@ -0,0 +1,930 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
+
+ caja-progress-info.h: file operation progress info.
+
+ Copyright (C) 2007 Red Hat, Inc.
+
+ 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 Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Author: Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include <math.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <eel/eel-string.h>
+#include <eel/eel-glib-extensions.h>
+#include "caja-progress-info.h"
+
+enum
+{
+ CHANGED,
+ PROGRESS_CHANGED,
+ STARTED,
+ FINISHED,
+ LAST_SIGNAL
+};
+
+/* TODO:
+ * Want an icon for the operation.
+ * Add and implement cancel button
+ */
+
+#define SIGNAL_DELAY_MSEC 100
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _CajaProgressInfo
+{
+ GObject parent_instance;
+
+ GCancellable *cancellable;
+
+ char *status;
+ char *details;
+ double progress;
+ gboolean activity_mode;
+ gboolean started;
+ gboolean finished;
+ gboolean paused;
+
+ GSource *idle_source;
+ gboolean source_is_now;
+
+ gboolean start_at_idle;
+ gboolean finish_at_idle;
+ gboolean changed_at_idle;
+ gboolean progress_at_idle;
+};
+
+struct _CajaProgressInfoClass
+{
+ GObjectClass parent_class;
+};
+
+static GList *active_progress_infos = NULL;
+
+static GtkStatusIcon *status_icon = NULL;
+static int n_progress_ops = 0;
+
+
+G_LOCK_DEFINE_STATIC(progress_info);
+
+G_DEFINE_TYPE (CajaProgressInfo, caja_progress_info, G_TYPE_OBJECT)
+
+GList *
+caja_get_all_progress_info (void)
+{
+ GList *l;
+
+ G_LOCK (progress_info);
+
+ l = eel_g_object_list_copy (active_progress_infos);
+
+ G_UNLOCK (progress_info);
+
+ return l;
+}
+
+static void
+caja_progress_info_finalize (GObject *object)
+{
+ CajaProgressInfo *info;
+
+ info = CAJA_PROGRESS_INFO (object);
+
+ g_free (info->status);
+ g_free (info->details);
+ g_object_unref (info->cancellable);
+
+ if (G_OBJECT_CLASS (caja_progress_info_parent_class)->finalize)
+ {
+ (*G_OBJECT_CLASS (caja_progress_info_parent_class)->finalize) (object);
+ }
+}
+
+static void
+caja_progress_info_dispose (GObject *object)
+{
+ CajaProgressInfo *info;
+
+ info = CAJA_PROGRESS_INFO (object);
+
+ G_LOCK (progress_info);
+
+ /* Remove from active list in dispose, since a get_all_progress_info()
+ call later could revive the object */
+ active_progress_infos = g_list_remove (active_progress_infos, object);
+
+ /* Destroy source in dispose, because the callback
+ could come here before the destroy, which should
+ ressurect the object for a while */
+ if (info->idle_source)
+ {
+ g_source_destroy (info->idle_source);
+ g_source_unref (info->idle_source);
+ info->idle_source = NULL;
+ }
+ G_UNLOCK (progress_info);
+}
+
+static void
+caja_progress_info_class_init (CajaProgressInfoClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = caja_progress_info_finalize;
+ gobject_class->dispose = caja_progress_info_dispose;
+
+ signals[CHANGED] =
+ g_signal_new ("changed",
+ CAJA_TYPE_PROGRESS_INFO,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[PROGRESS_CHANGED] =
+ g_signal_new ("progress-changed",
+ CAJA_TYPE_PROGRESS_INFO,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[STARTED] =
+ g_signal_new ("started",
+ CAJA_TYPE_PROGRESS_INFO,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[FINISHED] =
+ g_signal_new ("finished",
+ CAJA_TYPE_PROGRESS_INFO,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+}
+
+static gboolean
+delete_event (GtkWidget *widget,
+ GdkEventAny *event)
+{
+ gtk_widget_hide (widget);
+ return TRUE;
+}
+
+static void
+status_icon_activate_cb (GtkStatusIcon *icon,
+ GtkWidget *progress_window)
+{
+ if (gtk_widget_get_visible (progress_window))
+ {
+ gtk_widget_hide (progress_window);
+ }
+ else
+ {
+ gtk_window_present (GTK_WINDOW (progress_window));
+ }
+}
+
+static GtkWidget *
+get_progress_window (void)
+{
+ static GtkWidget *progress_window = NULL;
+ GtkWidget *vbox;
+
+ if (progress_window != NULL)
+ {
+ return progress_window;
+ }
+
+ progress_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_resizable (GTK_WINDOW (progress_window),
+ FALSE);
+ gtk_container_set_border_width (GTK_CONTAINER (progress_window), 10);
+
+ gtk_window_set_title (GTK_WINDOW (progress_window),
+ _("File Operations"));
+ gtk_window_set_wmclass (GTK_WINDOW (progress_window),
+ "file_progress", "Caja");
+ gtk_window_set_position (GTK_WINDOW (progress_window),
+ GTK_WIN_POS_CENTER);
+ gtk_window_set_icon_name (GTK_WINDOW (progress_window),
+ "system-file-manager");
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_box_set_spacing (GTK_BOX (vbox), 5);
+
+ gtk_container_add (GTK_CONTAINER (progress_window),
+ vbox);
+
+ gtk_widget_show_all (progress_window);
+
+ g_signal_connect (progress_window,
+ "delete_event",
+ (GCallback)delete_event, NULL);
+
+ status_icon = gtk_status_icon_new_from_icon_name ("system-file-manager");
+ g_signal_connect (status_icon, "activate",
+ (GCallback)status_icon_activate_cb,
+ progress_window);
+
+ gtk_status_icon_set_visible (status_icon, FALSE);
+
+ return progress_window;
+}
+
+
+typedef struct
+{
+ GtkWidget *widget;
+ CajaProgressInfo *info;
+ GtkLabel *status;
+ GtkLabel *details;
+ GtkProgressBar *progress_bar;
+} ProgressWidgetData;
+
+static void
+progress_widget_data_free (ProgressWidgetData *data)
+{
+ g_object_unref (data->info);
+ g_free (data);
+}
+
+static void
+update_data (ProgressWidgetData *data)
+{
+ char *status, *details;
+ char *markup;
+
+ status = caja_progress_info_get_status (data->info);
+ gtk_label_set_text (data->status, status);
+ g_free (status);
+
+ details = caja_progress_info_get_details (data->info);
+ markup = g_markup_printf_escaped ("<span size='small'>%s</span>", details);
+ gtk_label_set_markup (data->details, markup);
+ g_free (details);
+ g_free (markup);
+}
+
+static void
+update_progress (ProgressWidgetData *data)
+{
+ double progress;
+
+ progress = caja_progress_info_get_progress (data->info);
+ if (progress < 0)
+ {
+ gtk_progress_bar_pulse (data->progress_bar);
+ }
+ else
+ {
+ gtk_progress_bar_set_fraction (data->progress_bar, progress);
+ }
+}
+
+static void
+update_status_icon_and_window (void)
+{
+ char *tooltip;
+
+ tooltip = g_strdup_printf (ngettext ("%'d file operation active",
+ "%'d file operations active",
+ n_progress_ops),
+ n_progress_ops);
+ gtk_status_icon_set_tooltip_text (status_icon, tooltip);
+ g_free (tooltip);
+
+ if (n_progress_ops == 0)
+ {
+ gtk_status_icon_set_visible (status_icon, FALSE);
+ gtk_widget_hide (get_progress_window ());
+ }
+ else
+ {
+ gtk_status_icon_set_visible (status_icon, TRUE);
+ }
+}
+
+static void
+op_finished (ProgressWidgetData *data)
+{
+ gtk_widget_destroy (data->widget);
+
+ n_progress_ops--;
+ update_status_icon_and_window ();
+}
+
+static void
+cancel_clicked (GtkWidget *button,
+ ProgressWidgetData *data)
+{
+ caja_progress_info_cancel (data->info);
+ gtk_widget_set_sensitive (button, FALSE);
+}
+
+
+static GtkWidget *
+progress_widget_new (CajaProgressInfo *info)
+{
+ ProgressWidgetData *data;
+ GtkWidget *label, *progress_bar, *hbox, *vbox, *box, *button, *image;
+
+ data = g_new0 (ProgressWidgetData, 1);
+ data->info = g_object_ref (info);
+
+ vbox = gtk_vbox_new (FALSE, 0);
+ gtk_box_set_spacing (GTK_BOX (vbox), 5);
+
+
+ data->widget = vbox;
+ g_object_set_data_full (G_OBJECT (data->widget),
+ "data", data,
+ (GDestroyNotify)progress_widget_data_free);
+
+ label = gtk_label_new ("status");
+ gtk_widget_set_size_request (label, 500, -1);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_label_set_line_wrap_mode (GTK_LABEL (label), PANGO_WRAP_WORD_CHAR);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_box_pack_start (GTK_BOX (vbox),
+ label,
+ TRUE, FALSE,
+ 0);
+ data->status = GTK_LABEL (label);
+
+ hbox = gtk_hbox_new (FALSE,10);
+
+ progress_bar = gtk_progress_bar_new ();
+ data->progress_bar = GTK_PROGRESS_BAR (progress_bar);
+ gtk_progress_bar_set_pulse_step (data->progress_bar, 0.05);
+ box = gtk_vbox_new (FALSE,0);
+ gtk_box_pack_start(GTK_BOX (box),
+ progress_bar,
+ TRUE,FALSE,
+ 0);
+ gtk_box_pack_start(GTK_BOX (hbox),
+ box,
+ TRUE,TRUE,
+ 0);
+
+ image = gtk_image_new_from_stock (GTK_STOCK_CANCEL,
+ GTK_ICON_SIZE_BUTTON);
+ button = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (button), image);
+ gtk_box_pack_start (GTK_BOX (hbox),
+ button,
+ FALSE,FALSE,
+ 0);
+ g_signal_connect (button, "clicked", (GCallback)cancel_clicked, data);
+
+ gtk_box_pack_start (GTK_BOX (vbox),
+ hbox,
+ FALSE,FALSE,
+ 0);
+
+ label = gtk_label_new ("details");
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ gtk_box_pack_start (GTK_BOX (vbox),
+ label,
+ TRUE, FALSE,
+ 0);
+ data->details = GTK_LABEL (label);
+
+ gtk_widget_show_all (data->widget);
+
+ update_data (data);
+ update_progress (data);
+
+ g_signal_connect_swapped (data->info,
+ "changed",
+ (GCallback)update_data, data);
+ g_signal_connect_swapped (data->info,
+ "progress_changed",
+ (GCallback)update_progress, data);
+ g_signal_connect_swapped (data->info,
+ "finished",
+ (GCallback)op_finished, data);
+
+ return data->widget;
+}
+
+static void
+handle_new_progress_info (CajaProgressInfo *info)
+{
+ GtkWidget *window, *progress;
+
+ window = get_progress_window ();
+
+ progress = progress_widget_new (info);
+ gtk_box_pack_start (GTK_BOX (gtk_bin_get_child (GTK_BIN (window))),
+ progress,
+ FALSE, FALSE, 6);
+
+ gtk_window_present (GTK_WINDOW (window));
+
+ n_progress_ops++;
+ update_status_icon_and_window ();
+}
+
+static gboolean
+new_op_started_timeout (CajaProgressInfo *info)
+{
+ if (caja_progress_info_get_is_paused (info))
+ {
+ return TRUE;
+ }
+ if (!caja_progress_info_get_is_finished (info))
+ {
+ handle_new_progress_info (info);
+ }
+ g_object_unref (info);
+ return FALSE;
+}
+
+static void
+new_op_started (CajaProgressInfo *info)
+{
+ g_signal_handlers_disconnect_by_func (info, (GCallback)new_op_started, NULL);
+ g_timeout_add_seconds (2,
+ (GSourceFunc)new_op_started_timeout,
+ g_object_ref (info));
+}
+
+static void
+caja_progress_info_init (CajaProgressInfo *info)
+{
+ info->cancellable = g_cancellable_new ();
+
+ G_LOCK (progress_info);
+ active_progress_infos = g_list_append (active_progress_infos, info);
+ G_UNLOCK (progress_info);
+
+ g_signal_connect (info, "started", (GCallback)new_op_started, NULL);
+}
+
+CajaProgressInfo *
+caja_progress_info_new (void)
+{
+ CajaProgressInfo *info;
+
+ info = g_object_new (CAJA_TYPE_PROGRESS_INFO, NULL);
+
+ return info;
+}
+
+char *
+caja_progress_info_get_status (CajaProgressInfo *info)
+{
+ char *res;
+
+ G_LOCK (progress_info);
+
+ if (info->status)
+ {
+ res = g_strdup (info->status);
+ }
+ else
+ {
+ res = g_strdup (_("Preparing"));
+ }
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+char *
+caja_progress_info_get_details (CajaProgressInfo *info)
+{
+ char *res;
+
+ G_LOCK (progress_info);
+
+ if (info->details)
+ {
+ res = g_strdup (info->details);
+ }
+ else
+ {
+ res = g_strdup (_("Preparing"));
+ }
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+double
+caja_progress_info_get_progress (CajaProgressInfo *info)
+{
+ double res;
+
+ G_LOCK (progress_info);
+
+ if (info->activity_mode)
+ {
+ res = -1.0;
+ }
+ else
+ {
+ res = info->progress;
+ }
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+void
+caja_progress_info_cancel (CajaProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ g_cancellable_cancel (info->cancellable);
+
+ G_UNLOCK (progress_info);
+}
+
+GCancellable *
+caja_progress_info_get_cancellable (CajaProgressInfo *info)
+{
+ GCancellable *c;
+
+ G_LOCK (progress_info);
+
+ c = g_object_ref (info->cancellable);
+
+ G_UNLOCK (progress_info);
+
+ return c;
+}
+
+gboolean
+caja_progress_info_get_is_started (CajaProgressInfo *info)
+{
+ gboolean res;
+
+ G_LOCK (progress_info);
+
+ res = info->started;
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+gboolean
+caja_progress_info_get_is_finished (CajaProgressInfo *info)
+{
+ gboolean res;
+
+ G_LOCK (progress_info);
+
+ res = info->finished;
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+gboolean
+caja_progress_info_get_is_paused (CajaProgressInfo *info)
+{
+ gboolean res;
+
+ G_LOCK (progress_info);
+
+ res = info->paused;
+
+ G_UNLOCK (progress_info);
+
+ return res;
+}
+
+static gboolean
+idle_callback (gpointer data)
+{
+ CajaProgressInfo *info = data;
+ gboolean start_at_idle;
+ gboolean finish_at_idle;
+ gboolean changed_at_idle;
+ gboolean progress_at_idle;
+ GSource *source;
+
+ source = g_main_current_source ();
+
+ G_LOCK (progress_info);
+
+ /* Protect agains races where the source has
+ been destroyed on another thread while it
+ was being dispatched.
+ Similar to what gdk_threads_add_idle does.
+ */
+ if (g_source_is_destroyed (source))
+ {
+ G_UNLOCK (progress_info);
+ return FALSE;
+ }
+
+ /* We hadn't destroyed the source, so take a ref.
+ * This might ressurect the object from dispose, but
+ * that should be ok.
+ */
+ g_object_ref (info);
+
+ g_assert (source == info->idle_source);
+
+ g_source_unref (source);
+ info->idle_source = NULL;
+
+ start_at_idle = info->start_at_idle;
+ finish_at_idle = info->finish_at_idle;
+ changed_at_idle = info->changed_at_idle;
+ progress_at_idle = info->progress_at_idle;
+
+ info->start_at_idle = FALSE;
+ info->finish_at_idle = FALSE;
+ info->changed_at_idle = FALSE;
+ info->progress_at_idle = FALSE;
+
+ G_UNLOCK (progress_info);
+
+ if (start_at_idle)
+ {
+ g_signal_emit (info,
+ signals[STARTED],
+ 0);
+ }
+
+ if (changed_at_idle)
+ {
+ g_signal_emit (info,
+ signals[CHANGED],
+ 0);
+ }
+
+ if (progress_at_idle)
+ {
+ g_signal_emit (info,
+ signals[PROGRESS_CHANGED],
+ 0);
+ }
+
+ if (finish_at_idle)
+ {
+ g_signal_emit (info,
+ signals[FINISHED],
+ 0);
+ }
+
+ g_object_unref (info);
+
+ return FALSE;
+}
+
+/* Called with lock held */
+static void
+queue_idle (CajaProgressInfo *info, gboolean now)
+{
+ if (info->idle_source == NULL ||
+ (now && !info->source_is_now))
+ {
+ if (info->idle_source)
+ {
+ g_source_destroy (info->idle_source);
+ g_source_unref (info->idle_source);
+ info->idle_source = NULL;
+ }
+
+ info->source_is_now = now;
+ if (now)
+ {
+ info->idle_source = g_idle_source_new ();
+ }
+ else
+ {
+ info->idle_source = g_timeout_source_new (SIGNAL_DELAY_MSEC);
+ }
+ g_source_set_callback (info->idle_source, idle_callback, info, NULL);
+ g_source_attach (info->idle_source, NULL);
+ }
+}
+
+void
+caja_progress_info_pause (CajaProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ if (!info->paused)
+ {
+ info->paused = TRUE;
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+caja_progress_info_resume (CajaProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ if (info->paused)
+ {
+ info->paused = FALSE;
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+caja_progress_info_start (CajaProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ if (!info->started)
+ {
+ info->started = TRUE;
+
+ info->start_at_idle = TRUE;
+ queue_idle (info, TRUE);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+caja_progress_info_finish (CajaProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ if (!info->finished)
+ {
+ info->finished = TRUE;
+
+ info->finish_at_idle = TRUE;
+ queue_idle (info, TRUE);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+caja_progress_info_take_status (CajaProgressInfo *info,
+ char *status)
+{
+ G_LOCK (progress_info);
+
+ if (eel_strcmp (info->status, status) != 0)
+ {
+ g_free (info->status);
+ info->status = status;
+
+ info->changed_at_idle = TRUE;
+ queue_idle (info, FALSE);
+ }
+ else
+ {
+ g_free (status);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+caja_progress_info_set_status (CajaProgressInfo *info,
+ const char *status)
+{
+ G_LOCK (progress_info);
+
+ if (eel_strcmp (info->status, status) != 0)
+ {
+ g_free (info->status);
+ info->status = g_strdup (status);
+
+ info->changed_at_idle = TRUE;
+ queue_idle (info, FALSE);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+
+void
+caja_progress_info_take_details (CajaProgressInfo *info,
+ char *details)
+{
+ G_LOCK (progress_info);
+
+ if (eel_strcmp (info->details, details) != 0)
+ {
+ g_free (info->details);
+ info->details = details;
+
+ info->changed_at_idle = TRUE;
+ queue_idle (info, FALSE);
+ }
+ else
+ {
+ g_free (details);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+caja_progress_info_set_details (CajaProgressInfo *info,
+ const char *details)
+{
+ G_LOCK (progress_info);
+
+ if (eel_strcmp (info->details, details) != 0)
+ {
+ g_free (info->details);
+ info->details = g_strdup (details);
+
+ info->changed_at_idle = TRUE;
+ queue_idle (info, FALSE);
+ }
+
+ G_UNLOCK (progress_info);
+}
+
+void
+caja_progress_info_pulse_progress (CajaProgressInfo *info)
+{
+ G_LOCK (progress_info);
+
+ info->activity_mode = TRUE;
+ info->progress = 0.0;
+ info->progress_at_idle = TRUE;
+ queue_idle (info, FALSE);
+
+ G_UNLOCK (progress_info);
+}
+
+void
+caja_progress_info_set_progress (CajaProgressInfo *info,
+ double current,
+ double total)
+{
+ double current_percent;
+
+ if (total <= 0)
+ {
+ current_percent = 1.0;
+ }
+ else
+ {
+ current_percent = current / total;
+
+ if (current_percent < 0)
+ {
+ current_percent = 0;
+ }
+
+ if (current_percent > 1.0)
+ {
+ current_percent = 1.0;
+ }
+ }
+
+ G_LOCK (progress_info);
+
+ if (info->activity_mode || /* emit on switch from activity mode */
+ fabs (current_percent - info->progress) > 0.005 /* Emit on change of 0.5 percent */
+ )
+ {
+ info->activity_mode = FALSE;
+ info->progress = current_percent;
+ info->progress_at_idle = TRUE;
+ queue_idle (info, FALSE);
+ }
+
+ G_UNLOCK (progress_info);
+}