Commit 2db9a295 authored by Carlos Soriano Sánchez's avatar Carlos Soriano Sánchez

progress-info: allow finished infos to remain alive

We want to not hide the operations and the operations button
in the toolbar if the popover is open. For that, we need to
allow operations widgets to remain alive even when the operation
is finished.

For that we need to add a new status state to copying/moving/trashing,
like copied/moved/trashed.
On the way, improve the wording of file-operations to match the mockups,
and also make the report operations code more consistent to each other.
parent 192d2e2f
......@@ -102,14 +102,6 @@ nautilus_progress_info_manager_class_init (NautilusProgressInfoManagerClass *kla
g_type_class_add_private (klass, sizeof (NautilusProgressInfoManagerPriv));
}
static void
progress_info_finished_cb (NautilusProgressInfo *info,
NautilusProgressInfoManager *self)
{
self->priv->progress_infos =
g_list_remove (self->priv->progress_infos, info);
}
NautilusProgressInfoManager *
nautilus_progress_info_manager_dup_singleton (void)
{
......@@ -128,12 +120,27 @@ nautilus_progress_info_manager_add_new_info (NautilusProgressInfoManager *self,
self->priv->progress_infos =
g_list_prepend (self->priv->progress_infos, g_object_ref (info));
g_signal_connect (info, "finished",
G_CALLBACK (progress_info_finished_cb), self);
g_signal_emit (self, signals[NEW_PROGRESS_INFO], 0, info);
}
void
nautilus_progress_info_manager_remove_finished_or_cancelled_infos (NautilusProgressInfoManager *self)
{
GList *l;
GList *next;
l = self->priv->progress_infos;
while (l != NULL) {
next = l->next;
if (nautilus_progress_info_get_is_finished (l->data) ||
nautilus_progress_info_get_is_cancelled (l->data)) {
self->priv->progress_infos = g_list_remove (self->priv->progress_infos,
l->data);
}
l = next;
}
}
GList *
nautilus_progress_info_manager_get_all_infos (NautilusProgressInfoManager *self)
{
......@@ -141,12 +148,13 @@ nautilus_progress_info_manager_get_all_infos (NautilusProgressInfoManager *self)
}
gboolean
nautilus_progress_manager_are_all_infos_finished (NautilusProgressInfoManager *self)
nautilus_progress_manager_are_all_infos_finished_or_cancelled (NautilusProgressInfoManager *self)
{
GList *l;
for (l = self->priv->progress_infos; l != NULL; l = l->next) {
if (!nautilus_progress_info_get_is_finished (l->data)) {
if (!(nautilus_progress_info_get_is_finished (l->data) ||
nautilus_progress_info_get_is_cancelled (l->data))) {
return FALSE;
}
}
......
......@@ -62,7 +62,8 @@ NautilusProgressInfoManager* nautilus_progress_info_manager_dup_singleton (void)
void nautilus_progress_info_manager_add_new_info (NautilusProgressInfoManager *self,
NautilusProgressInfo *info);
GList *nautilus_progress_info_manager_get_all_infos (NautilusProgressInfoManager *self);
gboolean nautilus_progress_manager_are_all_infos_finished (NautilusProgressInfoManager *self);
void nautilus_progress_info_manager_remove_finished_or_cancelled_infos (NautilusProgressInfoManager *self);
gboolean nautilus_progress_manager_are_all_infos_finished_or_cancelled (NautilusProgressInfoManager *self);
G_END_DECLS
......
......@@ -34,6 +34,7 @@ enum {
PROGRESS_CHANGED,
STARTED,
FINISHED,
CANCELLED,
LAST_SIGNAL
};
......@@ -46,6 +47,8 @@ struct _NautilusProgressInfo
GObject parent_instance;
GCancellable *cancellable;
guint cancellable_id;
GCancellable *details_in_thread_cancellable;
char *status;
char *details;
......@@ -62,6 +65,7 @@ struct _NautilusProgressInfo
gboolean start_at_idle;
gboolean finish_at_idle;
gboolean cancel_at_idle;
gboolean changed_at_idle;
gboolean progress_at_idle;
};
......@@ -84,7 +88,10 @@ nautilus_progress_info_finalize (GObject *object)
g_free (info->status);
g_free (info->details);
g_cancellable_disconnect (info->cancellable, info->cancellable_id);
g_object_unref (info->cancellable);
g_cancellable_cancel (info->details_in_thread_cancellable);
g_clear_object (&info->details_in_thread_cancellable);
if (G_OBJECT_CLASS (nautilus_progress_info_parent_class)->finalize) {
(*G_OBJECT_CLASS (nautilus_progress_info_parent_class)->finalize) (object);
......@@ -155,6 +162,155 @@ nautilus_progress_info_class_init (NautilusProgressInfoClass *klass)
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
signals[CANCELLED] =
g_signal_new ("cancelled",
NAUTILUS_TYPE_PROGRESS_INFO,
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
static gboolean
idle_callback (gpointer data)
{
NautilusProgressInfo *info = data;
gboolean start_at_idle;
gboolean finish_at_idle;
gboolean changed_at_idle;
gboolean progress_at_idle;
gboolean cancelled_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;
cancelled_at_idle = info->cancel_at_idle;
info->start_at_idle = FALSE;
info->finish_at_idle = FALSE;
info->changed_at_idle = FALSE;
info->progress_at_idle = FALSE;
info->cancel_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);
}
if (cancelled_at_idle) {
g_signal_emit (info,
signals[CANCELLED],
0);
}
g_object_unref (info);
return FALSE;
}
/* Called with lock held */
static void
queue_idle (NautilusProgressInfo *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);
}
}
static void
set_details_in_thread (GTask *task,
NautilusProgressInfo *info,
gpointer user_data,
GCancellable *cancellable)
{
if (!g_cancellable_is_cancelled (cancellable)) {
nautilus_progress_info_set_details (info, _("Cancelled"));
G_LOCK (progress_info);
info->cancel_at_idle = TRUE;
queue_idle (info, TRUE);
G_UNLOCK (progress_info);
}
}
static void
on_canceled (GCancellable *cancellable,
NautilusProgressInfo *info)
{
GTask *task;
/* We can't do any lock operaton here, since this is probably the main
* thread, so modify the details in another thread. Also it can happens
* that we were finalizing the object, so create a new cancellable here
* so it can be cancelled in finalize */
info->details_in_thread_cancellable = g_cancellable_new ();
task = g_task_new (info, info->details_in_thread_cancellable, NULL, NULL);
g_task_run_in_thread (task, (GTaskThreadFunc) set_details_in_thread);
g_object_unref (task);
}
static void
......@@ -163,6 +319,10 @@ nautilus_progress_info_init (NautilusProgressInfo *info)
NautilusProgressInfoManager *manager;
info->cancellable = g_cancellable_new ();
info->cancellable_id = g_cancellable_connect (info->cancellable,
G_CALLBACK (on_canceled),
info,
NULL);
manager = nautilus_progress_info_manager_dup_singleton ();
nautilus_progress_info_manager_add_new_info (manager, info);
......@@ -257,6 +417,18 @@ nautilus_progress_info_get_cancellable (NautilusProgressInfo *info)
return c;
}
gboolean
nautilus_progress_info_get_is_cancelled (NautilusProgressInfo *info)
{
gboolean cancelled;
G_LOCK (progress_info);
cancelled = g_cancellable_is_cancelled (info->cancellable);
G_UNLOCK (progress_info);
return cancelled;
}
gboolean
nautilus_progress_info_get_is_started (NautilusProgressInfo *info)
{
......@@ -299,105 +471,6 @@ nautilus_progress_info_get_is_paused (NautilusProgressInfo *info)
return res;
}
static gboolean
idle_callback (gpointer data)
{
NautilusProgressInfo *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 (NautilusProgressInfo *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
nautilus_progress_info_pause (NautilusProgressInfo *info)
{
......
......@@ -60,6 +60,7 @@ void nautilus_progress_info_cancel (NautilusProgressInfo *info
gboolean nautilus_progress_info_get_is_started (NautilusProgressInfo *info);
gboolean nautilus_progress_info_get_is_finished (NautilusProgressInfo *info);
gboolean nautilus_progress_info_get_is_paused (NautilusProgressInfo *info);
gboolean nautilus_progress_info_get_is_cancelled (NautilusProgressInfo *info);
void nautilus_progress_info_start (NautilusProgressInfo *info);
void nautilus_progress_info_finish (NautilusProgressInfo *info);
......
......@@ -47,7 +47,13 @@ G_DEFINE_TYPE_WITH_PRIVATE (NautilusProgressInfoWidget, nautilus_progress_info_w
static void
info_finished (NautilusProgressInfoWidget *self)
{
gtk_widget_destroy (GTK_WIDGET (self));
gtk_widget_set_sensitive (self->priv->cancel, FALSE);
}
static void
info_cancelled (NautilusProgressInfoWidget *self)
{
gtk_widget_set_sensitive (self->priv->cancel, FALSE);
}
static void
......@@ -85,7 +91,6 @@ cancel_clicked (GtkWidget *button,
NautilusProgressInfoWidget *self)
{
nautilus_progress_info_cancel (self->priv->info);
gtk_widget_set_sensitive (button, FALSE);
}
static void
......@@ -117,6 +122,9 @@ nautilus_progress_info_widget_constructed (GObject *obj)
g_signal_connect_swapped (self->priv->info,
"finished",
G_CALLBACK (info_finished), self);
g_signal_connect_swapped (self->priv->info,
"cancelled",
G_CALLBACK (info_cancelled), self);
update_data (self);
update_progress (self);
......@@ -186,7 +194,15 @@ nautilus_progress_info_widget_class_init (NautilusProgressInfoWidgetClass *klass
GtkWidget *
nautilus_progress_info_widget_new (NautilusProgressInfo *info)
{
return g_object_new (NAUTILUS_TYPE_PROGRESS_INFO_WIDGET,
NautilusProgressInfoWidget *self;
self = g_object_new (NAUTILUS_TYPE_PROGRESS_INFO_WIDGET,
"info", info,
NULL);
gtk_widget_set_sensitive (self->priv->cancel,
!nautilus_progress_info_get_is_finished (self->priv->info) &&
!nautilus_progress_info_get_is_cancelled (self->priv->info));
return GTK_WIDGET (self);
}
......@@ -149,6 +149,7 @@
<object class="GtkMenuButton" id="operations_button">
<property name="visible">False</property>
<property name="popover">operations_popover</property>
<signal name="toggled" handler="on_operations_button_toggled" object="NautilusToolbar" swapped="yes"/>
<style>
<class name="button"/>
</style>
......
......@@ -40,6 +40,7 @@
#include <math.h>
#define OPERATION_MINIMUM_TIME 5 //s
#define REMOVE_FINISHED_OPERATIONS_TIEMOUT 2 //s
typedef enum {
NAUTILUS_NAVIGATION_DIRECTION_NONE,
......@@ -59,6 +60,7 @@ struct _NautilusToolbarPrivate {
guint popup_timeout_id;
guint start_operations_timeout_id;
guint remove_finished_operations_timeout_id;
GtkWidget *operations_button;
GtkWidget *view_button;
......@@ -98,6 +100,7 @@ static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
G_DEFINE_TYPE_WITH_PRIVATE(NautilusToolbar, nautilus_toolbar, GTK_TYPE_HEADER_BAR);
static void unschedule_menu_popup_timeout (NautilusToolbar *self);
static void update_operations (NautilusToolbar *self);
static void
toolbar_update_appearance (NautilusToolbar *self)
......@@ -443,7 +446,9 @@ should_hide_operations_button (NautilusToolbar *self)
for (l = progress_infos; l != NULL; l = l->next) {
if (nautilus_progress_info_get_elapsed_time (l->data) +
nautilus_progress_info_get_remaining_time (l->data) > OPERATION_MINIMUM_TIME) {
nautilus_progress_info_get_remaining_time (l->data) > OPERATION_MINIMUM_TIME &&
!nautilus_progress_info_get_is_cancelled (l->data) &&
!nautilus_progress_info_get_is_finished (l->data)) {
return FALSE;
}
}
......@@ -451,24 +456,67 @@ should_hide_operations_button (NautilusToolbar *self)
return TRUE;
}
static gboolean
remove_finished_operations (NautilusToolbar *self)
{
nautilus_progress_info_manager_remove_finished_or_cancelled_infos (self->priv->progress_manager);
if (should_hide_operations_button (self)) {
gtk_revealer_set_reveal_child (GTK_REVEALER (self->priv->operations_revealer),
FALSE);
} else {
update_operations (self);
}
self->priv->remove_finished_operations_timeout_id = 0;
return G_SOURCE_REMOVE;
}
static void
unschedule_remove_finished_operations (NautilusToolbar *self)
{
if (self->priv->remove_finished_operations_timeout_id != 0) {
g_source_remove (self->priv->remove_finished_operations_timeout_id);
self->priv->remove_finished_operations_timeout_id = 0;
}
}
static void
schedule_remove_finished_operations (NautilusToolbar *self)
{
if (self->priv->remove_finished_operations_timeout_id == 0) {
self->priv->remove_finished_operations_timeout_id =
g_timeout_add_seconds (REMOVE_FINISHED_OPERATIONS_TIEMOUT,
(GSourceFunc) remove_finished_operations,
self);
}
}
static void
on_progress_info_progress_changed (NautilusProgressInfo *info,
NautilusToolbar *self)
on_progress_info_cancelled (NautilusToolbar *self)
{
/* Update the pie chart progress */
gtk_widget_queue_draw (self->priv->operations_icon);
if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->priv->operations_button))) {
schedule_remove_finished_operations (self);
}
}
static void
on_progress_info_finished (NautilusProgressInfo *info,
NautilusToolbar *self)
on_progress_info_progress_changed (NautilusToolbar *self)
{
/* Update the pie chart progress */
gtk_widget_queue_draw (self->priv->operations_icon);
}
if (should_hide_operations_button (self)) {
gtk_revealer_set_reveal_child (GTK_REVEALER (self->priv->operations_revealer),
FALSE);
static void
on_progress_info_finished (NautilusToolbar *self)
{
/* Update the pie chart progress */
gtk_widget_queue_draw (self->priv->operations_icon);
if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->priv->operations_button))) {
schedule_remove_finished_operations (self);
}
}
......@@ -504,10 +552,12 @@ update_operations (NautilusToolbar *self)
nautilus_progress_info_get_remaining_time (l->data) > OPERATION_MINIMUM_TIME) {
total_remaining_time = nautilus_progress_info_get_remaining_time (l->data);
g_signal_connect (l->data, "finished",
G_CALLBACK (on_progress_info_finished), self);
g_signal_connect (l->data, "progress-changed",
G_CALLBACK (on_progress_info_progress_changed), self);
g_signal_connect_swapped (l->data, "finished",
G_CALLBACK (on_progress_info_finished), self);
g_signal_connect_swapped (l->data, "cancelled",
G_CALLBACK (on_progress_info_cancelled), self);
g_signal_connect_swapped (l->data, "progress-changed",
G_CALLBACK (on_progress_info_progress_changed), self);
progress = nautilus_progress_info_widget_new (l->data);
gtk_box_pack_start (GTK_BOX (self->priv->operations_container),
progress,
......@@ -540,10 +590,13 @@ on_progress_info_started_timeout (NautilusToolbar *self)
/* In case we didn't show the operations button because the operation total
* time stimation is not good enough, update again to make sure we don't miss
* a long time operation because of that */
if (!nautilus_progress_manager_are_all_infos_finished (self->priv->progress_manager)) {
if (!nautilus_progress_manager_are_all_infos_finished_or_cancelled (self->priv->progress_manager)) {
return G_SOURCE_CONTINUE;
} else {
self->priv->start_operations_timeout_id = 0;
if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->priv->operations_button))) {
schedule_remove_finished_operations (self);
}
return G_SOURCE_REMOVE;
}
}
......@@ -600,21 +653,31 @@ on_operations_icon_draw (GtkWidget *widget,
GList *l;
guint width;
guint height;
gboolean all_cancelled;
GdkRGBA background = {.red = 0, .green = 0, .blue = 0, .alpha = 0.2 };
GdkRGBA foreground = {.red = 0, .green = 0, .blue = 0, .alpha = 0.7 };
all_cancelled = TRUE;
progress_infos = nautilus_progress_info_manager_get_all_infos (self->priv->progress_manager);
for (l = progress_infos; l != NULL; l = l->next) {
remaining_progress += nautilus_progress_info_get_remaining_time (l->data);
elapsed_progress += nautilus_progress_info_get_elapsed_time (l->data);
if (!nautilus_progress_info_get_is_cancelled (l->data)) {
all_cancelled = FALSE;
remaining_progress += nautilus_progress_info_get_remaining_time (l->data);
elapsed_progress += nautilus_progress_info_get_elapsed_time (l->data);
}
}
total_progress = remaining_progress + elapsed_progress;
if (total_progress > 0)
ratio = MAX (0.05, elapsed_progress / total_progress);
else
ratio = 0.05;
if (all_cancelled) {
ratio = 1.0;
} else {
if (total_progress > 0) {
ratio = MAX (0.05, elapsed_progress / total_progress);
} else {
ratio = 0.05;
}
}
width = gtk_widget_get_allocated_width (widget);
......@@ -636,6 +699,17 @@ on_operations_icon_draw (GtkWidget *widget,
cairo_fill (cr);
}
static void
on_operations_button_toggled (NautilusToolbar *self)
{
unschedule_remove_finished_operations (self);
if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->priv->operations_button))) {
schedule_remove_finished_operations (self);
} else {
update_operations (self);
}
}
static void
nautilus_toolbar_init (NautilusToolbar *self)
{
......@@ -750,6 +824,7 @@ nautilus_toolbar_dispose (GObject *obj)
toolbar_update_appearance, self);
disconnect_progress_infos (self);
unschedule_menu_popup_timeout (self);
unschedule_remove_finished_operations (self);
unschedule_operations_start (self);
g_signal_handlers_disconnect_by_data (self->priv->progress_manager, self);
......@@ -801,6 +876,7 @@ nautilus_toolbar_class_init (NautilusToolbarClass *klass)
gtk_widget_class_bind_template_child_private (widget_class, NautilusToolbar, forward_button);
gtk_widget_class_bind_template_callback (widget_class, on_operations_icon_draw);
gtk_widget_class_bind_template_callback (widget_class, on_operations_button_toggled);
}
void
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment