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
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
32 #include "pidginstock.h"
35 #include "gtk3compat.h"
38 # include <shellapi.h>
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
53 PurpleXfer
*selected_xfer
;
63 GtkWidget
*local_user_desc_label
;
64 GtkWidget
*local_user_label
;
65 GtkWidget
*remote_user_desc_label
;
66 GtkWidget
*remote_user_label
;
67 GtkWidget
*protocol_label
;
68 GtkWidget
*filename_label
;
69 GtkWidget
*localfile_label
;
70 GtkWidget
*status_label
;
71 GtkWidget
*speed_label
;
72 GtkWidget
*time_elapsed_label
;
73 GtkWidget
*time_remaining_label
;
78 GtkWidget
*open_button
;
79 GtkWidget
*remove_button
;
80 GtkWidget
*stop_button
;
81 GtkWidget
*close_button
;
87 time_t last_updated_time
;
94 static PidginXferDialog
*xfer_dialog
= NULL
;
108 /**************************************************************************
110 **************************************************************************/
112 get_xfer_info_strings(PurpleXfer
*xfer
, char **kbsec
, char **time_elapsed
,
113 char **time_remaining
)
115 double kb_sent
, kb_rem
;
119 now
= purple_xfer_get_end_time(xfer
);
123 kb_sent
= purple_xfer_get_bytes_sent(xfer
) / 1024.0;
124 kb_rem
= purple_xfer_get_bytes_remaining(xfer
) / 1024.0;
125 elapsed
= purple_xfer_get_start_time(xfer
);
127 elapsed
= now
- elapsed
;
130 kbps
= (elapsed
> 0 ? (kb_sent
/ elapsed
) : 0);
133 *kbsec
= g_strdup_printf(_("%.2f KiB/s"), kbps
);
136 if (time_elapsed
!= NULL
)
141 if (purple_xfer_get_start_time(xfer
) > 0)
143 secs_elapsed
= now
- purple_xfer_get_start_time(xfer
);
145 h
= secs_elapsed
/ 3600;
146 m
= (secs_elapsed
% 3600) / 60;
147 s
= secs_elapsed
% 60;
149 *time_elapsed
= g_strdup_printf("%d:%02d:%02d", h
, m
, s
);
153 *time_elapsed
= g_strdup(_("Not started"));
157 if (time_remaining
!= NULL
) {
158 if (purple_xfer_is_completed(xfer
)) {
159 *time_remaining
= g_strdup(_("Finished"));
161 else if (purple_xfer_is_cancelled(xfer
)) {
162 *time_remaining
= g_strdup(_("Cancelled"));
164 else if (purple_xfer_get_size(xfer
) == 0 || (kb_sent
> 0 && kbps
< 0.001)) {
165 *time_remaining
= g_strdup(_("Unknown"));
167 else if (kb_sent
<= 0) {
168 *time_remaining
= g_strdup(_("Waiting for transfer to begin"));
174 secs_remaining
= (int)(kb_rem
/ kbps
);
176 h
= secs_remaining
/ 3600;
177 m
= (secs_remaining
% 3600) / 60;
178 s
= secs_remaining
% 60;
180 *time_remaining
= g_strdup_printf("%d:%02d:%02d", h
, m
, s
);
186 update_title_progress(PidginXferDialog
*dialog
)
190 int num_active_xfers
= 0;
191 guint64 total_bytes_xferred
= 0;
192 guint64 total_file_size
= 0;
194 if (dialog
->window
== NULL
)
197 valid
= gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dialog
->model
), &iter
);
199 /* Find all active transfers */
202 PurpleXfer
*xfer
= NULL
;
205 gtk_tree_model_get_value(GTK_TREE_MODEL(dialog
->model
),
206 &iter
, COLUMN_DATA
, &val
);
208 xfer
= g_value_get_pointer(&val
);
209 if (purple_xfer_get_status(xfer
) == PURPLE_XFER_STATUS_STARTED
) {
211 total_bytes_xferred
+= purple_xfer_get_bytes_sent(xfer
);
212 total_file_size
+= purple_xfer_get_size(xfer
);
215 valid
= gtk_tree_model_iter_next(GTK_TREE_MODEL(dialog
->model
), &iter
);
218 /* Update the title */
219 if (num_active_xfers
> 0)
224 if (total_file_size
> 0) {
225 total_pct
= 100 * total_bytes_xferred
/ total_file_size
;
228 title
= g_strdup_printf(ngettext("File Transfers - %d%% of %d file",
229 "File Transfers - %d%% of %d files",
231 total_pct
, num_active_xfers
);
232 gtk_window_set_title(GTK_WINDOW(dialog
->window
), title
);
235 gtk_window_set_title(GTK_WINDOW(dialog
->window
), _("File Transfers"));
240 update_detailed_info(PidginXferDialog
*dialog
, PurpleXfer
*xfer
)
242 PidginXferUiData
*data
;
243 char *kbsec
, *time_elapsed
, *time_remaining
;
246 if (dialog
== NULL
|| xfer
== NULL
)
249 data
= purple_xfer_get_ui_data(xfer
);
251 get_xfer_info_strings(xfer
, &kbsec
, &time_elapsed
, &time_remaining
);
253 status
= g_strdup_printf("%d%% (%" G_GOFFSET_FORMAT
" of %" G_GOFFSET_FORMAT
" bytes)",
254 (int)(purple_xfer_get_progress(xfer
)*100),
255 purple_xfer_get_bytes_sent(xfer
),
256 purple_xfer_get_size(xfer
));
258 if (purple_xfer_is_completed(xfer
)) {
260 GdkPixbuf
*pixbuf
= NULL
;
262 pixbuf
= gtk_widget_render_icon(xfer_dialog
->window
,
263 PIDGIN_STOCK_FILE_DONE
,
264 GTK_ICON_SIZE_MENU
, NULL
);
266 gtk_list_store_set(GTK_LIST_STORE(xfer_dialog
->model
), &data
->iter
,
267 COLUMN_STATUS
, pixbuf
,
270 g_object_unref(pixbuf
);
273 if (purple_xfer_get_xfer_type(xfer
) == PURPLE_XFER_TYPE_RECEIVE
) {
274 gtk_label_set_markup(GTK_LABEL(dialog
->local_user_desc_label
),
275 _("<b>Receiving As:</b>"));
276 gtk_label_set_markup(GTK_LABEL(dialog
->remote_user_desc_label
),
277 _("<b>Receiving From:</b>"));
280 gtk_label_set_markup(GTK_LABEL(dialog
->remote_user_desc_label
),
281 _("<b>Sending To:</b>"));
282 gtk_label_set_markup(GTK_LABEL(dialog
->local_user_desc_label
),
283 _("<b>Sending As:</b>"));
286 gtk_label_set_text(GTK_LABEL(dialog
->local_user_label
),
287 purple_account_get_username(purple_xfer_get_account(xfer
)));
288 gtk_label_set_text(GTK_LABEL(dialog
->remote_user_label
), purple_xfer_get_remote_user(xfer
));
289 gtk_label_set_text(GTK_LABEL(dialog
->protocol_label
),
290 purple_account_get_protocol_name(purple_xfer_get_account(xfer
)));
292 if (purple_xfer_get_xfer_type(xfer
) == PURPLE_XFER_TYPE_RECEIVE
) {
293 gtk_label_set_text(GTK_LABEL(dialog
->filename_label
),
294 purple_xfer_get_filename(xfer
));
298 tmp
= g_path_get_basename(purple_xfer_get_local_filename(xfer
));
299 utf8
= g_filename_to_utf8(tmp
, -1, NULL
, NULL
, NULL
);
302 gtk_label_set_text(GTK_LABEL(dialog
->filename_label
), utf8
);
306 utf8
= g_filename_to_utf8((purple_xfer_get_local_filename(xfer
)), -1, NULL
, NULL
, NULL
);
307 gtk_label_set_text(GTK_LABEL(dialog
->localfile_label
), utf8
);
310 gtk_label_set_text(GTK_LABEL(dialog
->status_label
), status
);
312 gtk_label_set_text(GTK_LABEL(dialog
->speed_label
), kbsec
);
313 gtk_label_set_text(GTK_LABEL(dialog
->time_elapsed_label
), time_elapsed
);
314 gtk_label_set_text(GTK_LABEL(dialog
->time_remaining_label
),
317 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dialog
->progress
),
318 purple_xfer_get_progress(xfer
));
321 g_free(time_elapsed
);
322 g_free(time_remaining
);
327 update_buttons(PidginXferDialog
*dialog
, PurpleXfer
*xfer
)
329 if (dialog
->selected_xfer
== NULL
) {
330 gtk_widget_set_sensitive(dialog
->expander
, FALSE
);
331 gtk_widget_set_sensitive(dialog
->open_button
, FALSE
);
332 gtk_widget_set_sensitive(dialog
->stop_button
, FALSE
);
334 gtk_widget_show(dialog
->stop_button
);
335 gtk_widget_hide(dialog
->remove_button
);
340 if (dialog
->selected_xfer
!= xfer
)
343 if (purple_xfer_is_completed(xfer
)) {
344 gtk_widget_hide(dialog
->stop_button
);
345 gtk_widget_show(dialog
->remove_button
);
348 /* If using Win32... */
349 if (purple_xfer_get_xfer_type(xfer
) == PURPLE_XFER_TYPE_RECEIVE
) {
350 gtk_widget_set_sensitive(dialog
->open_button
, TRUE
);
352 gtk_widget_set_sensitive(dialog
->open_button
, FALSE
);
355 if (purple_xfer_get_xfer_type(xfer
) == PURPLE_XFER_TYPE_RECEIVE
) {
356 gtk_widget_set_sensitive(dialog
->open_button
, TRUE
);
358 gtk_widget_set_sensitive (dialog
->open_button
, FALSE
);
362 gtk_widget_set_sensitive(dialog
->remove_button
, TRUE
);
363 } else if (purple_xfer_is_cancelled(xfer
)) {
364 gtk_widget_hide(dialog
->stop_button
);
365 gtk_widget_show(dialog
->remove_button
);
367 gtk_widget_set_sensitive(dialog
->open_button
, FALSE
);
369 gtk_widget_set_sensitive(dialog
->remove_button
, TRUE
);
371 gtk_widget_show(dialog
->stop_button
);
372 gtk_widget_hide(dialog
->remove_button
);
374 gtk_widget_set_sensitive(dialog
->open_button
, FALSE
);
375 gtk_widget_set_sensitive(dialog
->stop_button
, TRUE
);
380 ensure_row_selected(PidginXferDialog
*dialog
)
383 GtkTreeSelection
*selection
;
385 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog
->tree
));
387 if (gtk_tree_selection_get_selected(selection
, NULL
, &iter
))
390 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dialog
->model
), &iter
))
391 gtk_tree_selection_select_iter(selection
, &iter
);
394 /**************************************************************************
396 **************************************************************************/
398 delete_win_cb(GtkWidget
*w
, GdkEventAny
*e
, gpointer d
)
400 PidginXferDialog
*dialog
;
402 dialog
= (PidginXferDialog
*)d
;
404 pidgin_xfer_dialog_hide(dialog
);
410 toggle_keep_open_cb(GtkWidget
*w
, PidginXferDialog
*dialog
)
412 dialog
->keep_open
= !dialog
->keep_open
;
413 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/filetransfer/keep_open",
418 toggle_clear_finished_cb(GtkWidget
*w
, PidginXferDialog
*dialog
)
420 dialog
->auto_clear
= !dialog
->auto_clear
;
421 purple_prefs_set_bool(PIDGIN_PREFS_ROOT
"/filetransfer/clear_finished",
426 selection_changed_cb(GtkTreeSelection
*selection
, PidginXferDialog
*dialog
)
429 PurpleXfer
*xfer
= NULL
;
431 if (gtk_tree_selection_get_selected(selection
, NULL
, &iter
)) {
434 gtk_widget_set_sensitive(dialog
->expander
, TRUE
);
437 gtk_tree_model_get_value(GTK_TREE_MODEL(dialog
->model
),
438 &iter
, COLUMN_DATA
, &val
);
440 xfer
= g_value_get_pointer(&val
);
442 update_detailed_info(dialog
, xfer
);
444 dialog
->selected_xfer
= xfer
;
447 gtk_expander_set_expanded(GTK_EXPANDER(dialog
->expander
),
450 gtk_widget_set_sensitive(dialog
->expander
, FALSE
);
452 dialog
->selected_xfer
= NULL
;
455 update_buttons(dialog
, xfer
);
459 open_button_cb(GtkButton
*button
, PidginXferDialog
*dialog
)
462 /* If using Win32... */
464 wchar_t *wc_filename
= g_utf8_to_utf16(
465 purple_xfer_get_local_filename(
466 dialog
->selected_xfer
),
467 -1, NULL
, NULL
, NULL
);
469 code
= (int) ShellExecuteW(NULL
, NULL
, wc_filename
, NULL
, NULL
,
474 if (code
== SE_ERR_ASSOCINCOMPLETE
|| code
== SE_ERR_NOASSOC
)
476 purple_notify_error(dialog
, NULL
,
477 _("There is no application configured to open this type of file."),
482 purple_notify_error(dialog
, NULL
,
483 _("An error occurred while opening the file."), NULL
, NULL
);
484 purple_debug_warning("xfer", "filename: %s; code: %d\n",
485 purple_xfer_get_local_filename(dialog
->selected_xfer
), code
);
488 const char *filename
= purple_xfer_get_local_filename(dialog
->selected_xfer
);
489 char *command
= NULL
;
491 GError
*error
= NULL
;
493 if (purple_running_gnome())
495 char *escaped
= g_shell_quote(filename
);
496 command
= g_strdup_printf("gnome-open %s", escaped
);
499 else if (purple_running_kde())
501 char *escaped
= g_shell_quote(filename
);
503 if (purple_str_has_suffix(filename
, ".desktop"))
504 command
= g_strdup_printf("kfmclient openURL %s 'text/plain'", escaped
);
506 command
= g_strdup_printf("kfmclient openURL %s", escaped
);
511 gchar
*uri
= g_strdup_printf("file://%s", filename
);
512 purple_notify_uri(NULL
, uri
);
517 if (purple_program_is_valid(command
))
520 if (!g_spawn_command_line_sync(command
, NULL
, NULL
, &exit_status
, &error
))
522 tmp
= g_strdup_printf(_("Error launching %s: %s"),
523 purple_xfer_get_local_filename(dialog
->selected_xfer
),
525 purple_notify_error(dialog
, NULL
, _("Unable to open file."), tmp
, NULL
);
529 if (exit_status
!= 0)
531 char *primary
= g_strdup_printf(_("Error running %s"), command
);
532 char *secondary
= g_strdup_printf(_("Process returned error code %d"),
534 purple_notify_error(dialog
, NULL
, primary
, secondary
, NULL
);
542 remove_button_cb(GtkButton
*button
, PidginXferDialog
*dialog
)
544 pidgin_xfer_dialog_remove_xfer(dialog
, dialog
->selected_xfer
);
548 stop_button_cb(GtkButton
*button
, PidginXferDialog
*dialog
)
550 purple_xfer_cancel_local(dialog
->selected_xfer
);
554 close_button_cb(GtkButton
*button
, PidginXferDialog
*dialog
)
556 pidgin_xfer_dialog_hide(dialog
);
560 /**************************************************************************
561 * Dialog Building Functions
562 **************************************************************************/
564 setup_tree(PidginXferDialog
*dialog
)
568 GtkCellRenderer
*renderer
;
569 GtkTreeViewColumn
*column
;
570 GtkTreeSelection
*selection
;
572 /* Build the tree model */
573 /* Transfer type, Progress Bar, Filename, Size, Remaining */
574 model
= gtk_list_store_new(NUM_COLUMNS
, GDK_TYPE_PIXBUF
, G_TYPE_INT
,
575 G_TYPE_STRING
, G_TYPE_STRING
, G_TYPE_STRING
,
577 dialog
->model
= model
;
579 /* Create the treeview */
580 dialog
->tree
= tree
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(model
));
581 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree
), TRUE
);
582 selection
= gtk_tree_view_get_selection(GTK_TREE_VIEW(tree
));
583 /* gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); */
585 gtk_widget_show(tree
);
587 g_signal_connect(G_OBJECT(selection
), "changed",
588 G_CALLBACK(selection_changed_cb
), dialog
);
590 g_object_unref(G_OBJECT(model
));
595 /* Transfer Type column */
596 renderer
= gtk_cell_renderer_pixbuf_new();
597 column
= gtk_tree_view_column_new_with_attributes(NULL
, renderer
,
598 "pixbuf", COLUMN_STATUS
, NULL
);
599 gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column
),
600 GTK_TREE_VIEW_COLUMN_FIXED
);
601 gtk_tree_view_column_set_fixed_width(GTK_TREE_VIEW_COLUMN(column
), 25);
602 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column
), TRUE
);
603 gtk_tree_view_append_column(GTK_TREE_VIEW(tree
), column
);
605 /* Progress bar column */
606 renderer
= gtk_cell_renderer_progress_new();
607 column
= gtk_tree_view_column_new_with_attributes(_("Progress"), renderer
,
608 "value", COLUMN_PROGRESS
, NULL
);
609 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column
), TRUE
);
610 gtk_tree_view_append_column(GTK_TREE_VIEW(tree
), column
);
612 /* Filename column */
613 renderer
= gtk_cell_renderer_text_new();
614 column
= gtk_tree_view_column_new_with_attributes(_("Filename"), renderer
,
615 "text", COLUMN_FILENAME
, NULL
);
616 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column
), TRUE
);
617 gtk_tree_view_append_column(GTK_TREE_VIEW(tree
), column
);
619 /* File Size column */
620 renderer
= gtk_cell_renderer_text_new();
621 column
= gtk_tree_view_column_new_with_attributes(_("Size"), renderer
,
622 "text", COLUMN_SIZE
, NULL
);
623 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column
), TRUE
);
624 gtk_tree_view_append_column(GTK_TREE_VIEW(tree
), column
);
626 /* Bytes Remaining column */
627 renderer
= gtk_cell_renderer_text_new();
628 column
= gtk_tree_view_column_new_with_attributes(_("Remaining"),
629 renderer
, "text", COLUMN_REMAINING
, NULL
);
630 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column
), TRUE
);
631 gtk_tree_view_append_column(GTK_TREE_VIEW(tree
), column
);
633 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(tree
));
635 gtk_widget_show(tree
);
641 make_info_grid(PidginXferDialog
*dialog
)
649 GtkWidget
**desc_label
;
650 GtkWidget
**val_label
;
655 { &dialog
->local_user_desc_label
, &dialog
->local_user_label
, NULL
},
656 { &dialog
->remote_user_desc_label
, &dialog
->remote_user_label
, NULL
},
657 { &label
, &dialog
->protocol_label
, _("Protocol:") },
658 { &label
, &dialog
->filename_label
, _("Filename:") },
659 { &label
, &dialog
->localfile_label
, _("Local File:") },
660 { &label
, &dialog
->status_label
, _("Status:") },
661 { &label
, &dialog
->speed_label
, _("Speed:") },
662 { &label
, &dialog
->time_elapsed_label
, _("Time Elapsed:") },
663 { &label
, &dialog
->time_remaining_label
, _("Time Remaining:") }
666 /* Setup the initial grid */
667 dialog
->grid
= grid
= gtk_grid_new();
668 gtk_grid_set_row_spacing(GTK_GRID(grid
), PIDGIN_HIG_BOX_SPACE
);
669 gtk_grid_set_column_spacing(GTK_GRID(grid
), PIDGIN_HIG_BOX_SPACE
);
671 /* Setup the labels */
672 for (i
= 0; i
< G_N_ELEMENTS(labels
); i
++) {
676 g_snprintf(buf
, sizeof(buf
), "<b>%s</b>",
677 labels
[i
].desc
!= NULL
? labels
[i
].desc
: "");
679 *labels
[i
].desc_label
= label
= gtk_label_new(NULL
);
680 gtk_label_set_markup(GTK_LABEL(label
), buf
);
681 gtk_label_set_justify(GTK_LABEL(label
), GTK_JUSTIFY_RIGHT
);
682 gtk_label_set_xalign(GTK_LABEL(label
), 0);
683 gtk_grid_attach(GTK_GRID(grid
), label
, 0, i
, 1, 1);
684 gtk_widget_set_valign(label
, GTK_ALIGN_CENTER
);
686 gtk_widget_show(label
);
688 *labels
[i
].val_label
= label
= gtk_label_new(NULL
);
689 gtk_label_set_xalign(GTK_LABEL(label
), 0);
690 gtk_grid_attach(GTK_GRID(grid
), label
, 1, i
, 1, 1);
691 gtk_widget_set_hexpand(label
, TRUE
);
692 gtk_widget_set_valign(label
, GTK_ALIGN_CENTER
);
694 gtk_widget_show(label
);
697 /* Setup the progress bar */
698 dialog
->progress
= gtk_progress_bar_new();
699 gtk_grid_attach(GTK_GRID(grid
), dialog
->progress
,
700 0, G_N_ELEMENTS(labels
), 2, 1);
702 gtk_widget_show(dialog
->progress
);
708 pidgin_xfer_dialog_new(void)
710 PidginXferDialog
*dialog
;
714 #if !GTK_CHECK_VERSION(3,14,0)
715 GtkWidget
*alignment
;
721 dialog
= g_new0(PidginXferDialog
, 1);
723 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/filetransfer/keep_open");
725 purple_prefs_get_bool(PIDGIN_PREFS_ROOT
"/filetransfer/clear_finished");
727 /* Create the window. */
728 dialog
->window
= window
= pidgin_create_dialog(_("File Transfers"), 0, "file transfer", TRUE
);
729 gtk_window_set_default_size(GTK_WINDOW(window
), 450, 250);
731 g_signal_connect(G_OBJECT(window
), "delete_event",
732 G_CALLBACK(delete_win_cb
), dialog
);
734 /* Create the main vbox for top half of the window. */
735 vbox
= pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window
), FALSE
, PIDGIN_HIG_BORDER
);
737 /* Setup the listbox */
738 gtk_box_pack_start(GTK_BOX(vbox
),
739 pidgin_make_scrollable(setup_tree(dialog
), GTK_POLICY_AUTOMATIC
, GTK_POLICY_AUTOMATIC
, GTK_SHADOW_IN
, -1, 140),
742 /* "Close this window when all transfers finish" */
743 checkbox
= gtk_check_button_new_with_mnemonic(
744 _("Close this window when all transfers _finish"));
745 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox
),
747 g_signal_connect(G_OBJECT(checkbox
), "toggled",
748 G_CALLBACK(toggle_keep_open_cb
), dialog
);
749 gtk_box_pack_start(GTK_BOX(vbox
), checkbox
, FALSE
, FALSE
, 0);
750 gtk_widget_show(checkbox
);
752 /* "Clear finished transfers" */
753 checkbox
= gtk_check_button_new_with_mnemonic(
754 _("C_lear finished transfers"));
755 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox
),
757 g_signal_connect(G_OBJECT(checkbox
), "toggled",
758 G_CALLBACK(toggle_clear_finished_cb
), dialog
);
759 gtk_box_pack_start(GTK_BOX(vbox
), checkbox
, FALSE
, FALSE
, 0);
760 gtk_widget_show(checkbox
);
762 /* "Download Details" arrow */
763 expander
= gtk_expander_new_with_mnemonic(_("File transfer _details"));
764 dialog
->expander
= expander
;
765 gtk_box_pack_start(GTK_BOX(vbox
), expander
, FALSE
, FALSE
, 0);
766 gtk_widget_show(expander
);
768 gtk_widget_set_sensitive(expander
, FALSE
);
770 #if GTK_CHECK_VERSION(3,14,0)
771 /* The grid of information. */
772 grid
= make_info_grid(dialog
);
773 gtk_container_add(GTK_CONTAINER(expander
), grid
);
774 gtk_widget_show(grid
);
776 /* Small indent make grid fall under GtkExpander's label */
777 gtk_widget_set_margin_start(grid
, 20);
779 /* Small indent make grid fall under GtkExpander's label */
780 alignment
= gtk_alignment_new(1, 0, 1, 1);
781 gtk_alignment_set_padding(GTK_ALIGNMENT(alignment
), 0, 0, 20, 0);
782 gtk_container_add(GTK_CONTAINER(expander
), alignment
);
783 gtk_widget_show(alignment
);
785 /* The grid of information. */
786 grid
= make_info_grid(dialog
);
787 gtk_container_add(GTK_CONTAINER(alignment
), grid
);
788 gtk_widget_show(grid
);
791 bbox
= pidgin_dialog_get_action_area(GTK_DIALOG(window
));
793 #define ADD_BUTTON(b, label, callback, callbackdata) do { \
794 GtkWidget *button = gtk_button_new_from_stock(label); \
795 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); \
796 g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata); \
797 gtk_widget_show(button); \
802 ADD_BUTTON(dialog
->open_button
, GTK_STOCK_OPEN
, G_CALLBACK(open_button_cb
), dialog
);
803 gtk_widget_set_sensitive(dialog
->open_button
, FALSE
);
806 ADD_BUTTON(dialog
->remove_button
, GTK_STOCK_REMOVE
, G_CALLBACK(remove_button_cb
), dialog
);
807 gtk_widget_hide(dialog
->remove_button
);
810 ADD_BUTTON(dialog
->stop_button
, GTK_STOCK_STOP
, G_CALLBACK(stop_button_cb
), dialog
);
811 gtk_widget_set_sensitive(dialog
->stop_button
, FALSE
);
814 ADD_BUTTON(dialog
->close_button
, GTK_STOCK_CLOSE
, G_CALLBACK(close_button_cb
), dialog
);
819 g_signal_connect(G_OBJECT(dialog
->window
), "show",
820 G_CALLBACK(winpidgin_ensure_onscreen
), dialog
->window
);
827 pidgin_xfer_dialog_destroy(PidginXferDialog
*dialog
)
829 g_return_if_fail(dialog
!= NULL
);
831 purple_notify_close_with_handle(dialog
);
833 gtk_widget_destroy(dialog
->window
);
839 pidgin_xfer_dialog_show(PidginXferDialog
*dialog
)
841 PidginXferDialog
*tmp
;
843 if (dialog
== NULL
) {
844 tmp
= pidgin_get_xfer_dialog();
847 tmp
= pidgin_xfer_dialog_new();
848 pidgin_set_xfer_dialog(tmp
);
851 gtk_widget_show(tmp
->window
);
853 gtk_window_present(GTK_WINDOW(dialog
->window
));
858 pidgin_xfer_dialog_hide(PidginXferDialog
*dialog
)
860 g_return_if_fail(dialog
!= NULL
);
862 purple_notify_close_with_handle(dialog
);
864 gtk_widget_hide(dialog
->window
);
868 pidgin_xfer_dialog_add_xfer(PidginXferDialog
*dialog
, PurpleXfer
*xfer
)
870 PidginXferUiData
*data
;
873 char *size_str
, *remaining_str
;
874 char *lfilename
, *utf8
;
876 g_return_if_fail(dialog
!= NULL
);
877 g_return_if_fail(xfer
!= NULL
);
881 data
= purple_xfer_get_ui_data(xfer
);
882 data
->in_list
= TRUE
;
884 pidgin_xfer_dialog_show(dialog
);
886 data
->last_updated_time
= 0;
888 type
= purple_xfer_get_xfer_type(xfer
);
890 size_str
= purple_str_size_to_units(purple_xfer_get_size(xfer
));
891 remaining_str
= purple_str_size_to_units(purple_xfer_get_bytes_remaining(xfer
));
893 pixbuf
= gtk_widget_render_icon(dialog
->window
,
894 (type
== PURPLE_XFER_TYPE_RECEIVE
895 ? PIDGIN_STOCK_DOWNLOAD
896 : PIDGIN_STOCK_UPLOAD
),
897 GTK_ICON_SIZE_MENU
, NULL
);
899 gtk_list_store_append(dialog
->model
, &data
->iter
);
900 lfilename
= g_path_get_basename(purple_xfer_get_local_filename(xfer
));
901 utf8
= g_filename_to_utf8(lfilename
, -1, NULL
, NULL
, NULL
);
904 gtk_list_store_set(dialog
->model
, &data
->iter
,
905 COLUMN_STATUS
, pixbuf
,
907 COLUMN_FILENAME
, (type
== PURPLE_XFER_TYPE_RECEIVE
)
908 ? purple_xfer_get_filename(xfer
)
910 COLUMN_SIZE
, size_str
,
911 COLUMN_REMAINING
, _("Waiting for transfer to begin"),
916 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(dialog
->tree
));
918 g_object_unref(pixbuf
);
921 g_free(remaining_str
);
923 dialog
->num_transfers
++;
925 ensure_row_selected(dialog
);
926 update_title_progress(dialog
);
930 pidgin_xfer_dialog_remove_xfer(PidginXferDialog
*dialog
,
933 PidginXferUiData
*data
;
935 g_return_if_fail(dialog
!= NULL
);
936 g_return_if_fail(xfer
!= NULL
);
938 data
= purple_xfer_get_ui_data(xfer
);
946 data
->in_list
= FALSE
;
948 gtk_list_store_remove(GTK_LIST_STORE(dialog
->model
), &data
->iter
);
950 dialog
->num_transfers
--;
952 ensure_row_selected(dialog
);
954 update_title_progress(dialog
);
955 g_object_unref(xfer
);
959 pidgin_xfer_dialog_cancel_xfer(PidginXferDialog
*dialog
,
962 PidginXferUiData
*data
;
966 g_return_if_fail(dialog
!= NULL
);
967 g_return_if_fail(xfer
!= NULL
);
969 data
= purple_xfer_get_ui_data(xfer
);
977 if ((purple_xfer_get_status(xfer
) == PURPLE_XFER_STATUS_CANCEL_LOCAL
) && (dialog
->auto_clear
)) {
978 pidgin_xfer_dialog_remove_xfer(dialog
, xfer
);
982 data
= purple_xfer_get_ui_data(xfer
);
984 update_detailed_info(dialog
, xfer
);
985 update_title_progress(dialog
);
987 pixbuf
= gtk_widget_render_icon(dialog
->window
,
988 PIDGIN_STOCK_FILE_CANCELLED
,
989 GTK_ICON_SIZE_MENU
, NULL
);
991 if (purple_xfer_is_cancelled(xfer
))
992 status
= _("Cancelled");
994 status
= _("Failed");
996 gtk_list_store_set(dialog
->model
, &data
->iter
,
997 COLUMN_STATUS
, pixbuf
,
998 COLUMN_REMAINING
, status
,
1001 g_object_unref(pixbuf
);
1003 update_buttons(dialog
, xfer
);
1007 pidgin_xfer_dialog_update_xfer(PidginXferDialog
*dialog
,
1010 PidginXferUiData
*data
;
1011 char *size_str
, *remaining_str
;
1012 time_t current_time
;
1016 g_return_if_fail(dialog
!= NULL
);
1017 g_return_if_fail(xfer
!= NULL
);
1019 if ((data
= purple_xfer_get_ui_data(xfer
)) == NULL
)
1022 if (data
->in_list
== FALSE
)
1025 current_time
= time(NULL
);
1026 if (((current_time
- data
->last_updated_time
) == 0) &&
1027 (!purple_xfer_is_completed(xfer
)))
1029 /* Don't update the window more than once per second */
1032 data
->last_updated_time
= current_time
;
1034 size_str
= purple_str_size_to_units(purple_xfer_get_size(xfer
));
1035 remaining_str
= purple_str_size_to_units(purple_xfer_get_bytes_remaining(xfer
));
1037 gtk_list_store_set(xfer_dialog
->model
, &data
->iter
,
1038 COLUMN_PROGRESS
, (gint
)(purple_xfer_get_progress(xfer
) * 100),
1039 COLUMN_SIZE
, size_str
,
1040 COLUMN_REMAINING
, remaining_str
,
1044 g_free(remaining_str
);
1046 if (purple_xfer_is_completed(xfer
))
1050 pixbuf
= gtk_widget_render_icon(dialog
->window
,
1051 PIDGIN_STOCK_FILE_DONE
,
1052 GTK_ICON_SIZE_MENU
, NULL
);
1054 gtk_list_store_set(GTK_LIST_STORE(xfer_dialog
->model
), &data
->iter
,
1055 COLUMN_STATUS
, pixbuf
,
1056 COLUMN_REMAINING
, _("Finished"),
1059 g_object_unref(pixbuf
);
1062 update_title_progress(dialog
);
1063 if (xfer
== dialog
->selected_xfer
)
1064 update_detailed_info(xfer_dialog
, xfer
);
1066 if (purple_xfer_is_completed(xfer
) && dialog
->auto_clear
)
1067 pidgin_xfer_dialog_remove_xfer(dialog
, xfer
);
1069 update_buttons(dialog
, xfer
);
1072 * If all transfers are finished, and the pref is set, then
1073 * close the dialog. Otherwise just exit this function.
1075 if (dialog
->keep_open
)
1078 valid
= gtk_tree_model_get_iter_first(GTK_TREE_MODEL(dialog
->model
), &iter
);
1085 gtk_tree_model_get_value(GTK_TREE_MODEL(dialog
->model
),
1086 &iter
, COLUMN_DATA
, &val
);
1088 next
= g_value_get_pointer(&val
);
1089 if (!purple_xfer_is_completed(next
))
1092 valid
= gtk_tree_model_iter_next(GTK_TREE_MODEL(dialog
->model
), &iter
);
1095 /* If we got to this point then we know everything is finished */
1096 pidgin_xfer_dialog_hide(dialog
);
1099 /**************************************************************************
1100 * PidginXferDialog GBoxed code
1101 **************************************************************************/
1102 static PidginXferDialog
*
1103 pidgin_xfer_dialog_ref(PidginXferDialog
*dialog
)
1105 g_return_val_if_fail(dialog
!= NULL
, NULL
);
1107 dialog
->box_count
++;
1113 pidgin_xfer_dialog_unref(PidginXferDialog
*dialog
)
1115 g_return_if_fail(dialog
!= NULL
);
1116 g_return_if_fail(dialog
->box_count
>= 0);
1118 if (!dialog
->box_count
--)
1119 pidgin_xfer_dialog_destroy(dialog
);
1123 pidgin_xfer_dialog_get_type(void)
1125 static GType type
= 0;
1128 type
= g_boxed_type_register_static("PidginXferDialog",
1129 (GBoxedCopyFunc
)pidgin_xfer_dialog_ref
,
1130 (GBoxedFreeFunc
)pidgin_xfer_dialog_unref
);
1136 /**************************************************************************
1137 * File Transfer UI Ops
1138 **************************************************************************/
1140 pidgin_xfer_new_xfer(PurpleXfer
*xfer
)
1142 PidginXferUiData
*data
;
1144 /* This is where we're setting xfer's "ui_data" for the first time. */
1145 data
= g_new0(PidginXferUiData
, 1);
1146 purple_xfer_set_ui_data(xfer
, data
);
1150 pidgin_xfer_destroy(PurpleXfer
*xfer
)
1152 PidginXferUiData
*data
;
1154 data
= purple_xfer_get_ui_data(xfer
);
1158 purple_xfer_set_ui_data(xfer
, NULL
);
1163 pidgin_xfer_add_xfer(PurpleXfer
*xfer
)
1165 if (xfer_dialog
== NULL
)
1166 xfer_dialog
= pidgin_xfer_dialog_new();
1168 pidgin_xfer_dialog_add_xfer(xfer_dialog
, xfer
);
1172 pidgin_xfer_update_progress(PurpleXfer
*xfer
, double percent
)
1174 pidgin_xfer_dialog_update_xfer(xfer_dialog
, xfer
);
1178 pidgin_xfer_cancel_local(PurpleXfer
*xfer
)
1181 pidgin_xfer_dialog_cancel_xfer(xfer_dialog
, xfer
);
1185 pidgin_xfer_cancel_remote(PurpleXfer
*xfer
)
1188 pidgin_xfer_dialog_cancel_xfer(xfer_dialog
, xfer
);
1192 pidgin_xfer_add_thumbnail(PurpleXfer
*xfer
, const gchar
*formats
)
1194 purple_debug_info("xfer", "creating thumbnail for transfer\n");
1196 if (purple_xfer_get_size(xfer
) <= PIDGIN_XFER_MAX_SIZE_IMAGE_THUMBNAIL
) {
1197 GdkPixbuf
*thumbnail
=
1198 pidgin_pixbuf_new_from_file_at_size(
1199 purple_xfer_get_local_filename(xfer
), 128, 128);
1202 gchar
**formats_split
= g_strsplit(formats
, ",", 0);
1203 gchar
*buffer
= NULL
;
1205 char *option_keys
[2] = {NULL
, NULL
};
1206 char *option_values
[2] = {NULL
, NULL
};
1208 gchar
*format
= NULL
;
1210 for (i
= 0; formats_split
[i
]; i
++) {
1211 if (purple_strequal(formats_split
[i
], "jpeg")) {
1212 purple_debug_info("xfer", "creating JPEG thumbnail\n");
1213 option_keys
[0] = "quality";
1214 option_values
[0] = "90";
1217 } else if (purple_strequal(formats_split
[i
], "png")) {
1218 purple_debug_info("xfer", "creating PNG thumbnail\n");
1219 option_keys
[0] = "compression";
1220 option_values
[0] = "9";
1226 /* Try the first format given by the protocol without options */
1227 if (format
== NULL
) {
1228 purple_debug_info("xfer",
1229 "creating thumbnail of format %s as demanded by protocol\n",
1231 format
= formats_split
[0];
1234 gdk_pixbuf_save_to_bufferv(thumbnail
, &buffer
, &size
, format
,
1235 option_keys
, option_values
, NULL
);
1238 gchar
*mimetype
= g_strdup_printf("image/%s", format
);
1239 purple_debug_info("xfer",
1240 "created thumbnail of %" G_GSIZE_FORMAT
" bytes\n",
1242 purple_xfer_set_thumbnail(xfer
, buffer
, size
, mimetype
);
1246 g_object_unref(thumbnail
);
1247 g_strfreev(formats_split
);
1252 static PurpleXferUiOps ops
=
1254 pidgin_xfer_new_xfer
,
1255 pidgin_xfer_destroy
,
1256 pidgin_xfer_add_xfer
,
1257 pidgin_xfer_update_progress
,
1258 pidgin_xfer_cancel_local
,
1259 pidgin_xfer_cancel_remote
,
1263 pidgin_xfer_add_thumbnail
1266 /**************************************************************************
1267 * GTK+ File Transfer API
1268 **************************************************************************/
1270 pidgin_xfers_init(void)
1272 purple_prefs_add_none(PIDGIN_PREFS_ROOT
"/filetransfer");
1273 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/filetransfer/clear_finished", TRUE
);
1274 purple_prefs_add_bool(PIDGIN_PREFS_ROOT
"/filetransfer/keep_open", FALSE
);
1278 pidgin_xfers_uninit(void)
1280 if (xfer_dialog
!= NULL
)
1281 pidgin_xfer_dialog_destroy(xfer_dialog
);
1285 pidgin_set_xfer_dialog(PidginXferDialog
*dialog
)
1287 xfer_dialog
= dialog
;
1291 pidgin_get_xfer_dialog(void)
1297 pidgin_xfers_get_ui_ops(void)