diff options
author | Carlos Soriano <csoriano@gnome.org> | 2015-07-14 19:46:21 +0200 |
---|---|---|
committer | Carlos Soriano <csoriano@gnome.org> | 2015-07-17 00:42:05 +0200 |
commit | 9bea0f6f801195716c3627d27eb3fda451ff78d7 (patch) | |
tree | d23f2f84acda97f5cf43799383a3072c41f43cc2 | |
parent | 6c576b9266ee223075c08b2441b675ac7c1aef07 (diff) | |
download | nautilus-9bea0f6f801195716c3627d27eb3fda451ff78d7.tar.gz |
operations: show a notification when operation finishes
When the operations popover is not visible, we have to give
feedback to the user about if an operation finished.
For that, show a in-app notification in case the operation popover
is closed and a operation finishes. Also, to link the notification
with the operation button, make the button pulse for a second.
The code interaction between nautilus-file-operations and nautilus-progress-info
start to get crowed. At some point, the best we can do is delegate all the
progress UI part to nautilus-progress-info, providing all the needed information
like the CommonJob, TransferInfo and SourceInfo; so we avoid the current
odd interaction between them. This is a future work.
-rw-r--r-- | libnautilus-private/nautilus-file-operations.c | 21 | ||||
-rw-r--r-- | libnautilus-private/nautilus-progress-info.c | 25 | ||||
-rw-r--r-- | libnautilus-private/nautilus-progress-info.h | 4 | ||||
-rw-r--r-- | src/nautilus-toolbar.c | 50 | ||||
-rw-r--r-- | src/nautilus-window.c | 171 | ||||
-rw-r--r-- | src/nautilus-window.h | 4 | ||||
-rw-r--r-- | src/nautilus-window.ui | 69 |
7 files changed, 300 insertions, 44 deletions
diff --git a/libnautilus-private/nautilus-file-operations.c b/libnautilus-private/nautilus-file-operations.c index 38bdf3977..4b5b8539b 100644 --- a/libnautilus-private/nautilus-file-operations.c +++ b/libnautilus-private/nautilus-file-operations.c @@ -4910,6 +4910,9 @@ nautilus_file_operations_copy_file (GFile *source_file, job->done_callback_data = done_callback_data; job->files = g_list_append (NULL, g_object_ref (source_file)); job->destination = g_object_ref (target_dir); + /* Need to indicate the destination for the operation notification open + * button. */ + nautilus_progress_info_set_destination (((CommonJob *)job)->progress, target_dir); job->target_name = g_strdup (new_name); job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL); @@ -4947,6 +4950,9 @@ nautilus_file_operations_copy (GList *files, job->done_callback_data = done_callback_data; job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL); job->destination = g_object_ref (target_dir); + /* Need to indicate the destination for the operation notification open + * button. */ + nautilus_progress_info_set_destination (((CommonJob *)job)->progress, target_dir); if (relative_item_points != NULL && relative_item_points->len > 0) { job->icon_positions = @@ -5485,6 +5491,9 @@ nautilus_file_operations_move (GList *files, job->done_callback_data = done_callback_data; job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL); job->destination = g_object_ref (target_dir); + /* Need to indicate the destination for the operation notification open + * button. */ + nautilus_progress_info_set_destination (((CommonJob *)job)->progress, target_dir); if (relative_item_points != NULL && relative_item_points->len > 0) { job->icon_positions = @@ -5811,6 +5820,9 @@ nautilus_file_operations_link (GList *files, job->done_callback_data = done_callback_data; job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL); job->destination = g_object_ref (target_dir); + /* Need to indicate the destination for the operation notification open + * button. */ + nautilus_progress_info_set_destination (((CommonJob *)job)->progress, target_dir); if (relative_item_points != NULL && relative_item_points->len > 0) { job->icon_positions = @@ -5846,12 +5858,19 @@ nautilus_file_operations_duplicate (GList *files, gpointer done_callback_data) { CopyMoveJob *job; + GFile *parent; job = op_job_new (CopyMoveJob, parent_window); job->done_callback = done_callback; job->done_callback_data = done_callback_data; job->files = g_list_copy_deep (files, (GCopyFunc) g_object_ref, NULL); job->destination = NULL; + /* Duplicate files doesn't have a destination, since is the same as source. + * For that set as destination the source parent folder */ + parent = g_file_get_parent (files->data); + /* Need to indicate the destination for the operation notification open + * button. */ + nautilus_progress_info_set_destination (((CommonJob *)job)->progress, parent); if (relative_item_points != NULL && relative_item_points->len > 0) { job->icon_positions = @@ -5877,6 +5896,8 @@ nautilus_file_operations_duplicate (GList *files, NULL, /* destroy notify */ 0, job->common.cancellable); + + g_object_unref (parent); } static gboolean diff --git a/libnautilus-private/nautilus-progress-info.c b/libnautilus-private/nautilus-progress-info.c index ddec24896..24988991b 100644 --- a/libnautilus-private/nautilus-progress-info.c +++ b/libnautilus-private/nautilus-progress-info.c @@ -68,6 +68,8 @@ struct _NautilusProgressInfo gboolean cancel_at_idle; gboolean changed_at_idle; gboolean progress_at_idle; + + GFile *destination; }; struct _NautilusProgressInfoClass @@ -92,6 +94,7 @@ nautilus_progress_info_finalize (GObject *object) g_object_unref (info->cancellable); g_cancellable_cancel (info->details_in_thread_cancellable); g_clear_object (&info->details_in_thread_cancellable); + g_clear_object (&info->destination); if (G_OBJECT_CLASS (nautilus_progress_info_parent_class)->finalize) { (*G_OBJECT_CLASS (nautilus_progress_info_parent_class)->finalize) (object); @@ -687,3 +690,25 @@ nautilus_progress_info_get_elapsed_time (NautilusProgressInfo *info) return elapsed_time; } + +void +nautilus_progress_info_set_destination (NautilusProgressInfo *info, + GFile *file) +{ + G_LOCK (progress_info); + g_clear_object (&info->destination); + info->destination = g_object_ref (file); + G_UNLOCK (progress_info); +} + +GFile * +nautilus_progress_info_get_destination (NautilusProgressInfo *info) +{ + GFile *destination; + + G_LOCK (progress_info); + destination = g_object_ref (info->destination); + G_UNLOCK (progress_info); + + return destination; +} diff --git a/libnautilus-private/nautilus-progress-info.h b/libnautilus-private/nautilus-progress-info.h index 44b812c57..cf0daebac 100644 --- a/libnautilus-private/nautilus-progress-info.h +++ b/libnautilus-private/nautilus-progress-info.h @@ -86,6 +86,10 @@ void nautilus_progress_info_set_elapsed_time (NautilusProgressInfo *inf gdouble time); gdouble nautilus_progress_info_get_elapsed_time (NautilusProgressInfo *info); +void nautilus_progress_info_set_destination (NautilusProgressInfo *info, + GFile *file); +GFile *nautilus_progress_info_get_destination (NautilusProgressInfo *info); + #endif /* NAUTILUS_PROGRESS_INFO_H */ diff --git a/src/nautilus-toolbar.c b/src/nautilus-toolbar.c index 0b85ed0ed..b4934fe9f 100644 --- a/src/nautilus-toolbar.c +++ b/src/nautilus-toolbar.c @@ -61,6 +61,7 @@ struct _NautilusToolbarPrivate { guint popup_timeout_id; guint start_operations_timeout_id; guint remove_finished_operations_timeout_id; + guint operations_button_attention_timeout_id; GtkWidget *operations_button; GtkWidget *view_button; @@ -492,6 +493,19 @@ schedule_remove_finished_operations (NautilusToolbar *self) } } +static gboolean +remove_operations_button_attention_style (NautilusToolbar *self) +{ + GtkStyleContext *style_context; + + style_context = gtk_widget_get_style_context (self->priv->operations_button); + gtk_style_context_remove_class (style_context, + "suggested-action"); + self->priv->operations_button_attention_timeout_id = 0; + + return G_SOURCE_REMOVE; +} + static void on_progress_info_cancelled (NautilusToolbar *self) { @@ -510,14 +524,40 @@ on_progress_info_progress_changed (NautilusToolbar *self) } static void -on_progress_info_finished (NautilusToolbar *self) +on_progress_info_finished (NautilusToolbar *self, + NautilusProgressInfo *info) { + GtkStyleContext *style_context; + gchar *main_label; + GFile *folder_to_open; + /* 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); } + + folder_to_open = nautilus_progress_info_get_destination (info); + /* If destination is null, don't show a notification. This happens when the + * operation is a trash operation, which we already show a diferent kind of + * notification */ + if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->priv->operations_button)) && + folder_to_open != NULL) { + style_context = gtk_widget_get_style_context (self->priv->operations_button); + gtk_style_context_add_class (style_context, + "suggested-action"); + self->priv->operations_button_attention_timeout_id = g_timeout_add_seconds (1, + (GSourceFunc) remove_operations_button_attention_style, + self); + main_label = nautilus_progress_info_get_status (info); + nautilus_window_show_operation_notification (self->priv->window, + main_label, + folder_to_open); + g_free (main_label); + } + + g_clear_object (&folder_to_open); } static void @@ -594,9 +634,6 @@ on_progress_info_started_timeout (NautilusToolbar *self) 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; } } @@ -827,6 +864,11 @@ nautilus_toolbar_dispose (GObject *obj) unschedule_remove_finished_operations (self); unschedule_operations_start (self); + if (self->priv->operations_button_attention_timeout_id != 0) { + g_source_remove (self->priv->operations_button_attention_timeout_id); + self->priv->operations_button_attention_timeout_id = 0; + } + g_signal_handlers_disconnect_by_data (self->priv->progress_manager, self); g_clear_object (&self->priv->progress_manager); diff --git a/src/nautilus-window.c b/src/nautilus-window.c index d315316ab..748e91c09 100644 --- a/src/nautilus-window.c +++ b/src/nautilus-window.c @@ -120,6 +120,12 @@ struct _NautilusWindowPrivate { GtkWidget *notification_delete_close; GtkWidget *notification_delete_undo; guint notification_delete_timeout_id; + GtkWidget *notification_operation; + GtkWidget *notification_operation_label; + GtkWidget *notification_operation_close; + GtkWidget *notification_operation_open; + guint notification_operation_timeout_id; + GFile *folder_to_open; /* Toolbar */ GtkWidget *toolbar; @@ -1488,30 +1494,39 @@ nautilus_window_ensure_location_entry (NautilusWindow *window) } static void -nautilus_window_on_notification_delete_undo_clicked (GtkWidget *notification, - gpointer user_data) +remove_notifications (NautilusWindow *window) { - NautilusWindow *window; - - window = NAUTILUS_WINDOW (user_data); + GtkRevealerTransitionType transition_type; - if (window->priv->notification_delete_timeout_id != 0) { - g_source_remove (window->priv->notification_delete_timeout_id); - window->priv->notification_delete_timeout_id = 0; + /* Hide it inmediatily so we can animate the new notification. */ + transition_type = gtk_revealer_get_transition_type (GTK_REVEALER (window->priv->notification_delete)); + gtk_revealer_set_transition_type (GTK_REVEALER (window->priv->notification_delete), + GTK_REVEALER_TRANSITION_TYPE_NONE); + gtk_revealer_set_reveal_child (GTK_REVEALER (window->priv->notification_delete), + FALSE); + gtk_revealer_set_transition_type (GTK_REVEALER (window->priv->notification_delete), + transition_type); + if (window->priv->notification_delete_timeout_id != 0) { + g_source_remove (window->priv->notification_delete_timeout_id); + window->priv->notification_delete_timeout_id = 0; } - gtk_revealer_set_reveal_child (GTK_REVEALER (window->priv->notification_delete), FALSE); - nautilus_file_undo_manager_undo (GTK_WINDOW (window)); + transition_type = gtk_revealer_get_transition_type (GTK_REVEALER (window->priv->notification_operation)); + gtk_revealer_set_transition_type (GTK_REVEALER (window->priv->notification_operation), + GTK_REVEALER_TRANSITION_TYPE_NONE); + gtk_revealer_set_reveal_child (GTK_REVEALER (window->priv->notification_operation), + FALSE); + gtk_revealer_set_transition_type (GTK_REVEALER (window->priv->notification_operation), + transition_type); + if (window->priv->notification_operation_timeout_id != 0) { + g_source_remove (window->priv->notification_operation_timeout_id); + window->priv->notification_operation_timeout_id = 0; + } } static void -nautilus_window_on_notification_delete_close_clicked (GtkWidget *notification, - gpointer user_data) +hide_notification_delete (NautilusWindow *window) { - NautilusWindow *window; - - window = NAUTILUS_WINDOW (user_data); - if (window->priv->notification_delete_timeout_id != 0) { g_source_remove (window->priv->notification_delete_timeout_id); window->priv->notification_delete_timeout_id = 0; @@ -1520,19 +1535,26 @@ nautilus_window_on_notification_delete_close_clicked (GtkWidget *notification, gtk_revealer_set_reveal_child (GTK_REVEALER (window->priv->notification_delete), FALSE); } -static gboolean -nautilus_window_on_notification_delete_timeout (gpointer user_data) +static void +nautilus_window_on_notification_delete_undo_clicked (GtkWidget *notification, + NautilusWindow *window) { - NautilusWindow *window; + hide_notification_delete (window); - window = NAUTILUS_WINDOW (user_data); + nautilus_file_undo_manager_undo (GTK_WINDOW (window)); +} - if (window->priv->notification_delete_timeout_id != 0) { - g_source_remove (window->priv->notification_delete_timeout_id); - window->priv->notification_delete_timeout_id = 0; - } +static void +nautilus_window_on_notification_delete_close_clicked (GtkWidget *notification, + NautilusWindow *window) +{ + hide_notification_delete (window); +} - gtk_revealer_set_reveal_child (GTK_REVEALER (window->priv->notification_delete), FALSE); +static gboolean +nautilus_window_on_notification_delete_timeout (NautilusWindow *window) +{ + hide_notification_delete (window); return FALSE; } @@ -1566,20 +1588,9 @@ nautilus_window_on_undo_changed (NautilusFileUndoManager *manager, { NautilusFileUndoInfo *undo_info; NautilusFileUndoManagerState state; - int transition_durantion; gchar *label; GList *files; - /* Hide it inmediatily so we can animate the new notification. */ - transition_durantion = gtk_revealer_get_transition_duration (GTK_REVEALER (window->priv->notification_delete)); - gtk_revealer_set_transition_duration (GTK_REVEALER (window->priv->notification_delete), 0); - gtk_revealer_set_reveal_child (GTK_REVEALER (window->priv->notification_delete), FALSE); - gtk_revealer_set_transition_duration (GTK_REVEALER (window->priv->notification_delete), transition_durantion); - if (window->priv->notification_delete_timeout_id != 0) { - g_source_remove (window->priv->notification_delete_timeout_id); - window->priv->notification_delete_timeout_id = 0; - } - undo_info = nautilus_file_undo_manager_get_action (); state = nautilus_file_undo_manager_get_state (); @@ -1597,7 +1608,7 @@ nautilus_window_on_undo_changed (NautilusFileUndoManager *manager, gtk_label_set_markup (GTK_LABEL (window->priv->notification_delete_label), label); gtk_revealer_set_reveal_child (GTK_REVEALER (window->priv->notification_delete), TRUE); window->priv->notification_delete_timeout_id = g_timeout_add_seconds (NOTIFICATION_TIMEOUT, - nautilus_window_on_notification_delete_timeout, + (GSourceFunc) nautilus_window_on_notification_delete_timeout, window); g_free (label); } @@ -1606,6 +1617,74 @@ nautilus_window_on_undo_changed (NautilusFileUndoManager *manager, } static void +hide_notification_operation (NautilusWindow *window) +{ + if (window->priv->notification_operation_timeout_id != 0) { + g_source_remove (window->priv->notification_operation_timeout_id); + window->priv->notification_operation_timeout_id = 0; + } + + gtk_revealer_set_reveal_child (GTK_REVEALER (window->priv->notification_operation), FALSE); + g_clear_object (&window->priv->folder_to_open); +} + +static void +on_notification_operation_open_clicked (GtkWidget *notification, + NautilusWindow *window) +{ + nautilus_window_slot_open_location (window->priv->active_slot, + window->priv->folder_to_open, + 0); + hide_notification_operation (window); +} + +static void +on_notification_operation_close_clicked (GtkWidget *notification, + NautilusWindow *window) +{ + hide_notification_operation (window); +} + +static gboolean +on_notification_operation_timeout (NautilusWindow *window) +{ + hide_notification_operation (window); + + return FALSE; +} + +void +nautilus_window_show_operation_notification (NautilusWindow *window, + gchar *main_label, + GFile *folder_to_open) +{ + gchar *button_label; + gchar *folder_name; + NautilusFile *folder; + + if (gtk_window_has_toplevel_focus (GTK_WINDOW (window)) && + !NAUTILUS_IS_DESKTOP_WINDOW (window)) { + remove_notifications (window); + window->priv->folder_to_open = g_object_ref (folder_to_open); + folder = nautilus_file_get (folder_to_open); + folder_name = nautilus_file_get_display_name (folder); + button_label = g_strdup_printf (_("Open %s"), folder_name); + gtk_label_set_text (GTK_LABEL (window->priv->notification_operation_label), + main_label); + gtk_button_set_label (GTK_BUTTON (window->priv->notification_operation_open), + button_label); + gtk_revealer_set_reveal_child (GTK_REVEALER (window->priv->notification_operation), TRUE); + window->priv->notification_operation_timeout_id = g_timeout_add_seconds (NOTIFICATION_TIMEOUT, + (GSourceFunc) on_notification_operation_timeout, + window); + g_object_unref (folder); + g_free (folder_name); + g_free (button_label); + } + +} + +static void path_bar_location_changed_callback (GtkWidget *widget, GFile *location, NautilusWindow *window) @@ -2104,9 +2183,14 @@ nautilus_window_finalize (GObject *object) window->priv->sidebar_width_handler_id = 0; } - if (window->priv->notification_delete_timeout_id != 0) { - g_source_remove (window->priv->notification_delete_timeout_id); - window->priv->notification_delete_timeout_id = 0; + if (window->priv->notification_delete_timeout_id != 0) { + g_source_remove (window->priv->notification_delete_timeout_id); + window->priv->notification_delete_timeout_id = 0; + } + + if (window->priv->notification_operation_timeout_id != 0) { + g_source_remove (window->priv->notification_operation_timeout_id); + window->priv->notification_operation_timeout_id = 0; } g_signal_handlers_disconnect_by_func (nautilus_file_undo_manager_get (), @@ -2508,6 +2592,10 @@ nautilus_window_class_init (NautilusWindowClass *class) gtk_widget_class_bind_template_child_private (wclass, NautilusWindow, notification_delete_label); gtk_widget_class_bind_template_child_private (wclass, NautilusWindow, notification_delete_undo); gtk_widget_class_bind_template_child_private (wclass, NautilusWindow, notification_delete_close); + gtk_widget_class_bind_template_child_private (wclass, NautilusWindow, notification_operation); + gtk_widget_class_bind_template_child_private (wclass, NautilusWindow, notification_operation_label); + gtk_widget_class_bind_template_child_private (wclass, NautilusWindow, notification_operation_open); + gtk_widget_class_bind_template_child_private (wclass, NautilusWindow, notification_operation_close); properties[PROP_DISABLE_CHROME] = g_param_spec_boolean ("disable-chrome", @@ -2558,6 +2646,9 @@ nautilus_window_class_init (NautilusWindowClass *class) G_CALLBACK(use_extra_mouse_buttons_changed), NULL); + gtk_widget_class_bind_template_callback (wclass, on_notification_operation_open_clicked); + gtk_widget_class_bind_template_callback (wclass, on_notification_operation_close_clicked); + g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); } diff --git a/src/nautilus-window.h b/src/nautilus-window.h index 1612145d1..4b16bf752 100644 --- a/src/nautilus-window.h +++ b/src/nautilus-window.h @@ -146,4 +146,8 @@ void nautilus_window_sync_allow_stop (NautilusWindow *window, NautilusWindowSlot *slot); void nautilus_window_sync_title (NautilusWindow *window, NautilusWindowSlot *slot); + +void nautilus_window_show_operation_notification (NautilusWindow *window, + gchar *main_label, + GFile *folder_to_open); #endif diff --git a/src/nautilus-window.ui b/src/nautilus-window.ui index 5205ced76..2a8c439c9 100644 --- a/src/nautilus-window.ui +++ b/src/nautilus-window.ui @@ -123,6 +123,75 @@ </child> </object> </child> + <child type="overlay"> + <object class="GtkRevealer" id="notification_operation"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">start</property> + <property name="transition_duration">100</property> + <child> + <object class="GtkFrame"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_start">12</property> + <property name="margin_end">4</property> + <child> + <object class="GtkLabel" id="notification_operation_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="max_width_chars">50</property> + <property name="ellipsize">middle</property> + <property name="margin_end">30</property> + </object> + </child> + <child> + <object class="GtkButton" id="notification_operation_open"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="no_show_all">True</property> + <property name="margin_end">6</property> + <signal name="clicked" handler="on_notification_operation_open_clicked" object="NautilusWindow" swapped="no"/> + <style> + <class name="text-button"/> + </style> + </object> + </child> + <child> + <object class="GtkButton" id="notification_operation_close"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="relief">none</property> + <property name="focus_on_click">False</property> + <signal name="clicked" handler="on_notification_operation_close_clicked" object="NautilusWindow" swapped="no"/> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">window-close-symbolic</property> + <property name="icon_size">2</property> + </object> + </child> + <style> + <class name="image-button"/> + </style> + </object> + </child> + </object> + </child> + <style> + <class name="app-notification"/> + </style> + </object> + </child> + </object> + </child> </object> </child> </object> |