1 /*
2  * Seahorse
3  *
4  * Copyright (C) 2004 Stefan Walter
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "seahorse-progress.h"
20 #include "seahorse-widget.h"
21 
22 /* -----------------------------------------------------------------------------
23  *  GENERIC PROGRESS BAR HANDLING
24  */
25 
26 /**
27  * SECTION:seahorse-progress
28  * @short_description: Progress bar handling
29  *
30  **/
31 
32 static void     disconnect_progress     (SeahorseWidget *widget, SeahorseOperation *op);
33 
34 /**
35 * progress: The progress bar to pulse
36 *
37 * Called to pulse the progress bar when we're in pulse mode
38 *
39 * Returns TRUE if the bar pulsed
40 **/
41 static gboolean
pulse_timer(GtkProgressBar * progress)42 pulse_timer (GtkProgressBar *progress)
43 {
44     g_assert (GTK_IS_PROGRESS_BAR (progress));
45 
46     if (gtk_progress_bar_get_pulse_step (progress) != 0) {
47         gtk_progress_bar_pulse (progress);
48          return TRUE;
49     }
50 
51     return FALSE;
52 }
53 
54 /**
55 * progress: The progress bar to init
56 *
57 * Inits the progress bar and sets a timer function.
58 *
59 **/
60 static void
start_pulse(GtkProgressBar * progress)61 start_pulse (GtkProgressBar *progress)
62 {
63     guint stag;
64 
65     gtk_progress_bar_set_pulse_step (progress, 0.05);
66     gtk_progress_bar_pulse (progress);
67 
68     stag = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (progress), "pulse-timer"));
69     if (stag == 0) {
70         /* Start a pulse timer */
71         stag = g_timeout_add (100, (GSourceFunc)pulse_timer, progress);
72         g_object_set_data_full (G_OBJECT (progress), "pulse-timer",
73                                 GINT_TO_POINTER (stag), (GDestroyNotify)g_source_remove);
74     }
75 }
76 
77 /**
78 * progress: The progress bar
79 *
80 * Stops progress bar pulsing
81 *
82 **/
83 static void
stop_pulse(GtkProgressBar * progress)84 stop_pulse (GtkProgressBar *progress)
85 {
86     gtk_progress_bar_set_pulse_step (progress, 0.0);
87 
88     /* This causes the destroy callback to be called */
89     g_object_set_data (G_OBJECT (progress), "pulse-timer", NULL);
90 }
91 
92 
93 /**
94 * operation: The operation to process
95 * message: The message that will be pushed to the status bar
96 * fract: The fraction of the progress bar to fill
97 * swidget: The SeahorseWidget to extract the gtk widgets from
98 *
99 * This gets called whenever an operation updates it's progress status thingy.
100 * We update the appbar as appropriate. If operation != NULL then we only
101 * display the latest operation in our special list
102 *
103 **/
104 static void
operation_progress(SeahorseOperation * operation,const gchar * message,gdouble fract,SeahorseWidget * swidget)105 operation_progress (SeahorseOperation *operation, const gchar *message,
106                     gdouble fract, SeahorseWidget *swidget)
107 {
108     GtkProgressBar *progress;
109     GtkStatusbar *status;
110     guint id;
111 
112     progress = GTK_PROGRESS_BAR (seahorse_widget_get_widget (swidget, "progress"));
113     status = GTK_STATUSBAR (seahorse_widget_get_widget (swidget, "status"));
114 
115     if (!seahorse_operation_is_running (operation))
116         fract = 0.0;
117 
118     if (message != NULL && status) {
119         g_return_if_fail (GTK_IS_STATUSBAR (status));
120         id = gtk_statusbar_get_context_id (status, "operation-progress");
121         gtk_statusbar_pop (status, id);
122         if (message[0])
123             gtk_statusbar_push (status, id, message);
124     }
125 
126     if(progress) {
127         g_return_if_fail (GTK_IS_PROGRESS_BAR (progress));
128         if (fract >= 0.0) {
129             stop_pulse (progress);
130             gtk_progress_bar_set_fraction (progress, fract);
131         } else {
132             start_pulse (progress);
133         }
134     }
135 }
136 
137 /**
138 * operation: The finished operation
139 * swidget: The SeahorseWidget to get the progress bar from
140 *
141 * Handles the progress bar display of finished operations.
142 * If there is an error in the operation, it will be displayed.
143 *
144 **/
145 static void
operation_done(SeahorseOperation * operation,SeahorseWidget * swidget)146 operation_done (SeahorseOperation *operation, SeahorseWidget *swidget)
147 {
148     GError *err = NULL;
149 
150     if (!seahorse_operation_is_successful (operation)) {
151         seahorse_operation_copy_error (operation, &err);
152         if (err) {
153             operation_progress (operation, err->message, 0.0, swidget);
154             g_error_free (err);
155         }
156     } else {
157 	    operation_progress (operation, "", 0.0, swidget);
158     }
159 
160     g_signal_handlers_disconnect_by_func (swidget, disconnect_progress, operation);
161     g_object_set_data (G_OBJECT (swidget), "operation", NULL);
162 }
163 
164 /**
165 * widget: SeahorseWidget that is used for display
166 * op: SeahorseOperation to disconnect
167 *
168 * Disconnect the operation_progress and the operation_done functions from the
169 * operation
170 *
171 **/
172 static void
disconnect_progress(SeahorseWidget * widget,SeahorseOperation * op)173 disconnect_progress (SeahorseWidget *widget, SeahorseOperation *op)
174 {
175     g_signal_handlers_disconnect_by_func (op, operation_progress, widget);
176     g_signal_handlers_disconnect_by_func (op, operation_done, widget);
177 }
178 
179 /**
180  * seahorse_progress_status_set_operation:
181  * @swidget: The SeahorseWidget to add the operation to
182  * @operation: The operation to add
183  *
184  * Adds the operation to the widget.
185  *
186  */
187 void
seahorse_progress_status_set_operation(SeahorseWidget * swidget,SeahorseOperation * operation)188 seahorse_progress_status_set_operation (SeahorseWidget *swidget,
189                                         SeahorseOperation *operation)
190 {
191     SeahorseOperation *prev;
192 
193     /*
194      * Note that this is not one off, the operation is monitored until it is
195      * replaced, so if the operation starts up again the progress will be
196      * displayed
197      */
198 
199     g_return_if_fail (SEAHORSE_IS_WIDGET (swidget));
200     g_return_if_fail (SEAHORSE_IS_OPERATION (operation));
201 
202     prev = SEAHORSE_OPERATION (g_object_get_data (G_OBJECT (swidget), "operation"));
203     if (prev) {
204 
205         /* If it's the same operation, just ignore */
206         if (prev == operation)
207             return;
208 
209         /* If the previous one was a multi operation, just piggy back this one in */
210         if (SEAHORSE_IS_MULTI_OPERATION (prev)) {
211        	    g_object_ref (operation);
212             seahorse_multi_operation_take (SEAHORSE_MULTI_OPERATION (prev), operation);
213             return;
214         }
215 
216         /* Otherwise disconnect old progress, replace with new */
217         disconnect_progress (swidget, prev);
218     }
219 
220     g_object_ref (operation);
221     g_object_set_data_full (G_OBJECT (swidget), "operation", operation,
222                             (GDestroyNotify)g_object_unref);
223     g_signal_connect (swidget, "destroy",
224                       G_CALLBACK (disconnect_progress), operation);
225 
226     if (!seahorse_operation_is_running (operation))
227         operation_done (operation, swidget);
228 
229     operation_progress (operation, seahorse_operation_get_message (operation),
230                         seahorse_operation_get_progress (operation), swidget);
231 
232     g_signal_connect (operation, "done", G_CALLBACK (operation_done), swidget);
233     g_signal_connect (operation, "progress", G_CALLBACK (operation_progress), swidget);
234 }
235 
236 /**
237  * seahorse_progress_status_get_operation:
238  * @swidget: The SeahorseWidget to extract the operation from
239  *
240  *
241  *
242  * Returns: The operation stored in the widget
243  */
244 SeahorseOperation*
seahorse_progress_status_get_operation(SeahorseWidget * swidget)245 seahorse_progress_status_get_operation (SeahorseWidget *swidget)
246 {
247     return SEAHORSE_OPERATION (g_object_get_data (G_OBJECT (swidget), "operation"));
248 }
249 
250 /* -----------------------------------------------------------------------------
251  *  PROGRESS WINDOWS
252  */
253 
254 /**
255 * operation: The SeahorseOperation to use
256 * message: An optional message to display
257 * fract: The fraction finished
258 * swidget: the SeahorseWidget to get the widgets from
259 *
260 * Progress window update. Similar to operation_progress
261 *
262 **/
263 static void
progress_operation_update(SeahorseOperation * operation,const gchar * message,gdouble fract,SeahorseWidget * swidget)264 progress_operation_update (SeahorseOperation *operation, const gchar *message,
265                            gdouble fract, SeahorseWidget *swidget)
266 {
267     GtkProgressBar *progress;
268     GtkWidget *w;
269     const gchar *t;
270 
271     w = GTK_WIDGET (seahorse_widget_get_widget (swidget, "operation-details"));
272     g_return_if_fail (w != NULL);
273 
274     t = seahorse_operation_get_message (operation);
275     gtk_label_set_text (GTK_LABEL (w), t ? t : "");
276 
277     progress = GTK_PROGRESS_BAR (seahorse_widget_get_widget (swidget, "operation-bar"));
278     g_return_if_fail (w != NULL);
279 
280     if (fract >= 0.0) {
281         stop_pulse (progress);
282         gtk_progress_bar_set_fraction (progress, fract);
283     } else {
284         start_pulse (progress);
285     }
286 }
287 
288 /**
289  * on_progress_operation_cancel:
290  * @button: ignored
291  * @operation: The operation to cancel
292  *
293  * Cancels an operation
294  *
295  */
296 G_MODULE_EXPORT void
on_progress_operation_cancel(GtkButton * button,SeahorseOperation * operation)297 on_progress_operation_cancel (GtkButton *button, SeahorseOperation *operation)
298 {
299     if (seahorse_operation_is_running (operation))
300         seahorse_operation_cancel (operation);
301 }
302 
303 /**
304 * operation: ignored
305 * swidget: The SeahorseWidget to destroy
306 *
307 * Cleans up after the operation is done
308 *
309 **/
310 static void
progress_operation_done(SeahorseOperation * operation,SeahorseWidget * swidget)311 progress_operation_done (SeahorseOperation *operation, SeahorseWidget *swidget)
312 {
313     seahorse_widget_destroy (swidget);
314 }
315 
316 /**
317 * widget:
318 * event: ignored
319 * operation: The operation
320 *
321 * Closing the windows cancels the operation
322 *
323 * Returns TRUE
324 **/
325 static int
progress_delete_event(GtkWidget * widget,GdkEvent * event,SeahorseOperation * operation)326 progress_delete_event (GtkWidget *widget, GdkEvent *event,
327                        SeahorseOperation *operation)
328 {
329     /* When window close we simulate a cancel */
330     on_progress_operation_cancel (NULL, operation);
331 
332     /* Allow window to close regardless of outcome */
333     return TRUE;
334 }
335 
336 /**
337 * swidget: The SeahorseWidget relevant
338 * operation: The operation to disconnect handlers from
339 *
340 * Disconnects the handlers from the functions
341 *
342 **/
343 static void
progress_destroy(SeahorseWidget * swidget,SeahorseOperation * operation)344 progress_destroy (SeahorseWidget *swidget, SeahorseOperation *operation)
345 {
346     /*
347      * Since we allow the window to close (user forces it) even when the
348      * operation is not complete, we have to take care to cleanup these
349      * signal handlers.
350      */
351     g_signal_handlers_disconnect_by_func (operation, operation_done, swidget);
352     g_signal_handlers_disconnect_by_func (operation, operation_progress, swidget);
353 }
354 
355 /**
356 * operation: The operation to create a new progress window for
357 *
358 * Creates a new progress window and adds the operation to it.
359 *
360 * Returns FALSE
361 **/
362 static gboolean
progress_show(SeahorseOperation * operation)363 progress_show (SeahorseOperation *operation)
364 {
365     SeahorseWidget *swidget;
366     GtkWidget *w;
367     const gchar *title;
368     gchar *t;
369 
370     if (!seahorse_operation_is_running (operation)) {
371         /* Matches the ref in seahorse_progress_show */
372         g_object_unref (operation);
373         return FALSE;
374     }
375 
376     swidget = seahorse_widget_new_allow_multiple ("progress", NULL);
377     g_return_val_if_fail (swidget != NULL, FALSE);
378 
379     /* Release our reference on the operation when this window is destroyed */
380     g_object_set_data_full (G_OBJECT (swidget), "operation", operation,
381                             (GDestroyNotify)g_object_unref);
382 
383     w = GTK_WIDGET (seahorse_widget_get_widget (swidget, swidget->name));
384     gtk_window_move (GTK_WINDOW (w), 10, 10);
385 
386     /* Setup the title */
387     title = (const gchar*)g_object_get_data (G_OBJECT (operation), "progress-title");
388     if (title) {
389 
390         /* The window title */
391         w = GTK_WIDGET (seahorse_widget_get_widget (swidget, swidget->name));
392         g_return_val_if_fail (w != NULL, FALSE);
393         gtk_window_set_title (GTK_WINDOW (w), title);
394 
395         /* The main message title */
396         w = GTK_WIDGET (seahorse_widget_get_widget (swidget, "operation-title"));
397         g_return_val_if_fail (w != NULL, FALSE);
398         t = g_strdup_printf ("<b>%s</b>", title);
399         gtk_label_set_markup (GTK_LABEL (w), t);
400         g_free (t);
401     }
402 
403     /* The details */
404     progress_operation_update (operation, NULL,
405                                 seahorse_operation_get_progress (operation), swidget);
406     g_signal_connect (operation, "progress",
407                       G_CALLBACK (progress_operation_update), swidget);
408 
409     /* Cancel events */
410     g_signal_connect (seahorse_widget_get_toplevel (swidget), "delete_event",
411                       G_CALLBACK (progress_delete_event), operation);
412 
413     /* Done and cleanup */
414     w = GTK_WIDGET (seahorse_widget_get_widget (swidget, swidget->name));
415     g_signal_connect (w, "destroy", G_CALLBACK (progress_destroy), operation);
416     g_signal_connect (operation, "done", G_CALLBACK (progress_operation_done), swidget);
417 
418     return FALSE;
419 }
420 
421 /**
422  * seahorse_progress_show:
423  * @operation: The operation to create a progress window for
424  * @title: Optional title of this window
425  * @delayed: TRUE: wait 1 second before displaying the window
426  *
427  * Displays a progress window and adds an operation to it.
428  *
429  */
430 void
seahorse_progress_show(SeahorseOperation * operation,const gchar * title,gboolean delayed)431 seahorse_progress_show (SeahorseOperation *operation, const gchar *title,
432                         gboolean delayed)
433 {
434     /* Unref in the timeout callback */
435     g_object_ref (operation);
436     g_object_set_data_full (G_OBJECT (operation), "progress-title",
437                 title ? g_strdup (title) : NULL, (GDestroyNotify)g_free);
438 
439     /* Show the progress, after one second */
440     if (delayed)
441         g_timeout_add_seconds (1, (GSourceFunc)progress_show, operation);
442 
443     /* Right away */
444     else
445         progress_show (operation);
446 }
447