[nautilus/wip/csoriano/operations: 8/8] operations: show a notification when operation finishes



commit f191cdb79a0107e9ba5f499eb45105b46a737835
Author: Carlos Soriano <csoriano gnome org>
Date:   Tue Jul 14 19:46:21 2015 +0200

    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.

 libnautilus-private/nautilus-file-operations.c |   21 +++
 libnautilus-private/nautilus-progress-info.c   |   25 ++++
 libnautilus-private/nautilus-progress-info.h   |    4 +
 src/nautilus-toolbar.c                         |   38 ++++++-
 src/nautilus-window.c                          |  166 ++++++++++++++++++------
 src/nautilus-window.h                          |    4 +
 src/nautilus-window.ui                         |   69 ++++++++++
 7 files changed, 285 insertions(+), 42 deletions(-)
---
diff --git a/libnautilus-private/nautilus-file-operations.c b/libnautilus-private/nautilus-file-operations.c
index 88d35ff..84bbcf7 100644
--- a/libnautilus-private/nautilus-file-operations.c
+++ b/libnautilus-private/nautilus-file-operations.c
@@ -4901,6 +4901,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);
 
@@ -4938,6 +4941,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 =
@@ -5476,6 +5482,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 =
@@ -5802,6 +5811,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 =
@@ -5837,12 +5849,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 =
@@ -5868,6 +5887,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 389aaeb..e6a2c70 100644
--- a/libnautilus-private/nautilus-progress-info.c
+++ b/libnautilus-private/nautilus-progress-info.c
@@ -64,6 +64,8 @@ struct _NautilusProgressInfo
        gboolean finish_at_idle;
        gboolean changed_at_idle;
        gboolean progress_at_idle;
+
+        GFile *destination;
 };
 
 struct _NautilusProgressInfoClass
@@ -85,6 +87,7 @@ nautilus_progress_info_finalize (GObject *object)
        g_free (info->status);
        g_free (info->details);
        g_object_unref (info->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);
@@ -614,3 +617,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 0ce0ad1..e842667 100644
--- a/libnautilus-private/nautilus-progress-info.h
+++ b/libnautilus-private/nautilus-progress-info.h
@@ -85,6 +85,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 d6b39de..f153ceb 100644
--- a/src/nautilus-toolbar.c
+++ b/src/nautilus-toolbar.c
@@ -59,6 +59,7 @@ struct _NautilusToolbarPrivate {
 
        guint popup_timeout_id;
         guint operations_timeout_id;
+        guint operations_button_attention_timeout_id;
 
        GtkWidget *operations_button;
        GtkWidget *view_button;
@@ -455,13 +456,48 @@ remove_finished_operations (NautilusToolbar *self)
         update_operations_button_visibility (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_finished (NautilusProgressInfo *info,
                            NautilusToolbar      *self)
 {
-        if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->priv->operations_button))) {
+        GtkStyleContext *style_context;
+        gchar *main_label;
+        GFile *folder_to_open;
+
+        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) {
                 remove_finished_operations (self);
+                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_clear_object (&folder_to_open);
 }
 
 static void
diff --git a/src/nautilus-window.c b/src/nautilus-window.c
index d315316..8b00d4e 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,40 @@ 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 +1536,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 +1589,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 +1609,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 +1618,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,10 +2184,7 @@ 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;
-       }
+        remove_notifications (window);
 
        g_signal_handlers_disconnect_by_func (nautilus_file_undo_manager_get (),
                                               G_CALLBACK (nautilus_window_on_undo_changed),
@@ -2508,6 +2585,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 +2639,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 1612145..4b16bf7 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 5205ced..2a8c439 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>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]