]> git.0d.be Git - empathy.git/blob - src/empathy-ft-manager.c
Move the ft manager to src/ other programs do not need it.
[empathy.git] / src / empathy-ft-manager.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2003, 2004 Xan Lopez
4  * Copyright (C) 2007 Marco Barisione <marco@barisione.org>
5  * Copyright (C) 2008 Collabora Ltd.
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: Xan Lopez
23  *          Marco Barisione <marco@barisione.org>
24  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
25  */
26
27 /* The original file transfer manager code was copied from Epiphany */
28
29 #include "config.h"
30
31 #include <string.h>
32
33 #include <glib/gi18n.h>
34 #include <gtk/gtk.h>
35 #include <libgnomevfs/gnome-vfs.h>
36 #include <libgnomeui/libgnomeui.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 /**
51  * SECTION:empathy-ft-manager
52  * @short_description: File transfer dialog
53  * @see_also: #EmpathyTpFile, empathy_dispatcher_send_file()
54  * @include: libempthy-gtk/empathy-ft-manager.h
55  *
56  * The #EmpathyFTManager object represents the file transfer dialog,
57  * it can show multiple file transfers at the same time (added
58  * with empathy_ft_manager_add_tp_file()).
59  */
60
61 enum
62 {
63   COL_PERCENT,
64   COL_ICON,
65   COL_MESSAGE,
66   COL_REMAINING,
67   COL_FT_OBJECT
68 };
69
70 enum
71 {
72   PROGRESS_COL_POS,
73   FILE_COL_POS,
74   REMAINING_COL_POS
75 };
76
77 /**
78  * EmpathyFTManagerPriv:
79  *
80  * Private fields of the #EmpathyFTManager class.
81  */
82 struct _EmpathyFTManagerPriv
83 {
84   GtkTreeModel *model;
85   GHashTable *tp_file_to_row_ref;
86
87   /* Widgets */
88   GtkWidget *window;
89   GtkWidget *treeview;
90   GtkWidget *open_button;
91   GtkWidget *abort_button;
92
93   guint save_geometry_id;
94 };
95
96 enum
97 {
98   RESPONSE_OPEN  = 1,
99   RESPONSE_STOP  = 2,
100   RESPONSE_CLEAR = 3
101 };
102
103 G_DEFINE_TYPE (EmpathyFTManager, empathy_ft_manager, G_TYPE_OBJECT);
104
105 static EmpathyFTManager *manager_p = NULL;
106
107 /**
108  * empathy_ft_manager_get_default:
109  *
110  * Returns a new #EmpathyFTManager if there is not already one, or the existing
111  * one if it exists.
112  *
113  * Returns: a #EmpathyFTManager
114  */
115 EmpathyFTManager *
116 empathy_ft_manager_get_default (void)
117 {
118   if (!manager_p)
119     manager_p = g_object_new (EMPATHY_TYPE_FT_MANAGER, NULL);
120
121   return manager_p;
122 }
123
124 /**
125  * empathy_ft_manager_get_dialog:
126  * @ft_manager: an #EmpathyFTManager
127  *
128  * Returns the #GtkWidget of @ft_manager.
129  *
130  * Returns: the dialog
131  */
132 GtkWidget *
133 empathy_ft_manager_get_dialog (EmpathyFTManager *ft_manager)
134 {
135   g_return_val_if_fail (EMPATHY_IS_FT_MANAGER (ft_manager), NULL);
136
137   return ft_manager->priv->window;
138 }
139
140 static gchar *
141 ft_manager_format_interval (gint interval)
142 {
143   gint hours, mins, secs;
144
145   hours = interval / 3600;
146   interval -= hours * 3600;
147   mins = interval / 60;
148   interval -= mins * 60;
149   secs = interval;
150
151   if (hours > 0)
152     return g_strdup_printf (_("%u:%02u.%02u"), hours, mins, secs);
153   else
154     return g_strdup_printf (_("%02u.%02u"), mins, secs);
155 }
156
157 static GtkTreeRowReference *
158 ft_manager_get_row_from_tp_file (EmpathyFTManager *ft_manager,
159                                  EmpathyTpFile *tp_file)
160 {
161   return g_hash_table_lookup (ft_manager->priv->tp_file_to_row_ref, tp_file);
162 }
163
164 static void
165 ft_manager_update_buttons (EmpathyFTManager *ft_manager)
166 {
167   GtkTreeSelection *selection;
168   GtkTreeModel *model;
169   GtkTreeIter iter;
170   EmpathyTpFile *tp_file;
171   gboolean open_enabled = FALSE;
172   gboolean abort_enabled = FALSE;
173
174   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (
175       ft_manager->priv->treeview));
176   if (gtk_tree_selection_get_selected (selection, &model, &iter))
177     {
178       gtk_tree_model_get (model, &iter, COL_FT_OBJECT, &tp_file, -1);
179
180       if (empathy_tp_file_get_state (tp_file)
181           == EMP_FILE_TRANSFER_STATE_COMPLETED)
182         {
183           if (empathy_tp_file_is_incoming (tp_file))
184             open_enabled = TRUE;
185           else
186             open_enabled = FALSE;
187
188           abort_enabled = FALSE;
189
190         }
191       else if (empathy_tp_file_get_state (tp_file) ==
192         EMP_FILE_TRANSFER_STATE_CANCELLED)
193         {
194           open_enabled = FALSE;
195           abort_enabled = FALSE;
196         }
197       else
198         {
199           open_enabled = FALSE;
200           abort_enabled = TRUE;
201         }
202     }
203
204   gtk_widget_set_sensitive (ft_manager->priv->open_button, open_enabled);
205   gtk_widget_set_sensitive (ft_manager->priv->abort_button, abort_enabled);
206 }
207
208 static const gchar *
209 ft_manager_state_change_reason_to_string (EmpFileTransferStateChangeReason reason)
210 {
211   switch (reason)
212     {
213       case EMP_FILE_TRANSFER_STATE_CHANGE_REASON_NONE:
214         return _("File transfer not completed");
215       case EMP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_STOPPED:
216         return _("You canceled the file transfer");
217       case EMP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_STOPPED:
218         return _("The other participant canceled the file transfer");
219       case EMP_FILE_TRANSFER_STATE_CHANGE_REASON_LOCAL_ERROR:
220         return _("Error while trying to transfer the file");
221       case EMP_FILE_TRANSFER_STATE_CHANGE_REASON_REMOTE_ERROR:
222         return _("The other participant is unable to transfer the file");
223       default:
224         return _("Unknown reason");
225     }
226 }
227
228 static void
229 ft_manager_update_ft_row (EmpathyFTManager *ft_manager,
230                           EmpathyTpFile *tp_file)
231 {
232   GtkTreeRowReference  *row_ref;
233   GtkTreePath *path;
234   GtkTreeIter iter;
235   const gchar *filename;
236   const gchar *contact_name;
237   gchar *msg;
238   gchar *remaining_str;
239   gchar *first_line_format;
240   gchar *first_line;
241   gchar *second_line;
242   guint64 transferred_bytes;
243   guint64 total_size;
244   gint remaining = -1;
245   gint percent;
246   EmpFileTransferState state;
247   EmpFileTransferStateChangeReason reason;
248   gboolean incoming;
249
250   row_ref = ft_manager_get_row_from_tp_file (ft_manager, tp_file);
251   g_return_if_fail (row_ref != NULL);
252
253   filename = empathy_tp_file_get_filename (tp_file);
254   contact_name = empathy_contact_get_name (empathy_tp_file_get_contact (tp_file));
255   transferred_bytes = empathy_tp_file_get_transferred_bytes (tp_file);
256   total_size = empathy_tp_file_get_size (tp_file);
257   state = empathy_tp_file_get_state (tp_file);
258   reason = empathy_tp_file_get_state_change_reason (tp_file);
259   incoming = empathy_tp_file_is_incoming (tp_file);
260
261   switch (state)
262     {
263       case EMP_FILE_TRANSFER_STATE_PENDING:
264       case EMP_FILE_TRANSFER_STATE_OPEN:
265       case EMP_FILE_TRANSFER_STATE_ACCEPTED:
266         if (incoming)
267           /* translators: first %s is filename, second %s is the contact name */
268           first_line_format = _("Receiving \"%s\" from %s");
269         else
270           /* translators: first %s is filename, second %s is the contact name */
271           first_line_format = _("Sending \"%s\" to %s");
272
273         first_line = g_strdup_printf (first_line_format, filename, contact_name);
274
275         if (state == EMP_FILE_TRANSFER_STATE_OPEN
276             || (incoming && state == EMP_FILE_TRANSFER_STATE_ACCEPTED)
277             || (incoming && state == EMP_FILE_TRANSFER_STATE_PENDING))
278           {
279             gchar *total_size_str;
280             gchar *transferred_bytes_str;
281
282             if (total_size == EMPATHY_TP_FILE_UNKNOWN_SIZE)
283               /* translators: the text before the "|" is context to
284                * help you decide on the correct translation. You MUST
285                * OMIT it in the translated string. */
286               total_size_str = g_strdup (Q_("file size|Unknown"));
287             else
288               total_size_str = g_format_size_for_display (total_size);
289
290             transferred_bytes_str = g_format_size_for_display (transferred_bytes);
291
292             /* translators: first %s is the transferred size, second %s is
293              * the total file size */
294             second_line = g_strdup_printf (_("%s of %s"), transferred_bytes_str,
295                 total_size_str);
296             g_free (transferred_bytes_str);
297             g_free (total_size_str);
298
299           }
300         else
301           second_line = g_strdup (_("Waiting the other participant's response"));
302
303       remaining = empathy_tp_file_get_remaining_time (tp_file);
304       break;
305
306     case EMP_FILE_TRANSFER_STATE_COMPLETED:
307       if (empathy_tp_file_is_incoming (tp_file))
308         /* translators: first %s is filename, second %s
309          * is the contact name */
310         first_line = g_strdup_printf (
311             _("\"%s\" received from %s"), filename,
312             contact_name);
313       else
314         /* translators: first %s is filename, second %s
315          * is the contact name */
316         first_line = g_strdup_printf (
317             _("\"%s\" sent to %s"), filename,
318             contact_name);
319
320       second_line = g_strdup ("File transfer completed");
321
322       break;
323
324     case EMP_FILE_TRANSFER_STATE_CANCELLED:
325       if (empathy_tp_file_is_incoming (tp_file))
326         /* translators: first %s is filename, second %s
327          * is the contact name */
328         first_line = g_strdup_printf (
329             _("\"%s\" receiving from %s"), filename,
330             contact_name);
331       else
332         /* translators: first %s is filename, second %s
333          * is the contact name */
334         first_line = g_strdup_printf (
335             _("\"%s\" sending to %s"), filename,
336             contact_name);
337
338       second_line = g_strdup_printf (_("File transfer canceled: %s"),
339           ft_manager_state_change_reason_to_string (reason));
340
341       break;
342
343     default:
344       g_return_if_reached ();
345
346     }
347
348   if (total_size != EMPATHY_TP_FILE_UNKNOWN_SIZE)
349     percent = transferred_bytes * 100 / total_size;
350   else
351     percent = -1;
352
353   if (remaining < 0)
354     {
355       if (state == EMP_FILE_TRANSFER_STATE_COMPLETED ||
356           state == EMP_FILE_TRANSFER_STATE_CANCELLED)
357         remaining_str = g_strdup ("");
358       else
359         /* translators: the text before the "|" is context to
360          * help you decide on the correct translation. You
361          * MUST OMIT it in the translated string. */
362         remaining_str = g_strdup (Q_("remaining time|Unknown"));
363     }
364   else
365     remaining_str = ft_manager_format_interval (remaining);
366
367   msg = g_strdup_printf ("%s\n%s", first_line, second_line);
368
369   path = gtk_tree_row_reference_get_path (row_ref);
370   gtk_tree_model_get_iter (ft_manager->priv->model, &iter, path);
371   gtk_list_store_set (GTK_LIST_STORE (ft_manager->priv->model),
372       &iter,
373       COL_PERCENT, percent,
374       COL_MESSAGE, msg,
375       COL_REMAINING, remaining_str,
376       -1);
377
378   gtk_tree_path_free (path);
379
380   g_free (msg);
381   g_free (first_line);
382   g_free (second_line);
383   g_free (remaining_str);
384
385   ft_manager_update_buttons (ft_manager);
386 }
387
388 static void
389 ft_manager_transferred_bytes_changed_cb (EmpathyTpFile *tp_file,
390                                          GParamSpec *pspec,
391                                          EmpathyFTManager *ft_manager)
392 {
393   ft_manager_update_ft_row (ft_manager, tp_file);
394 }
395
396 static void
397 ft_manager_selection_changed (GtkTreeSelection *selection,
398                               EmpathyFTManager *ft_manager)
399 {
400   ft_manager_update_buttons (ft_manager);
401 }
402
403 static void
404 ft_manager_progress_cell_data_func (GtkTreeViewColumn *col,
405                                     GtkCellRenderer *renderer,
406                                     GtkTreeModel *model,
407                                     GtkTreeIter *iter,
408                                     gpointer user_data)
409 {
410   const gchar *text = NULL;
411   gint percent;
412
413   gtk_tree_model_get (model, iter, COL_PERCENT, &percent, -1);
414
415   if (percent < 0)
416     {
417       percent = 0;
418       /* Translators: The text before the "|" is context to help you
419        * decide on the correct translation. You MUST OMIT it in the
420        * translated string. */
421       text = Q_("file transfer percent|Unknown");
422     }
423
424   g_object_set (renderer, "text", text, "value", percent, NULL);
425 }
426
427 static gboolean
428 ft_manager_save_geometry_timeout_cb (EmpathyFTManager *ft_manager)
429 {
430   gint x, y, w, h;
431
432   gtk_window_get_size (GTK_WINDOW (ft_manager->priv->window), &w, &h);
433   gtk_window_get_position (GTK_WINDOW (ft_manager->priv->window), &x, &y);
434
435   empathy_geometry_save ("ft-manager", x, y, w, h);
436
437   ft_manager->priv->save_geometry_id = 0;
438
439   return FALSE;
440 }
441
442 static gboolean
443 ft_manager_configure_event_cb (GtkWidget *widget,
444                                GdkEventConfigure *event,
445                                EmpathyFTManager *ft_manager)
446 {
447   if (ft_manager->priv->save_geometry_id != 0)
448     g_source_remove (ft_manager->priv->save_geometry_id);
449
450   ft_manager->priv->save_geometry_id = g_timeout_add (500,
451       (GSourceFunc) ft_manager_save_geometry_timeout_cb, ft_manager);
452
453   return FALSE;
454 }
455
456 static void
457 ft_manager_remove_file_from_list (EmpathyFTManager *ft_manager,
458                                   EmpathyTpFile *tp_file)
459 {
460   GtkTreeRowReference *row_ref;
461   GtkTreePath *path = NULL;
462   GtkTreeIter iter, iter2;
463
464   row_ref = ft_manager_get_row_from_tp_file (ft_manager, tp_file);
465   g_return_if_fail (row_ref);
466
467   DEBUG ("Removing file transfer from window: contact=%s, filename=%s",
468       empathy_contact_get_name (empathy_tp_file_get_contact (tp_file)),
469       empathy_tp_file_get_filename (tp_file));
470
471   /* Get the row we'll select after removal ("smart" selection) */
472
473   path = gtk_tree_row_reference_get_path (row_ref);
474   gtk_tree_model_get_iter (GTK_TREE_MODEL (ft_manager->priv->model),
475       &iter, path);
476   gtk_tree_path_free (path);
477
478   row_ref = NULL;
479   iter2 = iter;
480   if (gtk_tree_model_iter_next (GTK_TREE_MODEL (ft_manager->priv->model), &iter))
481     {
482       path = gtk_tree_model_get_path  (GTK_TREE_MODEL (ft_manager->priv->model), &iter);
483       row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (ft_manager->priv->model), path);
484     }
485   else
486     {
487       path = gtk_tree_model_get_path (GTK_TREE_MODEL (ft_manager->priv->model), &iter2);
488       if (gtk_tree_path_prev (path))
489         {
490           row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (ft_manager->priv->model),
491               path);
492         }
493     }
494   gtk_tree_path_free (path);
495
496   /* Removal */
497
498   gtk_list_store_remove (GTK_LIST_STORE (ft_manager->priv->model), &iter2);
499   g_object_unref (tp_file);
500
501   /* Actual selection */
502
503   if (row_ref != NULL)
504     {
505       path = gtk_tree_row_reference_get_path (row_ref);
506       if (path != NULL)
507         {
508           gtk_tree_view_set_cursor (GTK_TREE_VIEW (ft_manager->priv->treeview),
509               path, NULL, FALSE);
510           gtk_tree_path_free (path);
511         }
512       gtk_tree_row_reference_free (row_ref);
513     }
514
515 }
516
517 static gboolean
518 remove_finished_transfer_foreach (gpointer key,
519                                   gpointer value,
520                                   gpointer user_data)
521 {
522   EmpathyTpFile *tp_file = EMPATHY_TP_FILE (key);
523   EmpathyFTManager *self = EMPATHY_FT_MANAGER (user_data);
524   EmpFileTransferState state;
525
526   state = empathy_tp_file_get_state (tp_file);
527   if (state == EMP_FILE_TRANSFER_STATE_COMPLETED ||
528       state == EMP_FILE_TRANSFER_STATE_CANCELLED)
529     {
530       ft_manager_remove_file_from_list (self, tp_file);
531       return TRUE;
532     }
533
534   return FALSE;
535 }
536
537 static void
538 ft_manager_clear (EmpathyFTManager *ft_manager)
539 {
540   DEBUG ("Clearing file transfer list");
541
542   /* Remove completed and cancelled transfers */
543   g_hash_table_foreach_remove (ft_manager->priv->tp_file_to_row_ref,
544       remove_finished_transfer_foreach, ft_manager);
545 }
546
547 static void
548 ft_manager_state_changed_cb (EmpathyTpFile *tp_file,
549                              GParamSpec *pspec,
550                              EmpathyFTManager *ft_manager)
551 {
552   gboolean remove;
553
554   switch (empathy_tp_file_get_state (tp_file))
555     {
556       case EMP_FILE_TRANSFER_STATE_COMPLETED:
557         if (empathy_tp_file_is_incoming (tp_file))
558           {
559             GtkRecentManager *manager;
560             const gchar *uri;
561
562             manager = gtk_recent_manager_get_default ();
563             uri = g_object_get_data (G_OBJECT (tp_file), "uri");
564             gtk_recent_manager_add_item (manager, uri);
565          }
566
567       case EMP_FILE_TRANSFER_STATE_CANCELLED:
568         /* Automatically remove file transfers if the
569          * window if not visible. */
570         /* FIXME how do the user know if the file transfer
571          * failed? */
572         remove = !GTK_WIDGET_VISIBLE (ft_manager->priv->window);
573         break;
574
575       default:
576         remove = FALSE;
577         break;
578     }
579
580   if (remove)
581     {
582       ft_manager_remove_file_from_list (ft_manager, tp_file);
583       g_hash_table_remove (ft_manager->priv->tp_file_to_row_ref, tp_file);
584     }
585   else
586     {
587       ft_manager_update_ft_row (ft_manager, tp_file);
588     }
589 }
590
591 static void
592 ft_manager_add_tp_file_to_list (EmpathyFTManager *ft_manager,
593                                 EmpathyTpFile *tp_file)
594 {
595   GtkTreeRowReference  *row_ref;
596   GtkTreeIter iter;
597   GtkTreeSelection *selection;
598   GtkTreePath *path;
599   GtkIconTheme *theme;
600   gchar *icon_name;
601   gchar *content_type;
602
603   gtk_list_store_insert_with_values (GTK_LIST_STORE (ft_manager->priv->model),
604       &iter, G_MAXINT, COL_FT_OBJECT, tp_file, -1);
605
606   path =  gtk_tree_model_get_path (GTK_TREE_MODEL (ft_manager->priv->model),
607       &iter);
608   row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (
609       ft_manager->priv->model), path);
610   gtk_tree_path_free (path);
611
612   g_object_ref (tp_file);
613   g_hash_table_insert (ft_manager->priv->tp_file_to_row_ref, tp_file,
614       row_ref);
615
616   ft_manager_update_ft_row (ft_manager, tp_file);
617
618   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (
619       ft_manager->priv->treeview));
620   gtk_tree_selection_select_iter (selection, &iter);
621
622   g_signal_connect (tp_file, "notify::state",
623       G_CALLBACK (ft_manager_state_changed_cb), ft_manager);
624   g_signal_connect (tp_file, "notify::transferred-bytes",
625       G_CALLBACK (ft_manager_transferred_bytes_changed_cb), ft_manager);
626
627   g_object_get (tp_file, "content-type", &content_type, NULL);
628
629   theme = gtk_icon_theme_get_default ();
630   /* FIXME remove the dependency on libgnomeui replacing this function
631    * with gio/gvfs or copying the code from gtk-recent.
632    * With GTK+ 2.14 we can get the GIcon using g_content_type_get_icon
633    * and then use the "gicon" property of GtkCellRendererPixbuf. */
634   icon_name = gnome_icon_lookup (theme, NULL, NULL, NULL, NULL,
635       content_type, GNOME_ICON_LOOKUP_FLAGS_NONE, NULL);
636
637   gtk_list_store_set (GTK_LIST_STORE (
638       ft_manager->priv->model), &iter, COL_ICON, icon_name, -1);
639
640   gtk_window_present (GTK_WINDOW (ft_manager->priv->window));
641   g_free (content_type);
642   g_free (icon_name);
643 }
644
645 static void
646 ft_manager_open (EmpathyFTManager *ft_manager)
647 {
648   GtkTreeSelection *selection;
649   GtkTreeIter iter;
650   GtkTreeModel *model;
651   EmpathyTpFile *tp_file;
652   const gchar *uri;
653
654   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ft_manager->priv->treeview));
655
656   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
657     return;
658
659   gtk_tree_model_get (model, &iter, COL_FT_OBJECT, &tp_file, -1);
660   g_return_if_fail (tp_file != NULL);
661
662   uri = g_object_get_data (G_OBJECT (tp_file), "uri");
663   DEBUG ("Opening URI: %s", uri);
664   empathy_url_show (uri);
665 }
666
667 static void
668 ft_manager_stop (EmpathyFTManager *ft_manager)
669 {
670   GtkTreeSelection *selection;
671   GtkTreeIter iter;
672   GtkTreeModel *model;
673   EmpathyTpFile *tp_file;
674
675   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ft_manager->priv->treeview));
676
677   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
678     return;
679
680   gtk_tree_model_get (model, &iter, COL_FT_OBJECT, &tp_file, -1);
681   g_return_if_fail (tp_file != NULL);
682
683   DEBUG ("Stopping file transfer: contact=%s, filename=%s",
684       empathy_contact_get_name (empathy_tp_file_get_contact (tp_file)),
685       empathy_tp_file_get_filename (tp_file));
686
687   empathy_tp_file_cancel (tp_file);
688 }
689
690 static void
691 ft_manager_response_cb (GtkWidget *dialog,
692                         gint response,
693                         EmpathyFTManager *ft_manager)
694 {
695   switch (response)
696     {
697       case RESPONSE_CLEAR:
698         ft_manager_clear (ft_manager);
699         break;
700       case RESPONSE_OPEN:
701         ft_manager_open (ft_manager);
702         break;
703       case RESPONSE_STOP:
704         ft_manager_stop (ft_manager);
705         break;
706     }
707 }
708
709 /*
710  * Receiving files
711  */
712
713 typedef struct {
714   EmpathyFTManager *ft_manager;
715   EmpathyTpFile *tp_file;
716 } ReceiveResponseData;
717
718 static void
719 ft_manager_receive_response_data_free (ReceiveResponseData *response_data)
720 {
721   if (!response_data)
722     return;
723
724   g_object_unref (response_data->tp_file);
725   g_object_unref (response_data->ft_manager);
726   g_slice_free (ReceiveResponseData, response_data);
727 }
728
729 static void
730 ft_manager_save_dialog_response_cb (GtkDialog *widget,
731                                     gint response_id,
732                                     ReceiveResponseData *response_data)
733 {
734   if (response_id == GTK_RESPONSE_OK)
735     {
736       gchar *uri;
737       gchar *folder;
738
739       uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (widget));
740
741       if (uri)
742         {
743           GFile *file;
744           GError *error = NULL;
745
746           file = g_file_new_for_uri (uri);
747           empathy_tp_file_accept (response_data->tp_file, 0, file, &error);
748
749           if (error)
750             {
751               GtkWidget *dialog;
752
753               DEBUG ("Error with opening file to write to: %s",
754                   error->message ? error->message : "no error");
755
756               /* Error is already translated */
757               dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_ERROR,
758                   GTK_BUTTONS_CLOSE, _("Cannot save file to this location"));
759
760               gtk_message_dialog_format_secondary_text (
761                   GTK_MESSAGE_DIALOG (dialog), "%s",
762                   error->message);
763
764               g_signal_connect (dialog, "response",
765                   G_CALLBACK (gtk_widget_destroy), NULL);
766
767               gtk_widget_show (dialog);
768
769               g_error_free (error);
770               return;
771             }
772
773           g_object_set_data_full (G_OBJECT (response_data->tp_file),
774               "uri", uri, g_free);
775
776           ft_manager_add_tp_file_to_list (response_data->ft_manager,
777               response_data->tp_file);
778
779           g_object_unref (file);
780         }
781
782       folder = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (widget));
783       if (folder)
784         {
785           empathy_conf_set_string (empathy_conf_get (),
786               EMPATHY_PREFS_FILE_TRANSFER_DEFAULT_FOLDER,
787               folder);
788           g_free (folder);
789         }
790     }
791
792   gtk_widget_destroy (GTK_WIDGET (widget));
793   ft_manager_receive_response_data_free (response_data);
794 }
795
796 static void
797 ft_manager_create_save_dialog (ReceiveResponseData *response_data)
798 {
799   GtkWidget *widget;
800   gchar *folder;
801
802   DEBUG ("Creating save file chooser");
803
804   widget = gtk_file_chooser_dialog_new (_("Save file as..."),
805       NULL, GTK_FILE_CHOOSER_ACTION_SAVE,
806       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
807       GTK_STOCK_SAVE_AS, GTK_RESPONSE_OK,
808       NULL);
809
810   if (!empathy_conf_get_string (empathy_conf_get (),
811       EMPATHY_PREFS_FILE_TRANSFER_DEFAULT_FOLDER,
812       &folder) || !folder)
813     folder = g_strdup (g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD));
814
815   if (folder)
816     gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (widget), folder);
817
818   gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (widget),
819       empathy_tp_file_get_filename (response_data->tp_file));
820
821   gtk_dialog_set_default_response (GTK_DIALOG (widget),
822       GTK_RESPONSE_OK);
823
824   gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (widget),
825       TRUE);
826
827   g_signal_connect (widget, "response",
828       G_CALLBACK (ft_manager_save_dialog_response_cb), response_data);
829
830   gtk_widget_show (widget);
831
832   g_free (folder);
833 }
834
835 static void
836 ft_manager_receive_file_response_cb (GtkWidget *dialog,
837                                      gint response,
838                                      ReceiveResponseData *response_data)
839 {
840   if (response == GTK_RESPONSE_ACCEPT)
841     ft_manager_create_save_dialog (response_data);
842   else
843     {
844       empathy_tp_file_cancel (response_data->tp_file);
845       ft_manager_receive_response_data_free (response_data);
846     }
847
848   gtk_widget_destroy (dialog);
849 }
850
851 static void
852 ft_manager_display_accept_dialog (EmpathyFTManager *ft_manager,
853                                   EmpathyTpFile *tp_file)
854 {
855   GtkWidget *dialog;
856   GtkWidget *image;
857   GtkWidget *button;
858   const gchar *contact_name;
859   const gchar *filename;
860   guint64 size;
861   gchar *size_str;
862   ReceiveResponseData *response_data;
863
864   g_return_if_fail (EMPATHY_IS_FT_MANAGER (ft_manager));
865   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
866
867   DEBUG ("Creating accept dialog");
868
869   contact_name = empathy_contact_get_name (empathy_tp_file_get_contact (tp_file));
870   filename = empathy_tp_file_get_filename (tp_file);
871
872   size = empathy_tp_file_get_size (tp_file);
873   if (size == EMPATHY_TP_FILE_UNKNOWN_SIZE)
874     size_str = g_strdup (_("unknown size"));
875   else
876     size_str = g_format_size_for_display (size);
877
878   dialog = gtk_message_dialog_new (NULL, 0, GTK_MESSAGE_INFO,
879       GTK_BUTTONS_NONE,
880       _("%s would like to send you a file"),
881       contact_name);
882
883   gtk_message_dialog_format_secondary_text
884       (GTK_MESSAGE_DIALOG (dialog),
885        _("Do you want to accept the file \"%s\" (%s)?"),
886        filename, size_str);
887
888   /* Icon */
889   image = gtk_image_new_from_stock (GTK_STOCK_SAVE, GTK_ICON_SIZE_DIALOG);
890   gtk_widget_show (image);
891   gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
892
893   /* Decline button */
894   button = gtk_button_new_with_mnemonic (_("_Decline"));
895   gtk_button_set_image (GTK_BUTTON (button),
896       gtk_image_new_from_stock (GTK_STOCK_CANCEL,
897           GTK_ICON_SIZE_BUTTON));
898   gtk_widget_show (button);
899   gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
900       GTK_RESPONSE_REJECT);
901
902   /* Accept button */
903   button = gtk_button_new_with_mnemonic (_("_Accept"));
904   gtk_button_set_image (GTK_BUTTON (button),
905       gtk_image_new_from_stock (GTK_STOCK_SAVE,
906           GTK_ICON_SIZE_BUTTON));
907   gtk_widget_show (button);
908   gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
909       GTK_RESPONSE_ACCEPT);
910   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
911   gtk_widget_grab_default (button);
912
913   response_data = g_slice_new0 (ReceiveResponseData);
914   response_data->ft_manager = g_object_ref (ft_manager);
915   response_data->tp_file = g_object_ref (tp_file);
916
917   g_signal_connect (dialog, "response",
918       G_CALLBACK (ft_manager_receive_file_response_cb), response_data);
919
920   gtk_widget_show (dialog);
921
922   g_free (size_str);
923 }
924
925 /**
926  * empathy_ft_manager_add_tp_file:
927  * @ft_manager: an #EmpathyFTManager
928  * @ft: an #EmpathyFT
929  *
930  * Adds a file transfer to the file transfer manager dialog @ft_manager.
931  * The manager dialog then shows the progress and other information about
932  * @ft.
933  */
934 void
935 empathy_ft_manager_add_tp_file (EmpathyFTManager *ft_manager,
936                                 EmpathyTpFile *tp_file)
937 {
938   EmpFileTransferState state;
939
940   g_return_if_fail (EMPATHY_IS_FT_MANAGER (ft_manager));
941   g_return_if_fail (EMPATHY_IS_TP_FILE (tp_file));
942
943   DEBUG ("Adding a file transfer: contact=%s, filename=%s",
944       empathy_contact_get_name (empathy_tp_file_get_contact (tp_file)),
945       empathy_tp_file_get_filename (tp_file));
946
947   state = empathy_tp_file_get_state (tp_file);
948
949   if (state == EMP_FILE_TRANSFER_STATE_PENDING &&
950       empathy_tp_file_is_incoming (tp_file))
951     ft_manager_display_accept_dialog (ft_manager, tp_file);
952   else
953     ft_manager_add_tp_file_to_list (ft_manager, tp_file);
954 }
955
956 static void
957 empathy_ft_manager_finalize (GObject *object)
958 {
959   EmpathyFTManager *ft_manager = (EmpathyFTManager *) object;
960
961   DEBUG ("Finalizing: %p", object);
962
963   g_hash_table_destroy (ft_manager->priv->tp_file_to_row_ref);
964
965   if (ft_manager->priv->save_geometry_id != 0)
966     g_source_remove (ft_manager->priv->save_geometry_id);
967
968   G_OBJECT_CLASS (empathy_ft_manager_parent_class)->finalize (object);
969 }
970
971 static gboolean
972 ft_manager_delete_event_cb (GtkWidget *widget,
973                             GdkEvent *event,
974                             EmpathyFTManager *ft_manager)
975 {
976   ft_manager_clear (ft_manager);
977   if (g_hash_table_size (ft_manager->priv->tp_file_to_row_ref) == 0)
978     {
979       DEBUG ("Destroying window");
980       if (manager_p != NULL)
981         g_object_unref (manager_p);
982
983       manager_p = NULL;
984       return FALSE;
985     }
986   else
987     {
988       DEBUG ("Hiding window");
989       gtk_widget_hide (widget);
990       return TRUE;
991     }
992 }
993
994 static void
995 ft_manager_build_ui (EmpathyFTManager *ft_manager)
996 {
997   gint x, y, w, h;
998   GtkListStore *liststore;
999   GtkTreeViewColumn *column;
1000   GtkCellRenderer *renderer;
1001   GtkTreeSelection *selection;
1002   gchar *filename;
1003
1004   filename = empathy_file_lookup ("empathy-ft-manager.glade",
1005       "libempathy-gtk");
1006   empathy_glade_get_file (filename,
1007       "ft_manager_dialog", NULL,
1008       "ft_manager_dialog", &ft_manager->priv->window,
1009       "ft_list", &ft_manager->priv->treeview,
1010       "open_button", &ft_manager->priv->open_button,
1011       "abort_button", &ft_manager->priv->abort_button,
1012       NULL);
1013   g_free (filename);
1014
1015   g_signal_connect (ft_manager->priv->window, "response",
1016       G_CALLBACK (ft_manager_response_cb), ft_manager);
1017   g_signal_connect (ft_manager->priv->window, "delete-event",
1018       G_CALLBACK (ft_manager_delete_event_cb), ft_manager);
1019   g_signal_connect (ft_manager->priv->window, "configure-event",
1020       G_CALLBACK (ft_manager_configure_event_cb), ft_manager);
1021
1022   /* Window geometry. */
1023   empathy_geometry_load ("ft-manager", &x, &y, &w, &h);
1024
1025   if (x >= 0 && y >= 0)
1026     {
1027       /* Let the window manager position it if we don't have
1028        * good x, y coordinates. */
1029       gtk_window_move (GTK_WINDOW (ft_manager->priv->window), x, y);
1030     }
1031
1032   if (w > 0 && h > 0)
1033     {
1034       /* Use the defaults from the glade file if we don't have
1035        * good w, h geometry. */
1036       gtk_window_resize (GTK_WINDOW (ft_manager->priv->window), w, h);
1037     }
1038
1039   gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (
1040       ft_manager->priv->treeview)), GTK_SELECTION_BROWSE);
1041
1042   liststore = gtk_list_store_new (5, G_TYPE_INT, G_TYPE_STRING,
1043       G_TYPE_STRING, G_TYPE_STRING, G_TYPE_OBJECT);
1044
1045   gtk_tree_view_set_model (GTK_TREE_VIEW(ft_manager->priv->treeview),
1046       GTK_TREE_MODEL (liststore));
1047   g_object_unref (liststore);
1048   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(ft_manager->priv->treeview), TRUE);
1049
1050   /* Icon and filename column*/
1051   column = gtk_tree_view_column_new ();
1052   gtk_tree_view_column_set_title (column, _("File"));
1053   renderer = gtk_cell_renderer_pixbuf_new ();
1054   g_object_set (renderer, "xpad", 3, NULL);
1055   gtk_tree_view_column_pack_start (column, renderer, FALSE);
1056   gtk_tree_view_column_set_attributes (column, renderer,
1057       "icon-name", COL_ICON,
1058       NULL);
1059   g_object_set (renderer, "stock-size", GTK_ICON_SIZE_DND, NULL);
1060   renderer = gtk_cell_renderer_text_new ();
1061   g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1062   gtk_tree_view_column_pack_start (column, renderer, TRUE);
1063   gtk_tree_view_column_set_attributes (column, renderer,
1064       "text", COL_MESSAGE,
1065       NULL);
1066   gtk_tree_view_insert_column (GTK_TREE_VIEW (ft_manager->priv->treeview), column,
1067       FILE_COL_POS);
1068   gtk_tree_view_column_set_expand (column, TRUE);
1069   gtk_tree_view_column_set_resizable (column, TRUE);
1070   gtk_tree_view_column_set_sort_column_id (column, COL_MESSAGE);
1071   gtk_tree_view_column_set_spacing (column, 3);
1072
1073   /* Progress column */
1074   renderer = gtk_cell_renderer_progress_new ();
1075   g_object_set (renderer, "xalign", 0.5, NULL);
1076   gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (ft_manager->priv->treeview),
1077       PROGRESS_COL_POS, _("%"),
1078       renderer,
1079       NULL);
1080   column = gtk_tree_view_get_column (GTK_TREE_VIEW (ft_manager->priv->treeview),
1081       PROGRESS_COL_POS);
1082   gtk_tree_view_column_set_cell_data_func(column, renderer,
1083       ft_manager_progress_cell_data_func,
1084       NULL, NULL);
1085   gtk_tree_view_column_set_sort_column_id (column, COL_PERCENT);
1086
1087   /* Remaining time column */
1088   renderer = gtk_cell_renderer_text_new ();
1089   g_object_set (renderer, "xalign", 0.5, NULL);
1090   gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (
1091       ft_manager->priv->treeview), REMAINING_COL_POS, _("Remaining"),
1092       renderer, "text", COL_REMAINING, NULL);
1093
1094   column = gtk_tree_view_get_column (GTK_TREE_VIEW (
1095       ft_manager->priv->treeview),
1096       REMAINING_COL_POS);
1097   gtk_tree_view_column_set_sort_column_id (column, COL_REMAINING);
1098
1099   gtk_tree_view_set_enable_search (GTK_TREE_VIEW (ft_manager->priv->treeview),
1100       FALSE);
1101
1102   ft_manager->priv->model = GTK_TREE_MODEL (liststore);
1103
1104   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (
1105       ft_manager->priv->treeview));
1106   g_signal_connect (selection, "changed",
1107       G_CALLBACK (ft_manager_selection_changed), ft_manager);
1108 }
1109
1110 static void
1111 empathy_ft_manager_init (EmpathyFTManager *ft_manager)
1112 {
1113   EmpathyFTManagerPriv *priv;
1114
1115   priv = G_TYPE_INSTANCE_GET_PRIVATE ((ft_manager), EMPATHY_TYPE_FT_MANAGER,
1116       EmpathyFTManagerPriv);
1117
1118   ft_manager->priv = priv;
1119
1120   priv->tp_file_to_row_ref = g_hash_table_new_full (g_direct_hash,
1121       g_direct_equal, NULL, (GDestroyNotify) gtk_tree_row_reference_free);
1122
1123   ft_manager_build_ui (ft_manager);
1124 }
1125
1126 static void
1127 empathy_ft_manager_class_init (EmpathyFTManagerClass *klass)
1128 {
1129   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1130
1131   object_class->finalize = empathy_ft_manager_finalize;
1132
1133   g_type_class_add_private (object_class, sizeof (EmpathyFTManagerPriv));
1134 }