No issues found
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3 * nautilus-progress-ui-handler.c: file operation progress user interface.
4 *
5 * Copyright (C) 2007, 2011 Red Hat, Inc.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public
18 * License along with this program; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
21 *
22 * Authors: Alexander Larsson <alexl@redhat.com>
23 * Cosimo Cecchi <cosimoc@redhat.com>
24 *
25 */
26
27 #include <config.h>
28
29 #include "nautilus-progress-ui-handler.h"
30
31 #include "nautilus-application.h"
32 #include "nautilus-progress-info-widget.h"
33
34 #include <glib/gi18n.h>
35
36 #include <libnautilus-private/nautilus-progress-info.h>
37 #include <libnautilus-private/nautilus-progress-info-manager.h>
38
39 #include <libnotify/notify.h>
40
41 struct _NautilusProgressUIHandlerPriv {
42 NautilusProgressInfoManager *manager;
43
44 GtkWidget *progress_window;
45 GtkWidget *window_vbox;
46 guint active_infos;
47
48 NotifyNotification *progress_notification;
49 GtkStatusIcon *status_icon;
50 };
51
52 G_DEFINE_TYPE (NautilusProgressUIHandler, nautilus_progress_ui_handler, G_TYPE_OBJECT);
53
54 /* Our policy for showing progress notification is the following:
55 * - file operations that end within two seconds do not get notified in any way
56 * - if no file operations are running, and one passes the two seconds
57 * timeout, a window is displayed with the progress
58 * - if the window is closed, we show a resident notification, or a status icon, depending on
59 * the capabilities of the notification daemon running in the session
60 * - if some file operations are running, and another one passes the two seconds
61 * timeout, and the window is showing, we add it to the window directly
62 * - in the same case, but when the window is not showing, we update the resident
63 * notification, changing its message, or the status icon's tooltip
64 * - when one file operation finishes, if it's not the last one, we only update the
65 * resident notification's message, or the status icon's tooltip
66 * - in the same case, if it's the last one, we close the resident notification,
67 * or the status icon, and trigger a transient one
68 * - in the same case, but the window was showing, we just hide the window
69 */
70
71 #define ACTION_DETAILS "details"
72
73 static gboolean server_has_persistence (void);
74
75 static void
76 status_icon_activate_cb (GtkStatusIcon *icon,
77 NautilusProgressUIHandler *self)
78 {
79 gtk_status_icon_set_visible (icon, FALSE);
80 gtk_window_present (GTK_WINDOW (self->priv->progress_window));
81 }
82
83 static void
84 notification_show_details_cb (NotifyNotification *notification,
85 char *action_name,
86 gpointer user_data)
87 {
88 NautilusProgressUIHandler *self = user_data;
89
90
91 if (g_strcmp0 (action_name, ACTION_DETAILS) != 0) {
92 return;
93 }
94
95 notify_notification_close (self->priv->progress_notification, NULL);
96 gtk_window_present (GTK_WINDOW (self->priv->progress_window));
97 }
98
99 static void
100 progress_ui_handler_ensure_notification (NautilusProgressUIHandler *self)
101 {
102 NotifyNotification *notify;
103
104 if (self->priv->progress_notification) {
105 return;
106 }
107
108 notify = notify_notification_new (_("File Operations"),
109 NULL, NULL);
110 self->priv->progress_notification = notify;
111
112 notify_notification_set_category (notify, "transfer");
113 notify_notification_set_hint (notify, "resident",
114 g_variant_new_boolean (TRUE));
115
116 notify_notification_add_action (notify, ACTION_DETAILS,
117 _("Show Details"),
118 notification_show_details_cb,
119 self,
120 NULL);
121 }
122
123 static void
124 progress_ui_handler_ensure_status_icon (NautilusProgressUIHandler *self)
125 {
126 GIcon *icon;
127 GtkStatusIcon *status_icon;
128
129 if (self->priv->status_icon != NULL) {
130 return;
131 }
132
133 icon = g_themed_icon_new_with_default_fallbacks ("system-file-manager-symbolic");
134 status_icon = gtk_status_icon_new_from_gicon (icon);
135 g_signal_connect (status_icon, "activate",
136 (GCallback) status_icon_activate_cb,
137 self);
138
139 gtk_status_icon_set_visible (status_icon, FALSE);
140 g_object_unref (icon);
141
142 self->priv->status_icon = status_icon;
143 }
144
145 static void
146 progress_ui_handler_update_notification (NautilusProgressUIHandler *self)
147 {
148 gchar *body;
149
150 progress_ui_handler_ensure_notification (self);
151
152 body = g_strdup_printf (ngettext ("%'d file operation active",
153 "%'d file operations active",
154 self->priv->active_infos),
155 self->priv->active_infos);
156
157 notify_notification_update (self->priv->progress_notification,
158 _("File Operations"),
159 body,
160 NULL);
161
162 notify_notification_show (self->priv->progress_notification, NULL);
163
164 g_free (body);
165 }
166
167 static void
168 progress_ui_handler_update_status_icon (NautilusProgressUIHandler *self)
169 {
170 gchar *tooltip;
171
172 progress_ui_handler_ensure_status_icon (self);
173
174 tooltip = g_strdup_printf (ngettext ("%'d file operation active",
175 "%'d file operations active",
176 self->priv->active_infos),
177 self->priv->active_infos);
178 gtk_status_icon_set_tooltip_text (self->priv->status_icon, tooltip);
179 g_free (tooltip);
180
181 gtk_status_icon_set_visible (self->priv->status_icon, TRUE);
182 }
183
184 static gboolean
185 progress_window_delete_event (GtkWidget *widget,
186 GdkEvent *event,
187 NautilusProgressUIHandler *self)
188 {
189 gtk_widget_hide (widget);
190
191 if (server_has_persistence ()) {
192 progress_ui_handler_update_notification (self);
193 } else {
194 progress_ui_handler_update_status_icon (self);
195 }
196
197 return TRUE;
198 }
199
200 static void
201 progress_ui_handler_ensure_window (NautilusProgressUIHandler *self)
202 {
203 GtkWidget *vbox, *progress_window;
204
205 if (self->priv->progress_window != NULL) {
206 return;
207 }
208
209 progress_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
210 self->priv->progress_window = progress_window;
211 gtk_window_set_resizable (GTK_WINDOW (progress_window),
212 FALSE);
213 gtk_container_set_border_width (GTK_CONTAINER (progress_window), 10);
214
215 gtk_window_set_title (GTK_WINDOW (progress_window),
216 _("File Operations"));
217 gtk_window_set_wmclass (GTK_WINDOW (progress_window),
218 "file_progress", "Nautilus");
219 gtk_window_set_position (GTK_WINDOW (progress_window),
220 GTK_WIN_POS_CENTER);
221 gtk_window_set_icon_name (GTK_WINDOW (progress_window),
222 "system-file-manager");
223
224 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
225 gtk_box_set_spacing (GTK_BOX (vbox), 5);
226 gtk_container_add (GTK_CONTAINER (progress_window),
227 vbox);
228 self->priv->window_vbox = vbox;
229 gtk_widget_show (vbox);
230
231 g_signal_connect (progress_window,
232 "delete-event",
233 (GCallback) progress_window_delete_event, self);
234 }
235
236 static void
237 progress_ui_handler_update_notification_or_status (NautilusProgressUIHandler *self)
238 {
239 if (server_has_persistence ()) {
240 progress_ui_handler_update_notification (self);
241 } else {
242 progress_ui_handler_update_status_icon (self);
243 }
244 }
245
246 static void
247 progress_ui_handler_add_to_window (NautilusProgressUIHandler *self,
248 NautilusProgressInfo *info)
249 {
250 GtkWidget *progress;
251
252 progress = nautilus_progress_info_widget_new (info);
253 progress_ui_handler_ensure_window (self);
254
255 gtk_box_pack_start (GTK_BOX (self->priv->window_vbox),
256 progress,
257 FALSE, FALSE, 6);
258
259 gtk_widget_show (progress);
260 }
261
262 static void
263 progress_ui_handler_show_complete_notification (NautilusProgressUIHandler *self)
264 {
265 NotifyNotification *complete_notification;
266
267 /* don't display the notification if we'd be using a status icon */
268 if (!server_has_persistence ()) {
269 return;
270 }
271
272 complete_notification = notify_notification_new (_("File Operations"),
273 _("All file operations have been successfully completed"),
274 NULL);
275 notify_notification_show (complete_notification, NULL);
276
277 g_object_unref (complete_notification);
278 }
279
280 static void
281 progress_ui_handler_hide_notification_or_status (NautilusProgressUIHandler *self)
282 {
283 if (self->priv->status_icon != NULL) {
284 gtk_status_icon_set_visible (self->priv->status_icon, FALSE);
285 }
286
287 if (self->priv->progress_notification != NULL) {
288 notify_notification_close (self->priv->progress_notification, NULL);
289 g_clear_object (&self->priv->progress_notification);
290 }
291 }
292
293 static void
294 progress_info_finished_cb (NautilusProgressInfo *info,
295 NautilusProgressUIHandler *self)
296 {
297 self->priv->active_infos--;
298
299 if (self->priv->active_infos > 0) {
300 if (!gtk_widget_get_visible (self->priv->progress_window)) {
301 progress_ui_handler_update_notification_or_status (self);
302 }
303 } else {
304 if (gtk_widget_get_visible (self->priv->progress_window)) {
305 gtk_widget_hide (self->priv->progress_window);
306 } else {
307 progress_ui_handler_hide_notification_or_status (self);
308 progress_ui_handler_show_complete_notification (self);
309 }
310 }
311 }
312
313 static void
314 handle_new_progress_info (NautilusProgressUIHandler *self,
315 NautilusProgressInfo *info)
316 {
317 g_signal_connect (info, "finished",
318 G_CALLBACK (progress_info_finished_cb), self);
319
320 self->priv->active_infos++;
321
322 if (self->priv->active_infos == 1) {
323 /* this is the only active operation, present the window */
324 progress_ui_handler_add_to_window (self, info);
325 gtk_window_present (GTK_WINDOW (self->priv->progress_window));
326 } else {
327 if (gtk_widget_get_visible (self->priv->progress_window)) {
328 progress_ui_handler_add_to_window (self, info);
329 } else {
330 progress_ui_handler_update_notification_or_status (self);
331 }
332 }
333 }
334
335 typedef struct {
336 NautilusProgressInfo *info;
337 NautilusProgressUIHandler *self;
338 } TimeoutData;
339
340 static void
341 timeout_data_free (TimeoutData *data)
342 {
343 g_clear_object (&data->self);
344 g_clear_object (&data->info);
345
346 g_slice_free (TimeoutData, data);
347 }
348
349 static TimeoutData *
350 timeout_data_new (NautilusProgressUIHandler *self,
351 NautilusProgressInfo *info)
352 {
353 TimeoutData *retval;
354
355 retval = g_slice_new0 (TimeoutData);
356 retval->self = g_object_ref (self);
357 retval->info = g_object_ref (info);
358
359 return retval;
360 }
361
362 static gboolean
363 new_op_started_timeout (TimeoutData *data)
364 {
365 NautilusProgressInfo *info = data->info;
366 NautilusProgressUIHandler *self = data->self;
367
368 if (nautilus_progress_info_get_is_paused (info)) {
369 return TRUE;
370 }
371
372 if (!nautilus_progress_info_get_is_finished (info)) {
373 handle_new_progress_info (self, info);
374 }
375
376 timeout_data_free (data);
377
378 return FALSE;
379 }
380
381 static void
382 release_application (NautilusProgressInfo *info,
383 NautilusProgressUIHandler *self)
384 {
385 /* release the GApplication hold we acquired */
386 g_application_release (g_application_get_default ());
387 }
388
389 static void
390 progress_info_started_cb (NautilusProgressInfo *info,
391 NautilusProgressUIHandler *self)
392 {
393 TimeoutData *data;
394
395 /* hold GApplication so we never quit while there's an operation pending */
396 g_application_hold (g_application_get_default ());
397
398 g_signal_connect (info, "finished",
399 G_CALLBACK (release_application), self);
400
401 data = timeout_data_new (self, info);
402
403 /* timeout for the progress window to appear */
404 g_timeout_add_seconds (2,
405 (GSourceFunc) new_op_started_timeout,
406 data);
407 }
408
409 static void
410 new_progress_info_cb (NautilusProgressInfoManager *manager,
411 NautilusProgressInfo *info,
412 NautilusProgressUIHandler *self)
413 {
414 g_signal_connect (info, "started",
415 G_CALLBACK (progress_info_started_cb), self);
416 }
417
418 static void
419 nautilus_progress_ui_handler_dispose (GObject *obj)
420 {
421 NautilusProgressUIHandler *self = NAUTILUS_PROGRESS_UI_HANDLER (obj);
422
423 g_clear_object (&self->priv->manager);
424
425 G_OBJECT_CLASS (nautilus_progress_ui_handler_parent_class)->dispose (obj);
426 }
427
428 static gboolean
429 server_has_persistence (void)
430 {
431 static gboolean retval = FALSE;
432 GList *caps, *l;
433 static gboolean initialized = FALSE;
434
435 if (initialized) {
436 return retval;
437 }
438 initialized = TRUE;
439
440 caps = notify_get_server_caps ();
441 if (caps == NULL) {
442 return FALSE;
443 }
444
445 l = g_list_find_custom (caps, "persistence", (GCompareFunc) g_strcmp0);
446 retval = (l != NULL);
447
448 g_list_free_full (caps, g_free);
449
450 return retval;
451 }
452
453 static void
454 nautilus_progress_ui_handler_init (NautilusProgressUIHandler *self)
455 {
456 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, NAUTILUS_TYPE_PROGRESS_UI_HANDLER,
457 NautilusProgressUIHandlerPriv);
458
459 self->priv->manager = nautilus_progress_info_manager_new ();
460 g_signal_connect (self->priv->manager, "new-progress-info",
461 G_CALLBACK (new_progress_info_cb), self);
462 }
463
464 static void
465 nautilus_progress_ui_handler_class_init (NautilusProgressUIHandlerClass *klass)
466 {
467 GObjectClass *oclass;
468
469 oclass = G_OBJECT_CLASS (klass);
470 oclass->dispose = nautilus_progress_ui_handler_dispose;
471
472 g_type_class_add_private (klass, sizeof (NautilusProgressUIHandlerPriv));
473 }
474
475 NautilusProgressUIHandler *
476 nautilus_progress_ui_handler_new (void)
477 {
478 return g_object_new (NAUTILUS_TYPE_PROGRESS_UI_HANDLER, NULL);
479 }