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