view src/gtkft.c @ 4514:7521e29658bc

[gaim-migrate @ 4792] Of course, file transfer wasn't really gone.. I'm trying my hardest to bring on the end of the world (see the roadmap at http://gaim.sf.net/roadmap.png). File transfer is being rewritten. This isn't the finished implementation, but it's enough to let us get the prpls working. There is now a file transfer dialog, which will appear when you get a new transfer request or when you go to Tools -> File Transfers. This is of course core/UI split. I'll also be working on documentation on how to write FT support in a prpl. Oh, and I'll get resumes and transfer batches done when school isn't breathing down my back. Only DCC receive in IRC currently works. Sorry. We'll get the other prpls working soon, as well as send. committer: Tailor Script <tailor@pidgin.im>
author Christian Hammond <chipx86@chipx86.com>
date Tue, 04 Feb 2003 06:57:35 +0000
parents
children a2b2cce63fb8
line wrap: on
line source

/**
 * @file gtkft.c The GTK+ file transfer UI
 *
 * gaim
 *
 * Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
#include "gaim.h"
#include "prpl.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include "gtkcellrendererprogress.h"

struct gaim_gtkxfer_dialog
{
	GtkWidget *window;
	GtkWidget *tree;
	GtkListStore *model;
};

struct gaim_gtkxfer_ui_data
{
	GtkWidget *filesel;
	GtkTreeIter iter;
	time_t start_time;

	char *name;
};

static struct gaim_gtkxfer_dialog *xfer_dialog = NULL;

enum
{
	COLUMN_STATUS = 0,
	COLUMN_USER,
	COLUMN_FILENAME,
	COLUMN_PROGRESS,
	COLUMN_SIZE,
	COLUMN_REMAINING,
	COLUMN_ESTIMATE,
	COLUMN_SPEED,
	NUM_COLUMNS
};

static gint
delete_win_cb(GtkWidget *w, GdkEventAny *e, gpointer d)
{
	gaim_gtkxfer_dialog_hide();

	return TRUE;
}

static void
close_win_cb(GtkButton *button, gpointer user_data)
{
	gaim_gtkxfer_dialog_hide();
}

static struct gaim_gtkxfer_dialog *
build_xfer_dialog(void)
{
	struct gaim_gtkxfer_dialog *dialog;
	GtkWidget *window;
	GtkWidget *vbox;
	GtkWidget *hbox;
	GtkWidget *frame;
	GtkWidget *sw;
	GtkWidget *sep;
	GtkWidget *button;
	GtkWidget *tree;
	GtkTreeViewColumn *column;
	GtkListStore *model;
	GtkCellRenderer *renderer;
	GtkSizeGroup *sg;

	dialog = g_malloc0(sizeof(struct gaim_gtkxfer_dialog));

	/* Create the window. */
	dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_role(GTK_WINDOW(window), "file transfer");
	gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);
	gtk_window_set_default_size(GTK_WINDOW(window), 550, 250);
	gtk_container_border_width(GTK_CONTAINER(window), 0);
	gtk_widget_realize(window);

	g_signal_connect(G_OBJECT(window), "delete_event",
					 G_CALLBACK(delete_win_cb), dialog);

	/* Create the parent vbox for everything. */
	vbox = gtk_vbox_new(FALSE, 6);
	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
	gtk_container_add(GTK_CONTAINER(window), vbox);
	gtk_widget_show(vbox);

	/* Create the scrolled window. */
	sw = gtk_scrolled_window_new(0, 0);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
								   GTK_POLICY_AUTOMATIC,
								   GTK_POLICY_ALWAYS);
	gtk_widget_show(sw);

	/* Build the tree model */
	/* Transfer type, User, Filename, Progress Bar, Size, Remaining */
	dialog->model = model = gtk_list_store_new(NUM_COLUMNS,
											   GDK_TYPE_PIXBUF, G_TYPE_STRING,
											   G_TYPE_STRING, G_TYPE_DOUBLE,
											   G_TYPE_LONG, G_TYPE_LONG,
											   G_TYPE_STRING, G_TYPE_STRING);

	/* Create the treeview */
	dialog->tree = tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree), TRUE);
	gtk_tree_selection_set_mode(
			gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)),
			GTK_SELECTION_MULTIPLE);
	gtk_widget_show(tree);

	g_object_unref(G_OBJECT(model));


	/* Columns */

	/* Transfer Type column */
	renderer = gtk_cell_renderer_pixbuf_new();
	column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
				"pixbuf", COLUMN_STATUS, NULL);
	gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
									GTK_TREE_VIEW_COLUMN_FIXED);
	gtk_tree_view_column_set_fixed_width(GTK_TREE_VIEW_COLUMN(column), 25);
	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

	/* User column */
	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes(_("User"), renderer,
				"text", COLUMN_USER, NULL);
	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

	/* Filename column */
	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes(_("Filename"), renderer,
				"text", COLUMN_FILENAME, NULL);
	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

	/* Progress bar column */
	renderer = gtk_cell_renderer_progress_new();
	column = gtk_tree_view_column_new_with_attributes(_("Progress"), renderer,
				"percentage", COLUMN_PROGRESS, NULL);
	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

	/* File Size column */
	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes(_("Size"), renderer,
				"text", COLUMN_SIZE, NULL);
	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

	/* Bytes Remaining column */
	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes(_("Remaining"),
				renderer, "text", COLUMN_REMAINING, NULL);
	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

	gtk_tree_view_columns_autosize(GTK_TREE_VIEW(tree));

	/* ETA column */
	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes(_("ETA"),
				renderer, "text", COLUMN_ESTIMATE, NULL);
	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

	/* Speed column */
	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes(_("Speed"),
				renderer, "text", COLUMN_SPEED, NULL);
	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

	gtk_tree_view_columns_autosize(GTK_TREE_VIEW(tree));

	gtk_container_add(GTK_CONTAINER(sw), tree);
	gtk_widget_show(tree);

	/* Create the outer frame for the scrolled window. */
	frame = gtk_frame_new(NULL),
	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
	gtk_container_add(GTK_CONTAINER(frame), sw);
	gtk_widget_show(frame);

	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);

	/* Separator */
	sep = gtk_hseparator_new();
	gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
	gtk_widget_show(sep);

	/* Now the hbox for the buttons */
	hbox = gtk_hbox_new(FALSE, 6);
	gtk_container_set_border_width(GTK_CONTAINER(hbox), 6);
	gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);

	sg = gtk_size_group_new(GTK_SIZE_GROUP_BOTH);

	/* Close button */
	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
	g_signal_connect(G_OBJECT(button), "clicked",
					 G_CALLBACK(close_win_cb), NULL);
	gtk_size_group_add_widget(sg, button);
	gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
	gtk_widget_show(button);

#if 0
	/* Cancel button */
	button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
	gtk_size_group_add_widget(sg, button);
	gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
	gtk_widget_show(button);
#endif

	return dialog;
}

static void
gaim_gtkxfer_destroy(struct gaim_xfer *xfer)
{
	struct gaim_gtkxfer_ui_data *data;

	data = xfer->ui_data;

	if (data == NULL)
		return;
}

static gboolean
choose_file_close_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
	gaim_xfer_request_denied((struct gaim_xfer *)user_data);

	return FALSE;
}

static void
choose_file_cancel_cb(GtkButton *button, gpointer user_data)
{
	struct gaim_xfer *xfer = (struct gaim_xfer *)user_data;
	struct gaim_gtkxfer_ui_data *data;

	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;

	gaim_xfer_request_denied(xfer);

	gtk_widget_destroy(data->filesel);
	data->filesel = NULL;
}

static int
do_overwrite_cb(struct gaim_xfer *xfer)
{
	struct gaim_gtkxfer_ui_data *data;
	
	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;

	gaim_xfer_request_accepted(xfer, data->name);

	/*
	 * No, we don't want to free data->name. gaim_xfer_request_accepted
	 * will deal with it.
	 */
	data->name = NULL;

	return 0;
}

static int
dont_overwrite_cb(struct gaim_xfer *xfer)
{
	struct gaim_gtkxfer_ui_data *data;
	
	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;

	g_free(data->name);
	data->name = NULL;

	gaim_xfer_request_denied(xfer);

	return 0;
}

static void
choose_file_ok_cb(GtkButton *button, gpointer user_data)
{
	struct gaim_xfer *xfer;
	struct gaim_gtkxfer_ui_data *data;
	struct stat st;
	const char *name;

	xfer = (struct gaim_xfer *)user_data;
	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;

	name = gtk_file_selection_get_filename(GTK_FILE_SELECTION(data->filesel));

	if (stat(name, &st) == 0) {
		if (S_ISDIR(st.st_mode)) {
			/* XXX */
			gaim_xfer_request_denied(xfer);
		}
		else if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) {
			data->name = g_strdup(name);

			do_ask_dialog(_("That file already exists. "
							"Would you like to overwrite it?"),
						  NULL, xfer,
						  _("Yes"), do_overwrite_cb,
						  _("No"), dont_overwrite_cb,
						  NULL, FALSE);
		}
		else {
			gaim_xfer_request_accepted(xfer, g_strdup(name));
		}
	}
	else {
		/* File not found. */
		if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) {
			gaim_xfer_request_accepted(xfer, g_strdup(name));
		}
		else {
			do_error_dialog(_("That file does not exist."),
							NULL, GAIM_ERROR);

			gaim_xfer_request_denied(xfer);
		}
	}

	gtk_widget_destroy(data->filesel);
	data->filesel = NULL;
}

static int
choose_file(struct gaim_xfer *xfer)
{
	char *cur_dir, *init_str;
	struct gaim_gtkxfer_ui_data *data;

	cur_dir = g_get_current_dir();

	/* This is where we're setting xfer->ui_data for the first time. */
	data = g_malloc0(sizeof(struct gaim_gtkxfer_ui_data));
	xfer->ui_data = data;

	if (gaim_xfer_get_type(xfer) == GAIM_XFER_SEND)
		data->filesel = gtk_file_selection_new(_("Gaim - Open..."));
	else
		data->filesel = gtk_file_selection_new(_("Gaim - Save As..."));

	if (gaim_xfer_get_filename(xfer) == NULL)
		init_str = g_strdup(cur_dir);
	else
		init_str = g_build_filename(cur_dir, gaim_xfer_get_filename(xfer),
									NULL);

	g_free(cur_dir);

	gtk_file_selection_set_filename(GTK_FILE_SELECTION(data->filesel),
									init_str);

	g_free(init_str);

	g_signal_connect(G_OBJECT(data->filesel), "delete_event",
					 G_CALLBACK(choose_file_close_cb), xfer);
	g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(data->filesel)->cancel_button),
					 "clicked",
					 G_CALLBACK(choose_file_cancel_cb), xfer);
	g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(data->filesel)->ok_button),
					 "clicked",
					 G_CALLBACK(choose_file_ok_cb), xfer);

	gtk_widget_show(data->filesel);

	return 0;
}

static int
cancel_recv_cb(struct gaim_xfer *xfer)
{
	gaim_xfer_request_denied(xfer);

	return 0;
}

static void
gaim_gtkxfer_ask_recv(struct gaim_xfer *xfer)
{
	static const char *size_str[4] = { "bytes", "KB", "MB", "GB" };
	char *buf;
	char *size_buf;
	float size_mag;
	size_t size;
	int size_index = 0;

	size = gaim_xfer_get_size(xfer);
	size_mag = (float)size;

	while ((size_index < 4) && (size_mag > 1024)) {
		size_mag /= 1024;
		size_index++;
	}

	if (size == -1)
		size_buf = g_strdup_printf(_("Unknown size"));
	else
		size_buf = g_strdup_printf("%.3g %s", size_mag, size_str[size_index]);

	buf = g_strdup_printf(_("%s wants to send you %s (%s)"),
						  xfer->who, gaim_xfer_get_filename(xfer), size_buf);

	g_free(size_buf);

	do_ask_dialog(buf, NULL, xfer,
				  _("Accept"), choose_file,
				  _("Cancel"), cancel_recv_cb,
				  NULL, FALSE);

	g_free(buf);
}

static void
gaim_gtkxfer_request_file(struct gaim_xfer *xfer)
{
	if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE)
	{
		gaim_gtkxfer_ask_recv(xfer);
	}
	else
	{
		choose_file(xfer);
	}
}

static void
gaim_gtkxfer_ask_cancel(struct gaim_xfer *xfer)
{
}

static void
gaim_gtkxfer_add_xfer(struct gaim_xfer *xfer)
{
	struct gaim_gtkxfer_ui_data *data;
	GaimXferType type;
	GdkPixbuf *pixbuf;

	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;

	gaim_gtkxfer_dialog_show();

	data->start_time = time(NULL);

	type = gaim_xfer_get_type(xfer);

	pixbuf = gtk_widget_render_icon(xfer_dialog->window,
									(type == GAIM_XFER_RECEIVE
									 ? GAIM_STOCK_DOWNLOAD
									 : GAIM_STOCK_UPLOAD),
									GTK_ICON_SIZE_MENU, NULL);

	gtk_list_store_append(xfer_dialog->model, &data->iter);
	gtk_list_store_set(xfer_dialog->model, &data->iter,
					   COLUMN_STATUS, pixbuf,
					   COLUMN_USER, xfer->who,
					   COLUMN_FILENAME, gaim_xfer_get_filename(xfer),
					   COLUMN_PROGRESS, 0.0,
					   COLUMN_SIZE, gaim_xfer_get_size(xfer),
					   COLUMN_REMAINING, gaim_xfer_get_bytes_remaining(xfer),
					   COLUMN_ESTIMATE, NULL,
					   COLUMN_SPEED, NULL,
					   -1);

	gtk_tree_view_columns_autosize(GTK_TREE_VIEW(xfer_dialog->tree));

	g_object_unref(pixbuf);
}

static void
gaim_gtkxfer_update_progress(struct gaim_xfer *xfer, double percent)
{
	struct gaim_gtkxfer_ui_data *data;
	time_t now;
	char speed_buf[256];
	char estimate_buf[256];
	double kb_sent, kb_rem;
	double kbps = 0.0;
	time_t elapsed;
	int secs_remaining;

	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;

	now     = time(NULL);
	kb_rem  = gaim_xfer_get_bytes_remaining(xfer) / 1024.0;
	kb_sent = gaim_xfer_get_bytes_sent(xfer) / 1024.0;
	elapsed = (now - data->start_time);
	kbps    = (elapsed > 0 ? (kb_sent / elapsed) : 0);

	secs_remaining = (int)(kb_rem / kbps);

	if (secs_remaining <= 0) {
		GdkPixbuf *pixbuf = NULL;

		*speed_buf = '\0';
		strncpy(estimate_buf, _("Done."), sizeof(estimate_buf));

		pixbuf = gtk_widget_render_icon(xfer_dialog->window,
										GAIM_STOCK_FILE_DONE,
										GTK_ICON_SIZE_MENU, NULL);

		gtk_list_store_set(xfer_dialog->model, &data->iter,
						   COLUMN_STATUS, pixbuf,
						   -1);

		g_object_unref(pixbuf);
	}
	else {
		int h = secs_remaining / 3600;
		int m = (secs_remaining % 3600) / 60;
		int s = secs_remaining % 60;

		g_snprintf(estimate_buf, sizeof(estimate_buf),
				   _("%d:%02d:%02d"), h, m, s);
		g_snprintf(speed_buf, sizeof(speed_buf),
				   _("%.2f KB/s"), kbps);
	}

	gtk_list_store_set(xfer_dialog->model, &data->iter,
					   COLUMN_REMAINING, gaim_xfer_get_bytes_remaining(xfer),
					   COLUMN_PROGRESS, percent,
					   COLUMN_ESTIMATE, estimate_buf,
					   COLUMN_SPEED, speed_buf,
					   -1);
}

static void
gaim_gtkxfer_cancel(struct gaim_xfer *xfer)
{
	struct gaim_gtkxfer_ui_data *data;
	GdkPixbuf *pixbuf;

	data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;

	pixbuf = gtk_widget_render_icon(xfer_dialog->window,
									GAIM_STOCK_FILE_CANCELED,
									GTK_ICON_SIZE_MENU, NULL);

	gtk_list_store_set(xfer_dialog->model, &data->iter,
					   COLUMN_STATUS, pixbuf,
					   -1);

	g_object_unref(pixbuf);
}

struct gaim_xfer_ui_ops ops =
{
	gaim_gtkxfer_destroy,
	gaim_gtkxfer_request_file,
	gaim_gtkxfer_ask_cancel,
	gaim_gtkxfer_add_xfer,
	gaim_gtkxfer_update_progress,
	gaim_gtkxfer_cancel
};

void
gaim_gtkxfer_dialog_show(void)
{
	if (xfer_dialog == NULL)
		xfer_dialog = build_xfer_dialog();

	gtk_widget_show(xfer_dialog->window);
}

void
gaim_gtkxfer_dialog_hide(void)
{
	gtk_widget_hide(xfer_dialog->window);
}

struct gaim_xfer_ui_ops *
gaim_get_gtk_xfer_ui_ops(void)
{
	return &ops;
}