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.
parent 2db9a295
......@@ -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
......
......@@ -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;
}
......@@ -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 */
......@@ -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);
......
......@@ -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);
}
......@@ -1605,6 +1616,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,
......@@ -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);
}
......
......@@ -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
......@@ -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>
......
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