1 /*
2  * e-activity-bar.c
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 #include "evolution-config.h"
19 
20 #include <glib/gi18n-lib.h>
21 #include <libedataserver/libedataserver.h>
22 
23 #include "e-dialog-widgets.h"
24 #include "e-misc-utils.h"
25 #include "e-spinner.h"
26 
27 #include "e-activity-bar.h"
28 
29 #define E_ACTIVITY_BAR_GET_PRIVATE(obj) \
30 	(G_TYPE_INSTANCE_GET_PRIVATE \
31 	((obj), E_TYPE_ACTIVITY_BAR, EActivityBarPrivate))
32 
33 #define FEEDBACK_PERIOD		1 /* seconds */
34 #define COMPLETED_ICON_NAME	"emblem-default"
35 
36 struct _EActivityBarPrivate {
37 	EActivity *activity;	/* weak reference */
38 	GtkWidget *image;	/* not referenced */
39 	GtkWidget *label;	/* not referenced */
40 	GtkWidget *cancel;	/* not referenced */
41 	GtkWidget *spinner;	/* not referenced */
42 
43 	/* If the user clicks the Cancel button, keep the cancelled
44 	 * EActivity object alive for a short duration so the user
45 	 * gets some visual feedback that cancellation worked. */
46 	guint timeout_id;
47 };
48 
49 enum {
50 	PROP_0,
51 	PROP_ACTIVITY
52 };
53 
54 G_DEFINE_TYPE (
55 	EActivityBar,
56 	e_activity_bar,
57 	GTK_TYPE_INFO_BAR)
58 
59 typedef struct _EActivityBarTimeoutData {
60 	EActivityBar *bar;
61 	EActivity *activity;
62 } EActivityBarTimeoutData;
63 
64 /* This is needed, because the scheduled timeout can hold the last
65    reference to the 'activity', which means removing the source will
66    free it, which in turn calls e_activity_bar_set_activity() with NULL,
67    which would call g_source_remove() again, with the same timeout id.
68 */
69 static void
activity_bar_unset_timeout_id(EActivityBar * bar)70 activity_bar_unset_timeout_id (EActivityBar *bar)
71 {
72 	guint timeout_id;
73 
74 	g_return_if_fail (E_IS_ACTIVITY_BAR (bar));
75 
76 	timeout_id = bar->priv->timeout_id;
77 	bar->priv->timeout_id = 0;
78 
79 	if (timeout_id)
80 		g_source_remove (timeout_id);
81 }
82 
83 static void
activity_bar_timeout_data_free(gpointer ptr)84 activity_bar_timeout_data_free (gpointer ptr)
85 {
86 	EActivityBarTimeoutData *data = ptr;
87 
88 	if (data) {
89 		g_object_unref (data->activity);
90 		g_slice_free (EActivityBarTimeoutData, data);
91 	}
92 }
93 
94 static gboolean
activity_bar_timeout_reached(gpointer user_data)95 activity_bar_timeout_reached (gpointer user_data)
96 {
97 	EActivityBarTimeoutData *data = user_data;
98 
99 	g_return_val_if_fail (data != NULL, FALSE);
100 	g_return_val_if_fail (E_IS_ACTIVITY_BAR (data->bar), FALSE);
101 
102 	if (!g_source_is_destroyed (g_main_current_source ()) &&
103 	    g_source_get_id (g_main_current_source ()) == data->bar->priv->timeout_id)
104 		data->bar->priv->timeout_id = 0;
105 
106 	return FALSE;
107 }
108 
109 static void
activity_bar_feedback(EActivityBar * bar)110 activity_bar_feedback (EActivityBar *bar)
111 {
112 	EActivity *activity;
113 	EActivityState state;
114 	EActivityBarTimeoutData *data;
115 
116 	activity = e_activity_bar_get_activity (bar);
117 	g_return_if_fail (E_IS_ACTIVITY (activity));
118 
119 	state = e_activity_get_state (activity);
120 	if (state != E_ACTIVITY_CANCELLED && state != E_ACTIVITY_COMPLETED)
121 		return;
122 
123 	activity_bar_unset_timeout_id (bar);
124 
125 	data = g_slice_new0 (EActivityBarTimeoutData);
126 
127 	data->bar = bar;
128 	data->activity = g_object_ref (activity);
129 
130 	/* Hold a reference on the EActivity for a short
131 	 * period so the activity bar stays visible. */
132 	bar->priv->timeout_id = e_named_timeout_add_seconds_full (
133 		G_PRIORITY_LOW, FEEDBACK_PERIOD, activity_bar_timeout_reached,
134 		data, activity_bar_timeout_data_free);
135 }
136 
137 static void
activity_bar_update(EActivityBar * bar)138 activity_bar_update (EActivityBar *bar)
139 {
140 	EActivity *activity;
141 	EActivityState state;
142 	GCancellable *cancellable;
143 	const gchar *icon_name;
144 	gboolean sensitive;
145 	gboolean visible;
146 	gchar *description;
147 
148 	activity = e_activity_bar_get_activity (bar);
149 
150 	if (activity == NULL) {
151 		gtk_widget_hide (GTK_WIDGET (bar));
152 		return;
153 	}
154 
155 	cancellable = e_activity_get_cancellable (activity);
156 	icon_name = e_activity_get_icon_name (activity);
157 	state = e_activity_get_state (activity);
158 
159 	description = e_activity_describe (activity);
160 	gtk_label_set_text (GTK_LABEL (bar->priv->label), description);
161 
162 	if (state == E_ACTIVITY_CANCELLED) {
163 		PangoAttribute *attr;
164 		PangoAttrList *attr_list;
165 
166 		attr_list = pango_attr_list_new ();
167 
168 		attr = pango_attr_strikethrough_new (TRUE);
169 		pango_attr_list_insert (attr_list, attr);
170 
171 		gtk_label_set_attributes (
172 			GTK_LABEL (bar->priv->label), attr_list);
173 
174 		pango_attr_list_unref (attr_list);
175 	} else
176 		gtk_label_set_attributes (
177 			GTK_LABEL (bar->priv->label), NULL);
178 
179 	if (state == E_ACTIVITY_COMPLETED)
180 		icon_name = COMPLETED_ICON_NAME;
181 
182 	if (state == E_ACTIVITY_CANCELLED) {
183 		gtk_image_set_from_icon_name (
184 			GTK_IMAGE (bar->priv->image),
185 			"process-stop", GTK_ICON_SIZE_BUTTON);
186 		gtk_widget_show (bar->priv->image);
187 	} else if (icon_name != NULL) {
188 		gtk_image_set_from_icon_name (
189 			GTK_IMAGE (bar->priv->image),
190 			icon_name, GTK_ICON_SIZE_BUTTON);
191 		gtk_widget_show (bar->priv->image);
192 	} else {
193 		gtk_widget_hide (bar->priv->image);
194 	}
195 
196 	visible = (cancellable != NULL);
197 	gtk_widget_set_visible (bar->priv->cancel, visible);
198 
199 	sensitive = (state == E_ACTIVITY_RUNNING);
200 	gtk_widget_set_sensitive (bar->priv->cancel, sensitive);
201 
202 	visible = (description != NULL && *description != '\0');
203 	gtk_widget_set_visible (GTK_WIDGET (bar), visible);
204 
205 	g_free (description);
206 }
207 
208 static void
activity_bar_cancel(EActivityBar * bar)209 activity_bar_cancel (EActivityBar *bar)
210 {
211 	EActivity *activity;
212 
213 	activity = e_activity_bar_get_activity (bar);
214 	g_return_if_fail (E_IS_ACTIVITY (activity));
215 
216 	e_activity_cancel (activity);
217 
218 	activity_bar_update (bar);
219 }
220 
221 static void
activity_bar_weak_notify_cb(EActivityBar * bar,GObject * where_the_object_was)222 activity_bar_weak_notify_cb (EActivityBar *bar,
223                              GObject *where_the_object_was)
224 {
225 	g_return_if_fail (E_IS_ACTIVITY_BAR (bar));
226 
227 	bar->priv->activity = NULL;
228 	e_activity_bar_set_activity (bar, NULL);
229 }
230 
231 static void
activity_bar_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)232 activity_bar_set_property (GObject *object,
233                            guint property_id,
234                            const GValue *value,
235                            GParamSpec *pspec)
236 {
237 	switch (property_id) {
238 		case PROP_ACTIVITY:
239 			e_activity_bar_set_activity (
240 				E_ACTIVITY_BAR (object),
241 				g_value_get_object (value));
242 			return;
243 	}
244 
245 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
246 }
247 
248 static void
activity_bar_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)249 activity_bar_get_property (GObject *object,
250                            guint property_id,
251                            GValue *value,
252                            GParamSpec *pspec)
253 {
254 	switch (property_id) {
255 		case PROP_ACTIVITY:
256 			g_value_set_object (
257 				value, e_activity_bar_get_activity (
258 				E_ACTIVITY_BAR (object)));
259 			return;
260 	}
261 
262 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
263 }
264 
265 static void
activity_bar_constructed(GObject * object)266 activity_bar_constructed (GObject *object)
267 {
268 	GObject *revealer;
269 
270 	/* Chain up to parent's method. */
271 	G_OBJECT_CLASS (e_activity_bar_parent_class)->constructed (object);
272 
273 	/* Disable animation of the revealer, until GtkInfoBar's bug #710888 is fixed */
274 	revealer = gtk_widget_get_template_child (GTK_WIDGET (object), GTK_TYPE_INFO_BAR, "revealer");
275 	if (revealer) {
276 		gtk_revealer_set_transition_type (GTK_REVEALER (revealer), GTK_REVEALER_TRANSITION_TYPE_NONE);
277 		gtk_revealer_set_transition_duration (GTK_REVEALER (revealer), 0);
278 	}
279 }
280 
281 static void
activity_bar_dispose(GObject * object)282 activity_bar_dispose (GObject *object)
283 {
284 	EActivityBarPrivate *priv;
285 
286 	priv = E_ACTIVITY_BAR_GET_PRIVATE (object);
287 
288 	activity_bar_unset_timeout_id (E_ACTIVITY_BAR (object));
289 
290 	if (priv->activity != NULL) {
291 		g_signal_handlers_disconnect_matched (
292 			priv->activity, G_SIGNAL_MATCH_DATA,
293 			0, 0, NULL, NULL, object);
294 		g_object_weak_unref (
295 			G_OBJECT (priv->activity), (GWeakNotify)
296 			activity_bar_weak_notify_cb, object);
297 		priv->activity = NULL;
298 	}
299 
300 	/* Chain up to parent's dispose() method. */
301 	G_OBJECT_CLASS (e_activity_bar_parent_class)->dispose (object);
302 }
303 
304 static void
e_activity_bar_class_init(EActivityBarClass * class)305 e_activity_bar_class_init (EActivityBarClass *class)
306 {
307 	GObjectClass *object_class;
308 
309 	g_type_class_add_private (class, sizeof (EActivityBarPrivate));
310 
311 	object_class = G_OBJECT_CLASS (class);
312 	object_class->set_property = activity_bar_set_property;
313 	object_class->get_property = activity_bar_get_property;
314 	object_class->constructed = activity_bar_constructed;
315 	object_class->dispose = activity_bar_dispose;
316 
317 	g_object_class_install_property (
318 		object_class,
319 		PROP_ACTIVITY,
320 		g_param_spec_object (
321 			"activity",
322 			NULL,
323 			NULL,
324 			E_TYPE_ACTIVITY,
325 			G_PARAM_READWRITE |
326 			G_PARAM_CONSTRUCT));
327 }
328 
329 static void
e_activity_bar_init(EActivityBar * bar)330 e_activity_bar_init (EActivityBar *bar)
331 {
332 	GtkWidget *container;
333 	GtkWidget *widget;
334 
335 	bar->priv = E_ACTIVITY_BAR_GET_PRIVATE (bar);
336 
337 	container = gtk_info_bar_get_content_area (GTK_INFO_BAR (bar));
338 
339 	widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
340 	gtk_container_add (GTK_CONTAINER (container), widget);
341 	gtk_widget_show (widget);
342 
343 	container = widget;
344 
345 	widget = gtk_image_new ();
346 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
347 	bar->priv->image = widget;
348 
349 	widget = e_spinner_new ();
350 	e_spinner_start (E_SPINNER (widget));
351 	gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
352 	bar->priv->spinner = widget;
353 
354 	/* The spinner is only visible when the image is not. */
355 	e_binding_bind_property (
356 		bar->priv->image, "visible",
357 		bar->priv->spinner, "visible",
358 		G_BINDING_BIDIRECTIONAL |
359 		G_BINDING_SYNC_CREATE |
360 		G_BINDING_INVERT_BOOLEAN);
361 
362 	widget = gtk_label_new (NULL);
363 	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.5);
364 	gtk_label_set_ellipsize (GTK_LABEL (widget), PANGO_ELLIPSIZE_END);
365 	gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
366 	bar->priv->label = widget;
367 	gtk_widget_show (widget);
368 
369 	/* This is only shown if the EActivity has a GCancellable. */
370 	widget = e_dialog_button_new_with_icon ("process-stop", _("_Cancel"));
371 	gtk_info_bar_add_action_widget (
372 		GTK_INFO_BAR (bar), widget, GTK_RESPONSE_CANCEL);
373 	bar->priv->cancel = widget;
374 	gtk_widget_hide (widget);
375 
376 	g_signal_connect_swapped (
377 		widget, "clicked",
378 		G_CALLBACK (activity_bar_cancel), bar);
379 }
380 
381 GtkWidget *
e_activity_bar_new(void)382 e_activity_bar_new (void)
383 {
384 	return g_object_new (E_TYPE_ACTIVITY_BAR, NULL);
385 }
386 
387 EActivity *
e_activity_bar_get_activity(EActivityBar * bar)388 e_activity_bar_get_activity (EActivityBar *bar)
389 {
390 	g_return_val_if_fail (E_IS_ACTIVITY_BAR (bar), NULL);
391 
392 	return bar->priv->activity;
393 }
394 
395 void
e_activity_bar_set_activity(EActivityBar * bar,EActivity * activity)396 e_activity_bar_set_activity (EActivityBar *bar,
397                              EActivity *activity)
398 {
399 	g_return_if_fail (E_IS_ACTIVITY_BAR (bar));
400 
401 	if (activity != NULL)
402 		g_return_if_fail (E_IS_ACTIVITY (activity));
403 
404 	activity_bar_unset_timeout_id (bar);
405 
406 	if (bar->priv->activity != NULL) {
407 		g_signal_handlers_disconnect_matched (
408 			bar->priv->activity, G_SIGNAL_MATCH_DATA,
409 			0, 0, NULL, NULL, bar);
410 		g_object_weak_unref (
411 			G_OBJECT (bar->priv->activity),
412 			(GWeakNotify) activity_bar_weak_notify_cb, bar);
413 	}
414 
415 	bar->priv->activity = activity;
416 
417 	if (activity != NULL) {
418 		g_object_weak_ref (
419 			G_OBJECT (activity), (GWeakNotify)
420 			activity_bar_weak_notify_cb, bar);
421 
422 		g_signal_connect_swapped (
423 			activity, "notify::state",
424 			G_CALLBACK (activity_bar_feedback), bar);
425 
426 		g_signal_connect_swapped (
427 			activity, "notify",
428 			G_CALLBACK (activity_bar_update), bar);
429 	}
430 
431 	activity_bar_update (bar);
432 
433 	g_object_notify (G_OBJECT (bar), "activity");
434 }
435