nautilus-3.6.3/src/nautilus-progress-ui-handler.c

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 }