]> git.0d.be Git - empathy.git/blob - src/empathy-ft-manager.c
Updated Polish translation
[empathy.git] / src / empathy-ft-manager.c
1 /*
2  * Copyright (C) 2003, 2004 Xan Lopez
3  * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
4  * Copyright (C) 2008-2009 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (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.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  USA
20  *
21  * Authors: Xan Lopez
22  *          Marco Barisione <marco@barisione.org>
23  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
24  *          Xavier Claessens <xclaesse@gmail.com>
25  *          Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
26  */
27
28 /* The original file transfer manager code was copied from Epiphany */
29
30 #include "config.h"
31
32 #include <string.h>
33
34 #include <glib/gi18n.h>
35 #include <gtk/gtk.h>
36 #include <gdk/gdkkeysyms.h>
37
38 #define DEBUG_FLAG EMPATHY_DEBUG_FT
39 #include <libempathy/empathy-debug.h>
40 #include <libempathy/empathy-tp-file.h>
41 #include <libempathy/empathy-utils.h>
42
43 #include <libempathy-gtk/empathy-conf.h>
44 #include <libempathy-gtk/empathy-ui-utils.h>
45 #include <libempathy-gtk/empathy-geometry.h>
46 #include <libempathy-gtk/empathy-images.h>
47
48 #include "empathy-ft-manager.h"
49
50 enum
51 {
52   COL_PERCENT,
53   COL_ICON,
54   COL_MESSAGE,
55   COL_REMAINING,
56   COL_FT_OBJECT
57 };
58
59 typedef struct {
60   GtkTreeModel *model;
61   GHashTable *ft_handler_to_row_ref;
62
63   /* Widgets */
64   GtkWidget *window;
65   GtkWidget *treeview;
66   GtkWidget *open_button;
67   GtkWidget *abort_button;
68   GtkWidget *clear_button;
69 } EmpathyFTManagerPriv;
70
71 enum
72 {
73   RESPONSE_OPEN  = 1,
74   RESPONSE_STOP  = 2,
75   RESPONSE_CLEAR = 3
76 };
77
78 G_DEFINE_TYPE (EmpathyFTManager, empathy_ft_manager, G_TYPE_OBJECT);
79
80 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyFTManager)
81
82 static EmpathyFTManager *manager_singleton = NULL;
83
84 static void ft_handler_hashing_started_cb (EmpathyFTHandler *handler,
85     EmpathyFTManager *manager);
86
87 static gchar *
88 ft_manager_format_interval (guint interval)
89 {
90   gint hours, mins, secs;
91
92   hours = interval / 3600;
93   interval -= hours * 3600;
94   mins = interval / 60;
95   interval -= mins * 60;
96   secs = interval;
97
98   if (hours > 0)
99     /* Translators: time left, when it is more than one hour */
100     return g_strdup_printf (_("%u:%02u.%02u"), hours, mins, secs);
101   else
102     /* Translators: time left, when is is less than one hour */
103     return g_strdup_printf (_("%02u.%02u"), mins, secs);
104 }
105
106 static void
107 ft_manager_update_buttons (EmpathyFTManager *manager)
108 {
109   GtkTreeSelection *selection;
110   GtkTreeModel *model;
111   GtkTreeIter iter;
112   EmpathyFTHandler *handler;
113   gboolean open_enabled = FALSE;
114   gboolean abort_enabled = FALSE;
115   gboolean clear_enabled = FALSE;
116   gboolean is_completed, is_cancelled;
117   GHashTableIter hash_iter;
118   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
119
120   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
121
122   if (gtk_tree_selection_get_selected (selection, &model, &iter))
123     {
124       gtk_tree_model_get (model, &iter, COL_FT_OBJECT, &handler, -1);
125
126       is_completed = empathy_ft_handler_is_completed (handler);
127       is_cancelled = empathy_ft_handler_is_cancelled (handler);
128
129       /* I can open the file if the transfer is completed and was incoming */
130       open_enabled = (is_completed && empathy_ft_handler_is_incoming (handler));
131
132       /* I can abort if the transfer is not already finished */
133       abort_enabled = (is_cancelled == FALSE && is_completed == FALSE);
134
135       g_object_unref (handler);
136     }
137
138   g_hash_table_iter_init (&hash_iter, priv->ft_handler_to_row_ref);
139
140   while (g_hash_table_iter_next (&hash_iter, (gpointer *) &handler, NULL))
141     {
142       if (empathy_ft_handler_is_completed (handler) ||
143           empathy_ft_handler_is_cancelled (handler))
144           clear_enabled = TRUE;
145
146       if (clear_enabled)
147         break;
148     }
149
150   gtk_widget_set_sensitive (priv->open_button, open_enabled);
151   gtk_widget_set_sensitive (priv->abort_button, abort_enabled);
152
153   if (clear_enabled)
154     gtk_widget_set_sensitive (priv->clear_button, TRUE);
155 }
156
157 static void
158 ft_manager_selection_changed (GtkTreeSelection *selection,
159                               EmpathyFTManager *manager)
160 {
161   ft_manager_update_buttons (manager);
162 }
163
164 static void
165 ft_manager_progress_cell_data_func (GtkTreeViewColumn *col,
166                                     GtkCellRenderer *renderer,
167                                     GtkTreeModel *model,
168                                     GtkTreeIter *iter,
169                                     gpointer user_data)
170 {
171   const gchar *text = NULL;
172   gint percent;
173
174   gtk_tree_model_get (model, iter, COL_PERCENT, &percent, -1);
175
176   if (percent < 0)
177     {
178       percent = 0;
179       text = C_("file transfer percent", "Unknown");
180     }
181
182   g_object_set (renderer, "text", text, "value", percent, NULL);
183 }
184
185 static GtkTreeRowReference *
186 ft_manager_get_row_from_handler (EmpathyFTManager *manager,
187                                  EmpathyFTHandler *handler)
188 {
189   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
190
191   return g_hash_table_lookup (priv->ft_handler_to_row_ref, handler);
192 }
193
194 static void
195 ft_manager_remove_file_from_model (EmpathyFTManager *manager,
196                                    EmpathyFTHandler *handler)
197 {
198   GtkTreeRowReference *row_ref;
199   GtkTreeSelection *selection;
200   GtkTreePath *path = NULL;
201   GtkTreeIter iter;
202   gboolean update_selection;
203   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
204
205   row_ref = ft_manager_get_row_from_handler (manager, handler);
206   g_return_if_fail (row_ref);
207
208   DEBUG ("Removing file transfer from window: contact=%s, filename=%s",
209       empathy_contact_get_name (empathy_ft_handler_get_contact (handler)),
210       empathy_ft_handler_get_filename (handler));
211
212   /* Get the iter from the row_ref */
213   path = gtk_tree_row_reference_get_path (row_ref);
214   gtk_tree_model_get_iter (priv->model, &iter, path);
215   gtk_tree_path_free (path);
216
217   /* We have to update the selection only if we are removing the selected row */
218   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
219   update_selection = gtk_tree_selection_iter_is_selected (selection, &iter);
220
221   /* Remove tp_file's row. After that iter points to the next row */
222   if (!gtk_list_store_remove (GTK_LIST_STORE (priv->model), &iter))
223     {
224       gint n_row;
225
226       /* There is no next row, set iter to the last row */
227       n_row = gtk_tree_model_iter_n_children (priv->model, NULL);
228       if (n_row > 0)
229         gtk_tree_model_iter_nth_child (priv->model, &iter, NULL, n_row - 1);
230       else
231         update_selection = FALSE;
232     }
233
234   if (update_selection)
235     gtk_tree_selection_select_iter (selection, &iter);
236 }
237
238 static gboolean
239 remove_finished_transfer_foreach (gpointer key,
240                                   gpointer value,
241                                   gpointer user_data)
242 {
243   EmpathyFTHandler *handler = key;
244   EmpathyFTManager *manager = user_data;
245
246   if (empathy_ft_handler_is_completed (handler) ||
247       empathy_ft_handler_is_cancelled (handler))
248     {
249       ft_manager_remove_file_from_model (manager, handler);
250       return TRUE;
251     }
252
253   return FALSE;
254 }
255
256 static gchar *
257 ft_manager_format_progress_bytes_and_percentage (guint64 current,
258                                                  guint64 total,
259                                                  gdouble speed,
260                                                  int *percentage)
261 {
262   char *total_str, *current_str, *retval;
263   char *speed_str = NULL;
264
265   total_str = g_format_size_for_display (total);
266   current_str = g_format_size_for_display (current);
267
268   if (speed > 0)
269     speed_str = g_format_size_for_display ((goffset) speed);
270
271   /* translators: first %s is the currently processed size, second %s is
272    * the total file size */
273   retval = speed_str ?
274     g_strdup_printf (_("%s of %s at %s/s"), current_str, total_str, speed_str) :
275     g_strdup_printf (_("%s of %s"), current_str, total_str);
276
277   g_free (total_str);
278   g_free (current_str);
279   g_free (speed_str);
280
281   if (percentage != NULL)
282     {
283       if (total != 0)
284         *percentage = current * 100 / total;
285       else
286         *percentage = -1;
287     }
288
289   return retval;
290 }
291
292 static gchar *
293 ft_manager_format_contact_info (EmpathyFTHandler *handler)
294 {
295   gboolean incoming;
296   const char *filename, *contact_name, *first_line_format;
297   char *retval;
298
299   incoming = empathy_ft_handler_is_incoming (handler);
300   contact_name = empathy_contact_get_name
301     (empathy_ft_handler_get_contact (handler));
302   filename = empathy_ft_handler_get_filename (handler);
303
304   if (incoming)
305     /* translators: first %s is filename, second %s is the contact name */
306     first_line_format = _("Receiving \"%s\" from %s");
307   else
308     /* translators: first %s is filename, second %s is the contact name */
309     first_line_format = _("Sending \"%s\" to %s");
310
311   retval = g_strdup_printf (first_line_format, filename, contact_name);
312
313   return retval;
314 }
315
316 static gchar *
317 ft_manager_format_error_message (EmpathyFTHandler *handler,
318                                  const GError *error)
319 {
320   const char *contact_name, *filename;
321   EmpathyContact *contact;
322   char *first_line, *message;
323   gboolean incoming;
324
325   contact_name = NULL;
326   incoming = empathy_ft_handler_is_incoming (handler);
327
328   contact = empathy_ft_handler_get_contact (handler);
329   if (contact)
330     contact_name = empathy_contact_get_name (contact);
331
332   filename = empathy_ft_handler_get_filename (handler);
333
334   if (incoming)
335     /* filename/contact_name here are either both NULL or both valid */
336     if (filename && contact_name)
337       /* translators: first %s is filename, second %s
338        * is the contact name */
339       first_line = g_strdup_printf (_("Error receiving \"%s\" from %s"), filename,
340           contact_name);
341     else
342       first_line = g_strdup (_("Error receiving a file"));
343   else
344     /* translators: first %s is filename, second %s
345      * is the contact name */
346     if (filename && contact_name)
347       first_line = g_strdup_printf (_("Error sending \"%s\" to %s"), filename,
348           contact_name);
349     else
350       first_line = g_strdup (_("Error sending a file"));
351
352   message = g_strdup_printf ("%s\n%s", first_line, error->message);
353
354   g_free (first_line);
355
356   return message;
357 }
358
359 static void
360 ft_manager_update_handler_message (EmpathyFTManager *manager,
361                                    GtkTreeRowReference *row_ref,
362                                    const char *message)
363 {
364   GtkTreePath *path;
365   GtkTreeIter iter;
366   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
367
368   /* Set new value in the store */
369   path = gtk_tree_row_reference_get_path (row_ref);
370   gtk_tree_model_get_iter (priv->model, &iter, path);
371   gtk_list_store_set (GTK_LIST_STORE (priv->model),
372       &iter,
373       COL_MESSAGE, message ? message : "",
374       -1);
375
376   gtk_tree_path_free (path);
377 }
378
379 static void
380 ft_manager_update_handler_progress (EmpathyFTManager *manager,
381                                     GtkTreeRowReference *row_ref,
382                                     int percentage)
383 {
384   GtkTreePath *path;
385   GtkTreeIter iter;
386   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
387
388   /* Set new value in the store */
389   path = gtk_tree_row_reference_get_path (row_ref);
390   gtk_tree_model_get_iter (priv->model, &iter, path);
391   gtk_list_store_set (GTK_LIST_STORE (priv->model),
392       &iter,
393       COL_PERCENT, percentage,
394       -1);
395
396   gtk_tree_path_free (path);
397
398 }
399
400 static void
401 ft_manager_update_handler_time (EmpathyFTManager *manager,
402                                 GtkTreeRowReference *row_ref,
403                                 guint remaining_time)
404 {
405   GtkTreePath *path;
406   GtkTreeIter iter;
407   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
408   char *remaining_str;
409
410   remaining_str = ft_manager_format_interval (remaining_time);
411
412   /* Set new value in the store */
413   path = gtk_tree_row_reference_get_path (row_ref);
414   gtk_tree_model_get_iter (priv->model, &iter, path);
415   gtk_list_store_set (GTK_LIST_STORE (priv->model),
416       &iter,
417       COL_REMAINING, remaining_str,
418       -1);
419
420   gtk_tree_path_free (path);
421   g_free (remaining_str);
422 }
423
424 static void
425 ft_manager_clear_handler_time (EmpathyFTManager *manager,
426                                GtkTreeRowReference *row_ref)
427 {
428   GtkTreePath *path;
429   GtkTreeIter iter;
430   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
431
432   /* Set new value in the store */
433   path = gtk_tree_row_reference_get_path (row_ref);
434   gtk_tree_model_get_iter (priv->model, &iter, path);
435   gtk_list_store_set (GTK_LIST_STORE (priv->model),
436       &iter,
437       COL_REMAINING, NULL,
438       -1);
439
440   gtk_tree_path_free (path);
441 }
442
443 static void
444 ft_handler_transfer_error_cb (EmpathyFTHandler *handler,
445                               GError *error,
446                               EmpathyFTManager *manager)
447 {
448   char *message;
449   GtkTreeRowReference *row_ref;
450
451   DEBUG ("Transfer error %s", error->message);
452
453   row_ref = ft_manager_get_row_from_handler (manager, handler);
454   g_return_if_fail (row_ref != NULL);
455
456   message = ft_manager_format_error_message (handler, error);
457
458   ft_manager_update_handler_message (manager, row_ref, message);
459   ft_manager_clear_handler_time (manager, row_ref);
460   ft_manager_update_buttons (manager);
461
462   g_free (message);
463 }
464
465 static void
466 do_real_transfer_done (EmpathyFTManager *manager,
467                        EmpathyFTHandler *handler)
468 {
469   const char *contact_name;
470   const char *filename;
471   char *first_line, *second_line, *message;
472   char *uri;
473   gboolean incoming;
474   GtkTreeRowReference *row_ref;
475   GtkRecentManager *recent_manager;
476   GFile *file;
477
478   row_ref = ft_manager_get_row_from_handler (manager, handler);
479   g_return_if_fail (row_ref != NULL);
480
481   incoming = empathy_ft_handler_is_incoming (handler);
482   contact_name = empathy_contact_get_name
483     (empathy_ft_handler_get_contact (handler));
484   filename = empathy_ft_handler_get_filename (handler);
485
486   if (incoming)
487     /* translators: first %s is filename, second %s
488      * is the contact name */
489     first_line = g_strdup_printf (_("\"%s\" received from %s"), filename,
490         contact_name);
491   else
492     /* translators: first %s is filename, second %s
493      * is the contact name */
494     first_line = g_strdup_printf (_("\"%s\" sent to %s"), filename,
495         contact_name);
496
497   second_line = g_strdup (_("File transfer completed"));
498
499   message = g_strdup_printf ("%s\n%s", first_line, second_line);
500   ft_manager_update_handler_message (manager, row_ref, message);
501   ft_manager_clear_handler_time (manager, row_ref);
502
503   /* update buttons */
504   ft_manager_update_buttons (manager);
505
506   g_free (message);
507   g_free (first_line);
508   g_free (second_line);
509
510   recent_manager = gtk_recent_manager_get_default ();
511   file = empathy_ft_handler_get_gfile (handler);
512   uri = g_file_get_uri (file);
513
514   gtk_recent_manager_add_item (recent_manager, uri);
515
516   g_free (uri);
517 }
518
519 static void
520 ft_handler_transfer_done_cb (EmpathyFTHandler *handler,
521                              EmpathyTpFile *tp_file,
522                              EmpathyFTManager *manager)
523 {
524   if (empathy_ft_handler_is_incoming (handler) &&
525       empathy_ft_handler_get_use_hash (handler))
526     {
527       DEBUG ("Transfer done, waiting for hashing-started");
528
529       /* connect to the signal and return early */
530       g_signal_connect (handler, "hashing-started",
531           G_CALLBACK (ft_handler_hashing_started_cb), manager);
532
533       return;
534     }
535
536   DEBUG ("Transfer done, no hashing");
537
538   do_real_transfer_done (manager, handler);
539 }
540
541 static void
542 ft_handler_transfer_progress_cb (EmpathyFTHandler *handler,
543                                  guint64 current_bytes,
544                                  guint64 total_bytes,
545                                  guint remaining_time,
546                                  gdouble speed,
547                                  EmpathyFTManager *manager)
548 {
549   char *first_line, *second_line, *message;
550   int percentage;
551   GtkTreeRowReference *row_ref;
552
553   DEBUG ("Transfer progress");
554
555   row_ref = ft_manager_get_row_from_handler (manager, handler);
556   g_return_if_fail (row_ref != NULL);
557
558   first_line = ft_manager_format_contact_info (handler);
559   second_line = ft_manager_format_progress_bytes_and_percentage
560     (current_bytes, total_bytes, speed, &percentage);
561
562   message = g_strdup_printf ("%s\n%s", first_line, second_line);
563
564   ft_manager_update_handler_message (manager, row_ref, message);
565   ft_manager_update_handler_progress (manager, row_ref, percentage);
566
567   if (remaining_time > 0)
568     ft_manager_update_handler_time (manager, row_ref, remaining_time);
569
570   g_free (message);
571   g_free (first_line);
572   g_free (second_line);
573 }
574
575 static void
576 ft_handler_transfer_started_cb (EmpathyFTHandler *handler,
577                                 EmpathyTpFile *tp_file,
578                                 EmpathyFTManager *manager)
579 {
580   guint64 transferred_bytes, total_bytes;
581
582   DEBUG ("Transfer started");
583
584   g_signal_connect (handler, "transfer-progress",
585       G_CALLBACK (ft_handler_transfer_progress_cb), manager);
586   g_signal_connect (handler, "transfer-done",
587       G_CALLBACK (ft_handler_transfer_done_cb), manager);
588
589   transferred_bytes = empathy_ft_handler_get_transferred_bytes (handler);
590   total_bytes = empathy_ft_handler_get_total_bytes (handler);
591
592   ft_handler_transfer_progress_cb (handler, transferred_bytes, total_bytes,
593       0, -1, manager);
594 }
595
596 static void
597 ft_handler_hashing_done_cb (EmpathyFTHandler *handler,
598                             EmpathyFTManager *manager)
599 {
600   GtkTreeRowReference *row_ref;
601   char *first_line, *second_line, *message;
602
603   DEBUG ("Hashing done");
604
605   /* update the message */
606   if (empathy_ft_handler_is_incoming (handler))
607     {
608       do_real_transfer_done (manager, handler);
609       return;
610     }
611
612   row_ref = ft_manager_get_row_from_handler (manager, handler);
613   g_return_if_fail (row_ref != NULL);
614
615   first_line = ft_manager_format_contact_info (handler);
616   second_line = g_strdup (_("Waiting for the other participant's response"));
617   message = g_strdup_printf ("%s\n%s", first_line, second_line);
618
619   ft_manager_update_handler_message (manager, row_ref, message);
620
621   g_free (message);
622   g_free (first_line);
623   g_free (second_line);
624
625   g_signal_connect (handler, "transfer-started",
626       G_CALLBACK (ft_handler_transfer_started_cb), manager);
627 }
628
629 static void
630 ft_handler_hashing_progress_cb (EmpathyFTHandler *handler,
631                                 guint64 current_bytes,
632                                 guint64 total_bytes,
633                                 EmpathyFTManager *manager)
634 {
635   char *first_line, *second_line, *message;
636   GtkTreeRowReference *row_ref;
637
638   row_ref = ft_manager_get_row_from_handler (manager, handler);
639   g_return_if_fail (row_ref != NULL);
640
641   if (empathy_ft_handler_is_incoming (handler))
642       first_line = g_strdup_printf (_("Checking integrity of \"%s\""),
643           empathy_ft_handler_get_filename (handler));
644   else
645       first_line =  g_strdup_printf (_("Hashing \"%s\""),
646           empathy_ft_handler_get_filename (handler));
647
648   second_line = ft_manager_format_progress_bytes_and_percentage
649     (current_bytes, total_bytes, -1, NULL);
650
651   message = g_strdup_printf ("%s\n%s", first_line, second_line);
652
653   ft_manager_update_handler_message (manager, row_ref, message);
654
655   g_free (message);
656   g_free (first_line);
657   g_free (second_line);
658 }
659
660 static void
661 ft_handler_hashing_started_cb (EmpathyFTHandler *handler,
662                                EmpathyFTManager *manager)
663 {
664   char *message, *first_line, *second_line;
665   GtkTreeRowReference *row_ref;
666
667   DEBUG ("Hashing started");
668
669   g_signal_connect (handler, "hashing-progress",
670      G_CALLBACK (ft_handler_hashing_progress_cb), manager);
671   g_signal_connect (handler, "hashing-done",
672      G_CALLBACK (ft_handler_hashing_done_cb), manager);
673
674   row_ref = ft_manager_get_row_from_handler (manager, handler);
675   g_return_if_fail (row_ref != NULL);
676
677   first_line = ft_manager_format_contact_info (handler);
678
679   if (empathy_ft_handler_is_incoming (handler))
680       second_line = g_strdup_printf (_("Checking integrity of \"%s\""),
681           empathy_ft_handler_get_filename (handler));
682   else
683       second_line = g_strdup_printf (_("Hashing \"%s\""),
684           empathy_ft_handler_get_filename (handler));
685
686   message = g_strdup_printf ("%s\n%s", first_line, second_line);
687
688   ft_manager_update_handler_message (manager, row_ref, message);
689
690   g_free (first_line);
691   g_free (second_line);
692   g_free (message);
693 }
694
695 static void
696 ft_manager_start_transfer (EmpathyFTManager *manager,
697                            EmpathyFTHandler *handler)
698 {
699   EmpathyFTManagerPriv *priv;
700   gboolean is_outgoing;
701
702   priv = GET_PRIV (manager);
703
704   is_outgoing = !empathy_ft_handler_is_incoming (handler);
705
706   DEBUG ("Start transfer, is outgoing %s",
707       is_outgoing ? "True" : "False");
708
709   /* now connect the signals */
710   g_signal_connect (handler, "transfer-error",
711       G_CALLBACK (ft_handler_transfer_error_cb), manager);
712
713   if (is_outgoing && empathy_ft_handler_get_use_hash (handler)) {
714     g_signal_connect (handler, "hashing-started",
715         G_CALLBACK (ft_handler_hashing_started_cb), manager);
716   } else {
717     /* either incoming or outgoing without hash */
718     g_signal_connect (handler, "transfer-started",
719         G_CALLBACK (ft_handler_transfer_started_cb), manager);
720   }
721
722   empathy_ft_handler_start_transfer (handler);
723 }
724
725 static void
726 ft_manager_add_handler_to_list (EmpathyFTManager *manager,
727                                 EmpathyFTHandler *handler,
728                                 const GError *error)
729 {
730   GtkTreeRowReference *row_ref;
731   GtkTreeIter iter;
732   GtkTreeSelection *selection;
733   GtkTreePath *path;
734   GIcon *icon;
735   const char *content_type, *second_line;
736   char *first_line, *message;
737   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
738
739   icon = NULL;
740
741   /* get the icon name from the mime-type of the file. */
742   content_type = empathy_ft_handler_get_content_type (handler);
743
744   if (content_type != NULL)
745     icon = g_content_type_get_icon (content_type);
746
747   /* append the handler in the store */
748   gtk_list_store_insert_with_values (GTK_LIST_STORE (priv->model),
749       &iter, G_MAXINT, COL_FT_OBJECT, handler,
750       COL_ICON, icon, -1);
751
752   if (icon != NULL)
753     g_object_unref (icon);
754
755   /* insert the new row_ref in the hash table  */
756   path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->model), &iter);
757   row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (priv->model), path);
758   gtk_tree_path_free (path);
759   g_hash_table_insert (priv->ft_handler_to_row_ref, g_object_ref (handler),
760       row_ref);
761
762   /* select the new row */
763   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
764   gtk_tree_selection_select_iter (selection, &iter);
765
766   if (error != NULL)
767     {
768       message = ft_manager_format_error_message (handler, error);
769       ft_manager_update_handler_message (manager, row_ref, message);
770
771       g_free (message);
772       return;
773     }
774
775   /* update the row with the initial values.
776    * the only case where we postpone this is in case we're managing
777    * an outgoing+hashing transfer, as the hashing started signal will
778    * take care of updating the information.
779    */
780   if (empathy_ft_handler_is_incoming (handler) ||
781       !empathy_ft_handler_get_use_hash (handler)) {
782     first_line = ft_manager_format_contact_info (handler);
783     second_line = _("Waiting for the other participant's response");
784     message = g_strdup_printf ("%s\n%s", first_line, second_line);
785
786     ft_manager_update_handler_message (manager, row_ref, message);
787
788     g_free (first_line);
789     g_free (message);
790   }
791
792   /* hook up the signals and start the transfer */
793   ft_manager_start_transfer (manager, handler);
794 }
795
796 static void
797 ft_manager_clear (EmpathyFTManager *manager)
798 {
799   EmpathyFTManagerPriv *priv;
800
801   DEBUG ("Clearing file transfer list");
802
803   priv = GET_PRIV (manager);
804
805   /* Remove completed and cancelled transfers */
806   g_hash_table_foreach_remove (priv->ft_handler_to_row_ref,
807       remove_finished_transfer_foreach, manager);
808
809   /* set the clear button back to insensitive */
810   gtk_widget_set_sensitive (priv->clear_button, FALSE);
811 }
812
813 static void
814 ft_manager_open (EmpathyFTManager *manager)
815 {
816   GtkTreeSelection *selection;
817   GtkTreeIter iter;
818   GtkTreeModel *model;
819   EmpathyFTHandler *handler;
820   char *uri;
821   GFile *file;
822   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
823
824   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
825
826   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
827     return;
828
829   gtk_tree_model_get (model, &iter, COL_FT_OBJECT, &handler, -1);
830
831   file = empathy_ft_handler_get_gfile (handler);
832   uri = g_file_get_uri (file);
833
834   DEBUG ("Opening URI: %s", uri);
835   empathy_url_show (GTK_WIDGET (priv->window), uri);
836
837   g_object_unref (handler);
838   g_free (uri);
839 }
840
841 static void
842 ft_manager_stop (EmpathyFTManager *manager)
843 {
844   GtkTreeSelection *selection;
845   GtkTreeIter iter;
846   GtkTreeModel *model;
847   EmpathyFTHandler *handler;
848   EmpathyFTManagerPriv *priv;
849
850   priv = GET_PRIV (manager);
851
852   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
853
854   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
855     return;
856
857   gtk_tree_model_get (model, &iter, COL_FT_OBJECT, &handler, -1);
858   g_return_if_fail (handler != NULL);
859
860   DEBUG ("Stopping file transfer: contact=%s, filename=%s",
861       empathy_contact_get_name (empathy_ft_handler_get_contact (handler)),
862       empathy_ft_handler_get_filename (handler));
863
864   empathy_ft_handler_cancel_transfer (handler);
865
866   g_object_unref (handler);
867 }
868
869 static void
870 ft_manager_response_cb (GtkWidget *widget,
871                         gint response,
872                         EmpathyFTManager *manager)
873 {
874   switch (response)
875     {
876       case RESPONSE_CLEAR:
877         ft_manager_clear (manager);
878         break;
879       case RESPONSE_OPEN:
880         ft_manager_open (manager);
881         break;
882       case RESPONSE_STOP:
883         ft_manager_stop (manager);
884         break;
885     }
886 }
887
888 static gboolean
889 ft_manager_delete_event_cb (GtkWidget *widget,
890                             GdkEvent *event,
891                             EmpathyFTManager *manager)
892 {
893   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
894
895   DEBUG ("%p", manager);
896
897   /* remove all the completed/cancelled/errored transfers */
898   ft_manager_clear (manager);
899
900   if (g_hash_table_size (priv->ft_handler_to_row_ref) > 0)
901     {
902       /* There is still FTs on flight, just hide the window */
903       DEBUG ("Hiding window");
904       gtk_widget_hide (widget);
905       return TRUE;
906     }
907
908   return FALSE;
909 }
910
911 static void
912 ft_manager_destroy_cb (GtkWidget *widget,
913                        EmpathyFTManager *manager)
914 {
915   DEBUG ("%p", manager);
916
917   g_object_unref (manager);
918 }
919
920 static gboolean
921 ft_manager_key_press_event_cb (GtkWidget *widget,
922                                GdkEventKey *event,
923                                gpointer user_data)
924 {
925   if ((event->state & GDK_CONTROL_MASK && event->keyval == GDK_w)
926       || event->keyval == GDK_Escape)
927     {
928       gtk_widget_destroy (widget);
929       return TRUE;
930     }
931
932   return FALSE;
933 }
934
935 static void
936 ft_manager_build_ui (EmpathyFTManager *manager)
937 {
938   GtkBuilder *gui;
939   GtkTreeView *view;
940   GtkListStore *liststore;
941   GtkTreeViewColumn *column;
942   GtkCellRenderer *renderer;
943   GtkTreeSelection *selection;
944   gchar *filename;
945   EmpathyFTManagerPriv *priv = GET_PRIV (manager);
946
947   filename = empathy_file_lookup ("empathy-ft-manager.ui", "src");
948   gui = empathy_builder_get_file (filename,
949       "ft_manager_dialog", &priv->window,
950       "ft_list", &priv->treeview,
951       "clear_button", &priv->clear_button,
952       "open_button", &priv->open_button,
953       "abort_button", &priv->abort_button,
954       NULL);
955   g_free (filename);
956
957   empathy_builder_connect (gui, manager,
958       "ft_manager_dialog", "destroy", ft_manager_destroy_cb,
959       "ft_manager_dialog", "response", ft_manager_response_cb,
960       "ft_manager_dialog", "delete-event", ft_manager_delete_event_cb,
961       "ft_manager_dialog", "key-press-event", ft_manager_key_press_event_cb,
962       NULL);
963
964   empathy_builder_unref_and_keep_widget (gui, priv->window);
965
966   /* Window geometry. */
967   empathy_geometry_bind (GTK_WINDOW (priv->window), "ft-manager");
968
969   /* Setup the tree view */
970   view = GTK_TREE_VIEW (priv->treeview);
971   selection = gtk_tree_view_get_selection (view);
972   gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
973   g_signal_connect (selection, "changed",
974       G_CALLBACK (ft_manager_selection_changed), manager);
975   gtk_tree_view_set_headers_visible (view, TRUE);
976   gtk_tree_view_set_enable_search (view, FALSE);
977
978   /* Setup the model */
979   liststore = gtk_list_store_new (5,
980       G_TYPE_INT,     /* percent */
981       G_TYPE_ICON,    /* icon */
982       G_TYPE_STRING,  /* message */
983       G_TYPE_STRING,  /* remaining */
984       G_TYPE_OBJECT); /* ft_handler */
985   gtk_tree_view_set_model (view, GTK_TREE_MODEL (liststore));
986   priv->model = GTK_TREE_MODEL (liststore);
987   g_object_unref (liststore);
988
989   /* Progress column */
990   column = gtk_tree_view_column_new ();
991   gtk_tree_view_column_set_title (column, _("%"));
992   gtk_tree_view_column_set_sort_column_id (column, COL_PERCENT);
993   gtk_tree_view_insert_column (view, column, -1);
994
995   renderer = gtk_cell_renderer_progress_new ();
996   g_object_set (renderer, "xalign", 0.5, NULL);
997   gtk_tree_view_column_pack_start (column, renderer, FALSE);
998   gtk_tree_view_column_set_cell_data_func (column, renderer,
999       ft_manager_progress_cell_data_func, NULL, NULL);
1000
1001   /* Icon and filename column*/
1002   column = gtk_tree_view_column_new ();
1003   gtk_tree_view_column_set_title (column, _("File"));
1004   gtk_tree_view_column_set_expand (column, TRUE);
1005   gtk_tree_view_column_set_resizable (column, TRUE);
1006   gtk_tree_view_column_set_sort_column_id (column, COL_MESSAGE);
1007   gtk_tree_view_column_set_spacing (column, 3);
1008   gtk_tree_view_insert_column (view, column, -1);
1009
1010   renderer = gtk_cell_renderer_pixbuf_new ();
1011   g_object_set (renderer, "xpad", 3,
1012       "stock-size", GTK_ICON_SIZE_DND, NULL);
1013   gtk_tree_view_column_pack_start (column, renderer, FALSE);
1014   gtk_tree_view_column_set_attributes (column, renderer,
1015       "gicon", COL_ICON, NULL);
1016
1017   renderer = gtk_cell_renderer_text_new ();
1018   g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1019   gtk_tree_view_column_pack_start (column, renderer, TRUE);
1020   gtk_tree_view_column_set_attributes (column, renderer,
1021       "text", COL_MESSAGE, NULL);
1022
1023   /* Remaining time column */
1024   column = gtk_tree_view_column_new ();
1025   gtk_tree_view_column_set_title (column, _("Remaining"));
1026   gtk_tree_view_column_set_sort_column_id (column, COL_REMAINING);
1027   gtk_tree_view_insert_column (view, column, -1);
1028
1029   renderer = gtk_cell_renderer_text_new ();
1030   g_object_set (renderer, "xalign", 0.5, NULL);
1031   gtk_tree_view_column_pack_start (column, renderer, FALSE);
1032   gtk_tree_view_column_set_attributes (column, renderer,
1033       "text", COL_REMAINING, NULL);
1034
1035   /* clear button should be sensitive only if there are completed/cancelled
1036    * handlers in the store.
1037    */
1038   gtk_widget_set_sensitive (priv->clear_button, FALSE);
1039 }
1040
1041 /* GObject method overrides */
1042
1043 static void
1044 empathy_ft_manager_finalize (GObject *object)
1045 {
1046   EmpathyFTManagerPriv *priv = GET_PRIV (object);
1047
1048   DEBUG ("FT Manager %p", object);
1049
1050   g_hash_table_destroy (priv->ft_handler_to_row_ref);
1051
1052   G_OBJECT_CLASS (empathy_ft_manager_parent_class)->finalize (object);
1053 }
1054
1055 static void
1056 empathy_ft_manager_init (EmpathyFTManager *manager)
1057 {
1058   EmpathyFTManagerPriv *priv;
1059
1060   priv = G_TYPE_INSTANCE_GET_PRIVATE ((manager), EMPATHY_TYPE_FT_MANAGER,
1061       EmpathyFTManagerPriv);
1062
1063   manager->priv = priv;
1064
1065   priv->ft_handler_to_row_ref = g_hash_table_new_full (g_direct_hash,
1066       g_direct_equal, (GDestroyNotify) g_object_unref,
1067       (GDestroyNotify) gtk_tree_row_reference_free);
1068
1069   ft_manager_build_ui (manager);
1070 }
1071
1072 static GObject *
1073 empathy_ft_manager_constructor (GType type,
1074                                 guint n_props,
1075                                 GObjectConstructParam *props)
1076 {
1077   GObject *retval;
1078
1079   if (manager_singleton)
1080     {
1081       retval = G_OBJECT (manager_singleton);
1082     }
1083   else
1084     {
1085       retval = G_OBJECT_CLASS (empathy_ft_manager_parent_class)->constructor
1086           (type, n_props, props);
1087
1088       manager_singleton = EMPATHY_FT_MANAGER (retval);
1089       g_object_add_weak_pointer (retval, (gpointer) &manager_singleton);
1090     }
1091
1092   return retval;
1093 }
1094
1095 static void
1096 empathy_ft_manager_class_init (EmpathyFTManagerClass *klass)
1097 {
1098   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1099
1100   object_class->finalize = empathy_ft_manager_finalize;
1101   object_class->constructor = empathy_ft_manager_constructor;
1102
1103   g_type_class_add_private (object_class, sizeof (EmpathyFTManagerPriv));
1104 }
1105
1106 /* public methods */
1107
1108 void
1109 empathy_ft_manager_add_handler (EmpathyFTHandler *handler)
1110 {
1111   EmpathyFTManager *manager;
1112   EmpathyFTManagerPriv *priv;
1113
1114   DEBUG ("Adding handler");
1115
1116   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1117
1118   manager = g_object_new (EMPATHY_TYPE_FT_MANAGER, NULL);
1119   priv = GET_PRIV (manager);
1120
1121   ft_manager_add_handler_to_list (manager, handler, NULL);
1122   gtk_window_present (GTK_WINDOW (priv->window));
1123 }
1124
1125 void
1126 empathy_ft_manager_display_error (EmpathyFTHandler *handler,
1127                                   const GError *error)
1128 {
1129   EmpathyFTManager *manager;
1130   EmpathyFTManagerPriv *priv;
1131
1132   g_return_if_fail (EMPATHY_IS_FT_HANDLER (handler));
1133   g_return_if_fail (error != NULL);
1134
1135   manager = g_object_new (EMPATHY_TYPE_FT_MANAGER, NULL);
1136   priv = GET_PRIV (manager);
1137
1138   ft_manager_add_handler_to_list (manager, handler, error);
1139   gtk_window_present (GTK_WINDOW (priv->window));
1140 }
1141
1142 void
1143 empathy_ft_manager_show (void)
1144 {
1145   EmpathyFTManager *manager;
1146   EmpathyFTManagerPriv *priv;
1147
1148   manager = g_object_new (EMPATHY_TYPE_FT_MANAGER, NULL);
1149   priv = GET_PRIV (manager);
1150
1151   gtk_window_present (GTK_WINDOW (priv->window));
1152 }