Merged in default (pull request #594)
[pidgin-git.git] / pidgin / gtkxfer.c
blobbb5f5eb6ff78dd4e349840a9f9f40af64eb12507
1 /* pidgin
3 * Pidgin is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
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
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
21 #include "internal.h"
22 #include "pidgin.h"
24 #include "debug.h"
25 #include "notify.h"
26 #include "xfer.h"
27 #include "protocol.h"
28 #include "util.h"
30 #include "gtkxfer.h"
31 #include "prefs.h"
32 #include "pidginstock.h"
33 #include "gtkutils.h"
35 #include "gtk3compat.h"
37 #ifdef _WIN32
38 # include <shellapi.h>
39 #endif
41 /* the maximum size of files we will try to make a thumbnail for */
42 #define PIDGIN_XFER_MAX_SIZE_IMAGE_THUMBNAIL 10 * 1024 * 1024
44 struct _PidginXferDialog
46 GtkDialog parent;
48 GtkWidget *keep_open;
49 GtkWidget *auto_clear;
51 gint num_transfers;
53 PurpleXfer *selected_xfer;
55 GtkWidget *tree;
56 GtkListStore *model;
58 GtkWidget *expander;
60 GtkWidget *local_user_desc_label;
61 GtkWidget *local_user_label;
62 GtkWidget *remote_user_desc_label;
63 GtkWidget *remote_user_label;
64 GtkWidget *protocol_label;
65 GtkWidget *filename_label;
66 GtkWidget *localfile_label;
67 GtkWidget *status_label;
68 GtkWidget *speed_label;
69 GtkWidget *time_elapsed_label;
70 GtkWidget *time_remaining_label;
72 GtkWidget *progress;
74 /* Buttons */
75 GtkWidget *open_button;
76 GtkWidget *remove_button;
77 GtkWidget *stop_button;
78 GtkWidget *close_button;
81 G_DEFINE_TYPE(PidginXferDialog, pidgin_xfer_dialog, GTK_TYPE_DIALOG);
83 typedef struct
85 GtkTreeIter iter;
86 gint64 last_updated_time;
87 gboolean in_list;
89 char *name;
91 } PidginXferUiData;
93 static PidginXferDialog *xfer_dialog = NULL;
95 enum
97 COLUMN_STATUS = 0,
98 COLUMN_PROGRESS,
99 COLUMN_FILENAME,
100 COLUMN_SIZE,
101 COLUMN_REMAINING,
102 COLUMN_XFER,
103 NUM_COLUMNS
106 /**************************************************************************
107 * Utility Functions
108 **************************************************************************/
109 static void
110 get_xfer_info_strings(PurpleXfer *xfer, char **kbsec, char **time_elapsed,
111 char **time_remaining)
113 double kb_sent, kb_rem;
114 double kbps = 0.0;
115 gint64 now;
116 gint64 elapsed = 0;
118 elapsed = purple_xfer_get_start_time(xfer);
119 if (elapsed > 0) {
120 now = purple_xfer_get_end_time(xfer);
121 if (now == 0) {
122 now = g_get_monotonic_time();
124 elapsed = now - elapsed;
127 kb_sent = purple_xfer_get_bytes_sent(xfer) / 1000.0;
128 kb_rem = purple_xfer_get_bytes_remaining(xfer) / 1000.0;
129 kbps = (elapsed > 0 ? (kb_sent * G_USEC_PER_SEC) / elapsed : 0);
131 if (kbsec != NULL) {
132 *kbsec = g_strdup_printf(_("%.2f KB/s"), kbps);
135 if (time_elapsed != NULL)
137 int h, m, s;
138 int secs_elapsed;
140 if (purple_xfer_get_start_time(xfer) > 0) {
141 secs_elapsed = elapsed / G_USEC_PER_SEC;
143 h = secs_elapsed / 3600;
144 m = (secs_elapsed % 3600) / 60;
145 s = secs_elapsed % 60;
147 *time_elapsed = g_strdup_printf("%d:%02d:%02d", h, m, s);
148 } else {
149 *time_elapsed = g_strdup(_("Not started"));
153 if (time_remaining != NULL) {
154 if (purple_xfer_is_completed(xfer)) {
155 *time_remaining = g_strdup(_("Finished"));
157 else if (purple_xfer_is_cancelled(xfer)) {
158 *time_remaining = g_strdup(_("Cancelled"));
160 else if (purple_xfer_get_size(xfer) == 0 || (kb_sent > 0 && kbps < 0.001)) {
161 *time_remaining = g_strdup(_("Unknown"));
163 else if (kb_sent <= 0) {
164 *time_remaining = g_strdup(_("Waiting for transfer to begin"));
166 else {
167 int h, m, s;
168 int secs_remaining;
170 secs_remaining = (int)(kb_rem / kbps);
172 h = secs_remaining / 3600;
173 m = (secs_remaining % 3600) / 60;
174 s = secs_remaining % 60;
176 *time_remaining = g_strdup_printf("%d:%02d:%02d", h, m, s);
181 static void
182 update_title_progress(PidginXferDialog *dialog)
184 gboolean valid;
185 GtkTreeIter iter;
186 int num_active_xfers = 0;
187 guint64 total_bytes_xferred = 0;
188 guint64 total_file_size = 0;
190 valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dialog->model), &iter);
192 /* Find all active transfers */
193 while (valid) {
194 GValue val;
195 PurpleXfer *xfer = NULL;
197 val.g_type = 0;
198 gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
199 COLUMN_XFER, &val);
200 xfer = g_value_get_object(&val);
202 if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_STARTED) {
203 num_active_xfers++;
204 total_bytes_xferred += purple_xfer_get_bytes_sent(xfer);
205 total_file_size += purple_xfer_get_size(xfer);
208 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(dialog->model), &iter);
211 /* Update the title */
212 if (num_active_xfers > 0)
214 gchar *title;
215 int total_pct = 0;
217 if (total_file_size > 0) {
218 total_pct = 100 * total_bytes_xferred / total_file_size;
221 title = g_strdup_printf(ngettext("File Transfers - %d%% of %d file",
222 "File Transfers - %d%% of %d files",
223 num_active_xfers),
224 total_pct, num_active_xfers);
225 gtk_window_set_title(GTK_WINDOW(dialog), title);
226 g_free(title);
227 } else {
228 gtk_window_set_title(GTK_WINDOW(dialog), _("File Transfers"));
232 static void
233 update_detailed_info(PidginXferDialog *dialog, PurpleXfer *xfer)
235 PidginXferUiData *data;
236 char *kbsec, *time_elapsed, *time_remaining;
237 char *status, *utf8;
239 if (dialog == NULL || xfer == NULL)
240 return;
242 data = purple_xfer_get_ui_data(xfer);
244 get_xfer_info_strings(xfer, &kbsec, &time_elapsed, &time_remaining);
246 status = g_strdup_printf("%d%% (%" G_GOFFSET_FORMAT " of %" G_GOFFSET_FORMAT " bytes)",
247 (int)(purple_xfer_get_progress(xfer)*100),
248 purple_xfer_get_bytes_sent(xfer),
249 purple_xfer_get_size(xfer));
251 if (purple_xfer_is_completed(xfer)) {
252 gtk_list_store_set(GTK_LIST_STORE(xfer_dialog->model), &data->iter,
253 COLUMN_STATUS, NULL,
254 -1);
257 if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) {
258 gtk_label_set_markup(GTK_LABEL(dialog->local_user_desc_label),
259 _("<b>Receiving As:</b>"));
260 gtk_label_set_markup(GTK_LABEL(dialog->remote_user_desc_label),
261 _("<b>Receiving From:</b>"));
263 else {
264 gtk_label_set_markup(GTK_LABEL(dialog->remote_user_desc_label),
265 _("<b>Sending To:</b>"));
266 gtk_label_set_markup(GTK_LABEL(dialog->local_user_desc_label),
267 _("<b>Sending As:</b>"));
270 gtk_label_set_text(GTK_LABEL(dialog->local_user_label),
271 purple_account_get_username(purple_xfer_get_account(xfer)));
272 gtk_label_set_text(GTK_LABEL(dialog->remote_user_label), purple_xfer_get_remote_user(xfer));
273 gtk_label_set_text(GTK_LABEL(dialog->protocol_label),
274 purple_account_get_protocol_name(purple_xfer_get_account(xfer)));
276 if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) {
277 gtk_label_set_text(GTK_LABEL(dialog->filename_label),
278 purple_xfer_get_filename(xfer));
279 } else {
280 char *tmp;
282 tmp = g_path_get_basename(purple_xfer_get_local_filename(xfer));
283 utf8 = g_filename_to_utf8(tmp, -1, NULL, NULL, NULL);
284 g_free(tmp);
286 gtk_label_set_text(GTK_LABEL(dialog->filename_label), utf8);
287 g_free(utf8);
290 utf8 = g_filename_to_utf8((purple_xfer_get_local_filename(xfer)), -1, NULL, NULL, NULL);
291 gtk_label_set_text(GTK_LABEL(dialog->localfile_label), utf8);
292 g_free(utf8);
294 gtk_label_set_text(GTK_LABEL(dialog->status_label), status);
296 gtk_label_set_text(GTK_LABEL(dialog->speed_label), kbsec);
297 gtk_label_set_text(GTK_LABEL(dialog->time_elapsed_label), time_elapsed);
298 gtk_label_set_text(GTK_LABEL(dialog->time_remaining_label),
299 time_remaining);
301 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dialog->progress),
302 purple_xfer_get_progress(xfer));
304 g_free(kbsec);
305 g_free(time_elapsed);
306 g_free(time_remaining);
307 g_free(status);
310 static void
311 update_buttons(PidginXferDialog *dialog, PurpleXfer *xfer)
313 if (dialog->selected_xfer == NULL) {
314 gtk_widget_set_sensitive(dialog->expander, FALSE);
315 gtk_widget_set_sensitive(dialog->open_button, FALSE);
316 gtk_widget_set_sensitive(dialog->stop_button, FALSE);
318 gtk_widget_show(dialog->stop_button);
319 gtk_widget_hide(dialog->remove_button);
321 return;
324 if (dialog->selected_xfer != xfer)
325 return;
327 if (purple_xfer_is_completed(xfer)) {
328 gtk_widget_hide(dialog->stop_button);
329 gtk_widget_show(dialog->remove_button);
331 #ifdef _WIN32
332 /* If using Win32... */
333 if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) {
334 gtk_widget_set_sensitive(dialog->open_button, TRUE);
335 } else {
336 gtk_widget_set_sensitive(dialog->open_button, FALSE);
338 #else
339 if (purple_xfer_get_xfer_type(xfer) == PURPLE_XFER_TYPE_RECEIVE) {
340 gtk_widget_set_sensitive(dialog->open_button, TRUE);
341 } else {
342 gtk_widget_set_sensitive (dialog->open_button, FALSE);
344 #endif
346 gtk_widget_set_sensitive(dialog->remove_button, TRUE);
347 } else if (purple_xfer_is_cancelled(xfer)) {
348 gtk_widget_hide(dialog->stop_button);
349 gtk_widget_show(dialog->remove_button);
351 gtk_widget_set_sensitive(dialog->open_button, FALSE);
353 gtk_widget_set_sensitive(dialog->remove_button, TRUE);
354 } else {
355 gtk_widget_show(dialog->stop_button);
356 gtk_widget_hide(dialog->remove_button);
358 gtk_widget_set_sensitive(dialog->open_button, FALSE);
359 gtk_widget_set_sensitive(dialog->stop_button, TRUE);
363 static void
364 ensure_row_selected(PidginXferDialog *dialog)
366 GtkTreeIter iter;
367 GtkTreeSelection *selection;
369 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->tree));
371 if (gtk_tree_selection_get_selected(selection, NULL, &iter))
372 return;
374 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dialog->model), &iter))
375 gtk_tree_selection_select_iter(selection, &iter);
378 /**************************************************************************
379 * Callbacks
380 **************************************************************************/
381 static gint
382 delete_win_cb(GtkWidget *w, GdkEventAny *e, gpointer d)
384 PidginXferDialog *dialog;
386 dialog = (PidginXferDialog *)d;
388 pidgin_xfer_dialog_hide(dialog);
390 return TRUE;
393 static void
394 toggle_keep_open_cb(GtkWidget *w, G_GNUC_UNUSED gpointer data)
396 purple_prefs_set_bool(
397 PIDGIN_PREFS_ROOT "/filetransfer/keep_open",
398 !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)));
401 static void
402 toggle_clear_finished_cb(GtkWidget *w, G_GNUC_UNUSED gpointer data)
404 purple_prefs_set_bool(
405 PIDGIN_PREFS_ROOT "/filetransfer/clear_finished",
406 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)));
409 static void
410 selection_changed_cb(GtkTreeSelection *selection, PidginXferDialog *dialog)
412 GtkTreeIter iter;
413 PurpleXfer *xfer = NULL;
415 if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
416 GValue val;
418 gtk_widget_set_sensitive(dialog->expander, TRUE);
420 val.g_type = 0;
421 gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
422 COLUMN_XFER, &val);
423 xfer = g_value_get_object(&val);
425 update_detailed_info(dialog, xfer);
427 dialog->selected_xfer = xfer;
429 else {
430 gtk_expander_set_expanded(GTK_EXPANDER(dialog->expander),
431 FALSE);
433 gtk_widget_set_sensitive(dialog->expander, FALSE);
435 dialog->selected_xfer = NULL;
438 update_buttons(dialog, xfer);
441 static void
442 open_button_cb(GtkButton *button, PidginXferDialog *dialog)
444 #ifdef _WIN32
445 /* If using Win32... */
446 int code;
447 wchar_t *wc_filename = g_utf8_to_utf16(
448 purple_xfer_get_local_filename(
449 dialog->selected_xfer),
450 -1, NULL, NULL, NULL);
452 code = (int) ShellExecuteW(NULL, NULL, wc_filename, NULL, NULL,
453 SW_SHOW);
455 g_free(wc_filename);
457 if (code == SE_ERR_ASSOCINCOMPLETE || code == SE_ERR_NOASSOC)
459 purple_notify_error(dialog, NULL,
460 _("There is no application configured to open this type of file."),
461 NULL, NULL);
463 else if (code < 32)
465 purple_notify_error(dialog, NULL,
466 _("An error occurred while opening the file."), NULL, NULL);
467 purple_debug_warning("xfer", "filename: %s; code: %d\n",
468 purple_xfer_get_local_filename(dialog->selected_xfer), code);
470 #else
471 const char *filename = purple_xfer_get_local_filename(dialog->selected_xfer);
472 char *command = NULL;
473 GError *error = NULL;
475 if (purple_running_gnome())
477 char *escaped = g_shell_quote(filename);
478 command = g_strdup_printf("gnome-open %s", escaped);
479 g_free(escaped);
481 else if (purple_running_kde())
483 char *escaped = g_shell_quote(filename);
485 if (purple_str_has_suffix(filename, ".desktop"))
486 command = g_strdup_printf("kfmclient openURL %s 'text/plain'", escaped);
487 else
488 command = g_strdup_printf("kfmclient openURL %s", escaped);
489 g_free(escaped);
491 else
493 gchar *uri = g_strdup_printf("file://%s", filename);
494 purple_notify_uri(NULL, uri);
495 g_free(uri);
496 return;
499 if (purple_program_is_valid(command))
501 gint exit_status;
502 if (!g_spawn_command_line_sync(command, NULL, NULL, &exit_status, &error))
504 char *tmp = g_strdup_printf(_("Error launching %s: %s"),
505 purple_xfer_get_local_filename(dialog->selected_xfer),
506 error->message);
507 purple_notify_error(dialog, NULL, _("Unable to open file."), tmp, NULL);
508 g_free(tmp);
509 g_error_free(error);
511 if (exit_status != 0)
513 char *primary = g_strdup_printf(_("Error running %s"), command);
514 char *secondary = g_strdup_printf(_("Process returned error code %d"),
515 exit_status);
516 purple_notify_error(dialog, NULL, primary, secondary, NULL);
517 g_free(primary);
518 g_free(secondary);
521 #endif
524 static void
525 remove_button_cb(GtkButton *button, PidginXferDialog *dialog)
527 pidgin_xfer_dialog_remove_xfer(dialog, dialog->selected_xfer);
530 static void
531 stop_button_cb(GtkButton *button, PidginXferDialog *dialog)
533 purple_xfer_cancel_local(dialog->selected_xfer);
536 static void
537 close_button_cb(GtkButton *button, PidginXferDialog *dialog)
539 pidgin_xfer_dialog_hide(dialog);
543 /**************************************************************************
544 * Dialog Building Functions
545 **************************************************************************/
546 PidginXferDialog *
547 pidgin_xfer_dialog_new(void)
549 return PIDGIN_XFER_DIALOG(g_object_new(PIDGIN_TYPE_XFER_DIALOG, NULL));
552 static void
553 pidgin_xfer_dialog_class_init(PidginXferDialogClass *klass)
555 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
557 gtk_widget_class_set_template_from_resource(
558 widget_class, "/im/pidgin/Pidgin/Xfer/xfer.ui");
560 gtk_widget_class_bind_template_callback(widget_class, delete_win_cb);
562 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
563 tree);
564 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
565 model);
566 gtk_widget_class_bind_template_callback(widget_class,
567 selection_changed_cb);
569 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
570 keep_open);
571 gtk_widget_class_bind_template_callback(widget_class,
572 toggle_keep_open_cb);
574 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
575 auto_clear);
576 gtk_widget_class_bind_template_callback(widget_class,
577 toggle_clear_finished_cb);
579 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
580 expander);
582 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
583 local_user_desc_label);
584 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
585 local_user_label);
586 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
587 remote_user_desc_label);
588 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
589 remote_user_label);
590 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
591 protocol_label);
592 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
593 filename_label);
594 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
595 localfile_label);
596 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
597 status_label);
598 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
599 speed_label);
600 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
601 time_elapsed_label);
602 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
603 time_remaining_label);
605 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
606 progress);
608 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
609 open_button);
610 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
611 remove_button);
612 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
613 stop_button);
614 gtk_widget_class_bind_template_child(widget_class, PidginXferDialog,
615 close_button);
617 gtk_widget_class_bind_template_callback(widget_class, open_button_cb);
618 gtk_widget_class_bind_template_callback(widget_class, remove_button_cb);
619 gtk_widget_class_bind_template_callback(widget_class, stop_button_cb);
620 gtk_widget_class_bind_template_callback(widget_class, close_button_cb);
623 static void
624 pidgin_xfer_dialog_init(PidginXferDialog *dialog)
626 gtk_widget_init_template(GTK_WIDGET(dialog));
628 /* "Close this window when all transfers finish" */
629 gtk_toggle_button_set_active(
630 GTK_TOGGLE_BUTTON(dialog->keep_open),
631 !purple_prefs_get_bool(PIDGIN_PREFS_ROOT
632 "/filetransfer/keep_open"));
634 /* "Clear finished transfers" */
635 gtk_toggle_button_set_active(
636 GTK_TOGGLE_BUTTON(dialog->auto_clear),
637 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
638 "/filetransfer/clear_finished"));
640 #ifdef _WIN32
641 g_signal_connect(G_OBJECT(dialog), "show",
642 G_CALLBACK(winpidgin_ensure_onscreen), NULL);
643 #endif
646 void
647 pidgin_xfer_dialog_destroy(PidginXferDialog *dialog)
649 g_return_if_fail(dialog != NULL);
651 purple_notify_close_with_handle(dialog);
653 gtk_widget_destroy(GTK_WIDGET(dialog));
656 void
657 pidgin_xfer_dialog_show(PidginXferDialog *dialog)
659 PidginXferDialog *tmp;
661 if (dialog == NULL) {
662 tmp = pidgin_get_xfer_dialog();
664 if (tmp == NULL) {
665 tmp = pidgin_xfer_dialog_new();
666 pidgin_set_xfer_dialog(tmp);
669 gtk_widget_show(GTK_WIDGET(tmp));
670 } else {
671 gtk_window_present(GTK_WINDOW(dialog));
675 void
676 pidgin_xfer_dialog_hide(PidginXferDialog *dialog)
678 g_return_if_fail(dialog != NULL);
680 purple_notify_close_with_handle(dialog);
682 gtk_widget_hide(GTK_WIDGET(dialog));
685 void
686 pidgin_xfer_dialog_add_xfer(PidginXferDialog *dialog, PurpleXfer *xfer)
688 PidginXferUiData *data;
689 PurpleXferType type;
690 const gchar *icon_name;
691 char *size_str, *remaining_str;
692 char *lfilename, *utf8;
694 g_return_if_fail(dialog != NULL);
695 g_return_if_fail(xfer != NULL);
697 g_object_ref(xfer);
699 data = purple_xfer_get_ui_data(xfer);
700 data->in_list = TRUE;
702 pidgin_xfer_dialog_show(dialog);
704 data->last_updated_time = 0;
706 type = purple_xfer_get_xfer_type(xfer);
708 size_str = g_format_size(purple_xfer_get_size(xfer));
709 remaining_str = g_format_size(purple_xfer_get_bytes_remaining(xfer));
711 icon_name = (type == PURPLE_XFER_TYPE_RECEIVE ? "go-down" : "go-up");
713 gtk_list_store_append(dialog->model, &data->iter);
714 lfilename = g_path_get_basename(purple_xfer_get_local_filename(xfer));
715 utf8 = g_filename_to_utf8(lfilename, -1, NULL, NULL, NULL);
716 g_free(lfilename);
717 lfilename = utf8;
718 gtk_list_store_set(dialog->model, &data->iter, COLUMN_STATUS, icon_name,
719 COLUMN_PROGRESS, 0, COLUMN_FILENAME,
720 (type == PURPLE_XFER_TYPE_RECEIVE)
721 ? purple_xfer_get_filename(xfer)
722 : lfilename,
723 COLUMN_SIZE, size_str, COLUMN_REMAINING,
724 _("Waiting for transfer to begin"), COLUMN_XFER,
725 xfer, -1);
726 g_free(lfilename);
728 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(dialog->tree));
730 g_free(size_str);
731 g_free(remaining_str);
733 dialog->num_transfers++;
735 ensure_row_selected(dialog);
736 update_title_progress(dialog);
739 void
740 pidgin_xfer_dialog_remove_xfer(PidginXferDialog *dialog,
741 PurpleXfer *xfer)
743 PidginXferUiData *data;
745 g_return_if_fail(dialog != NULL);
746 g_return_if_fail(xfer != NULL);
748 data = purple_xfer_get_ui_data(xfer);
750 if (data == NULL)
751 return;
753 if (!data->in_list)
754 return;
756 data->in_list = FALSE;
758 gtk_list_store_remove(GTK_LIST_STORE(dialog->model), &data->iter);
760 dialog->num_transfers--;
762 ensure_row_selected(dialog);
764 update_title_progress(dialog);
765 g_object_unref(xfer);
768 void
769 pidgin_xfer_dialog_cancel_xfer(PidginXferDialog *dialog,
770 PurpleXfer *xfer)
772 PidginXferUiData *data;
773 const gchar *status;
775 g_return_if_fail(dialog != NULL);
776 g_return_if_fail(xfer != NULL);
778 data = purple_xfer_get_ui_data(xfer);
780 if (data == NULL)
781 return;
783 if (!data->in_list)
784 return;
786 if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL &&
787 gtk_toggle_button_get_active(
788 GTK_TOGGLE_BUTTON(dialog->auto_clear))) {
789 pidgin_xfer_dialog_remove_xfer(dialog, xfer);
790 return;
793 data = purple_xfer_get_ui_data(xfer);
795 update_detailed_info(dialog, xfer);
796 update_title_progress(dialog);
798 if (purple_xfer_is_cancelled(xfer))
799 status = _("Cancelled");
800 else
801 status = _("Failed");
803 gtk_list_store_set(dialog->model, &data->iter,
804 COLUMN_STATUS, "dialog-error",
805 COLUMN_REMAINING, status,
806 -1);
808 update_buttons(dialog, xfer);
811 void
812 pidgin_xfer_dialog_update_xfer(PidginXferDialog *dialog,
813 PurpleXfer *xfer)
815 PidginXferUiData *data;
816 char *size_str, *remaining_str;
817 gint64 current_time;
818 GtkTreeIter iter;
819 gboolean valid;
821 g_return_if_fail(dialog != NULL);
822 g_return_if_fail(xfer != NULL);
824 if ((data = purple_xfer_get_ui_data(xfer)) == NULL)
825 return;
827 if (data->in_list == FALSE)
828 return;
830 current_time = g_get_monotonic_time();
831 if (((current_time - data->last_updated_time) < G_USEC_PER_SEC) &&
832 (!purple_xfer_is_completed(xfer)))
834 /* Don't update the window more than once per second */
835 return;
837 data->last_updated_time = current_time;
839 size_str = g_format_size(purple_xfer_get_size(xfer));
840 remaining_str = g_format_size(purple_xfer_get_bytes_remaining(xfer));
842 gtk_list_store_set(xfer_dialog->model, &data->iter,
843 COLUMN_PROGRESS, (gint)(purple_xfer_get_progress(xfer) * 100),
844 COLUMN_SIZE, size_str,
845 COLUMN_REMAINING, remaining_str,
846 -1);
848 g_free(size_str);
849 g_free(remaining_str);
851 if (purple_xfer_is_completed(xfer))
853 gtk_list_store_set(GTK_LIST_STORE(xfer_dialog->model), &data->iter,
854 COLUMN_STATUS, NULL,
855 COLUMN_REMAINING, _("Finished"),
856 -1);
859 update_title_progress(dialog);
860 if (xfer == dialog->selected_xfer)
861 update_detailed_info(xfer_dialog, xfer);
863 if (purple_xfer_is_completed(xfer) &&
864 gtk_toggle_button_get_active(
865 GTK_TOGGLE_BUTTON(dialog->auto_clear))) {
866 pidgin_xfer_dialog_remove_xfer(dialog, xfer);
867 } else {
868 update_buttons(dialog, xfer);
872 * If all transfers are finished, and the pref is set, then
873 * close the dialog. Otherwise just exit this function.
875 if (!gtk_toggle_button_get_active(
876 GTK_TOGGLE_BUTTON(dialog->keep_open))) {
877 return;
880 valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dialog->model), &iter);
881 while (valid)
883 GValue val;
884 PurpleXfer *next;
886 val.g_type = 0;
887 gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
888 COLUMN_XFER, &val);
889 next = g_value_get_object(&val);
891 if (!purple_xfer_is_completed(next))
892 return;
894 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(dialog->model), &iter);
897 /* If we got to this point then we know everything is finished */
898 pidgin_xfer_dialog_hide(dialog);
901 /**************************************************************************
902 * File Transfer UI Ops
903 **************************************************************************/
904 static void
905 pidgin_xfer_new_xfer(PurpleXfer *xfer)
907 PidginXferUiData *data;
909 /* This is where we're setting xfer's "ui_data" for the first time. */
910 data = g_new0(PidginXferUiData, 1);
911 purple_xfer_set_ui_data(xfer, data);
914 static void
915 pidgin_xfer_destroy(PurpleXfer *xfer)
917 PidginXferUiData *data;
919 data = purple_xfer_get_ui_data(xfer);
920 if (data) {
921 g_free(data->name);
922 g_free(data);
923 purple_xfer_set_ui_data(xfer, NULL);
927 static void
928 pidgin_xfer_add_xfer(PurpleXfer *xfer)
930 if (xfer_dialog == NULL)
931 xfer_dialog = pidgin_xfer_dialog_new();
933 pidgin_xfer_dialog_add_xfer(xfer_dialog, xfer);
936 static void
937 pidgin_xfer_update_progress(PurpleXfer *xfer, double percent)
939 pidgin_xfer_dialog_update_xfer(xfer_dialog, xfer);
942 static void
943 pidgin_xfer_cancel_local(PurpleXfer *xfer)
945 if (xfer_dialog)
946 pidgin_xfer_dialog_cancel_xfer(xfer_dialog, xfer);
949 static void
950 pidgin_xfer_cancel_remote(PurpleXfer *xfer)
952 if (xfer_dialog)
953 pidgin_xfer_dialog_cancel_xfer(xfer_dialog, xfer);
956 static void
957 pidgin_xfer_add_thumbnail(PurpleXfer *xfer, const gchar *formats)
959 purple_debug_info("xfer", "creating thumbnail for transfer\n");
961 if (purple_xfer_get_size(xfer) <= PIDGIN_XFER_MAX_SIZE_IMAGE_THUMBNAIL) {
962 GdkPixbuf *thumbnail =
963 pidgin_pixbuf_new_from_file_at_size(
964 purple_xfer_get_local_filename(xfer), 128, 128);
966 if (thumbnail) {
967 gchar **formats_split = g_strsplit(formats, ",", 0);
968 gchar *buffer = NULL;
969 gsize size;
970 char *option_keys[2] = {NULL, NULL};
971 char *option_values[2] = {NULL, NULL};
972 int i;
973 gchar *format = NULL;
975 for (i = 0; formats_split[i]; i++) {
976 if (purple_strequal(formats_split[i], "jpeg")) {
977 purple_debug_info("xfer", "creating JPEG thumbnail\n");
978 option_keys[0] = "quality";
979 option_values[0] = "90";
980 format = "jpeg";
981 break;
982 } else if (purple_strequal(formats_split[i], "png")) {
983 purple_debug_info("xfer", "creating PNG thumbnail\n");
984 option_keys[0] = "compression";
985 option_values[0] = "9";
986 format = "png";
987 break;
991 /* Try the first format given by the protocol without options */
992 if (format == NULL) {
993 purple_debug_info("xfer",
994 "creating thumbnail of format %s as demanded by protocol\n",
995 formats_split[0]);
996 format = formats_split[0];
999 gdk_pixbuf_save_to_bufferv(thumbnail, &buffer, &size, format,
1000 option_keys, option_values, NULL);
1002 if (buffer) {
1003 gchar *mimetype = g_strdup_printf("image/%s", format);
1004 purple_debug_info("xfer",
1005 "created thumbnail of %" G_GSIZE_FORMAT " bytes\n",
1006 size);
1007 purple_xfer_set_thumbnail(xfer, buffer, size, mimetype);
1008 g_free(buffer);
1009 g_free(mimetype);
1011 g_object_unref(thumbnail);
1012 g_strfreev(formats_split);
1017 static PurpleXferUiOps ops =
1019 pidgin_xfer_new_xfer,
1020 pidgin_xfer_destroy,
1021 pidgin_xfer_add_xfer,
1022 pidgin_xfer_update_progress,
1023 pidgin_xfer_cancel_local,
1024 pidgin_xfer_cancel_remote,
1025 NULL,
1026 NULL,
1027 NULL,
1028 pidgin_xfer_add_thumbnail
1031 /**************************************************************************
1032 * GTK+ File Transfer API
1033 **************************************************************************/
1034 void
1035 pidgin_xfers_init(void)
1037 purple_prefs_add_none(PIDGIN_PREFS_ROOT "/filetransfer");
1038 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/filetransfer/clear_finished", TRUE);
1039 purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/filetransfer/keep_open", FALSE);
1042 void
1043 pidgin_xfers_uninit(void)
1045 if (xfer_dialog != NULL)
1046 pidgin_xfer_dialog_destroy(xfer_dialog);
1049 void
1050 pidgin_set_xfer_dialog(PidginXferDialog *dialog)
1052 xfer_dialog = dialog;
1055 PidginXferDialog *
1056 pidgin_get_xfer_dialog(void)
1058 return xfer_dialog;
1061 PurpleXferUiOps *
1062 pidgin_xfers_get_ui_ops(void)
1064 return &ops;