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