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