comparison 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
comparison
equal deleted inserted replaced
4513:adb0245e1dfc 4514:7521e29658bc
1 /**
2 * @file gtkft.c The GTK+ file transfer UI
3 *
4 * gaim
5 *
6 * Copyright (C) 2003, Christian Hammond <chipx86@gnupdate.org>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *
22 */
23 #include "gaim.h"
24 #include "prpl.h"
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include "gtkcellrendererprogress.h"
30
31 struct gaim_gtkxfer_dialog
32 {
33 GtkWidget *window;
34 GtkWidget *tree;
35 GtkListStore *model;
36 };
37
38 struct gaim_gtkxfer_ui_data
39 {
40 GtkWidget *filesel;
41 GtkTreeIter iter;
42 time_t start_time;
43
44 char *name;
45 };
46
47 static struct gaim_gtkxfer_dialog *xfer_dialog = NULL;
48
49 enum
50 {
51 COLUMN_STATUS = 0,
52 COLUMN_USER,
53 COLUMN_FILENAME,
54 COLUMN_PROGRESS,
55 COLUMN_SIZE,
56 COLUMN_REMAINING,
57 COLUMN_ESTIMATE,
58 COLUMN_SPEED,
59 NUM_COLUMNS
60 };
61
62 static gint
63 delete_win_cb(GtkWidget *w, GdkEventAny *e, gpointer d)
64 {
65 gaim_gtkxfer_dialog_hide();
66
67 return TRUE;
68 }
69
70 static void
71 close_win_cb(GtkButton *button, gpointer user_data)
72 {
73 gaim_gtkxfer_dialog_hide();
74 }
75
76 static struct gaim_gtkxfer_dialog *
77 build_xfer_dialog(void)
78 {
79 struct gaim_gtkxfer_dialog *dialog;
80 GtkWidget *window;
81 GtkWidget *vbox;
82 GtkWidget *hbox;
83 GtkWidget *frame;
84 GtkWidget *sw;
85 GtkWidget *sep;
86 GtkWidget *button;
87 GtkWidget *tree;
88 GtkTreeViewColumn *column;
89 GtkListStore *model;
90 GtkCellRenderer *renderer;
91 GtkSizeGroup *sg;
92
93 dialog = g_malloc0(sizeof(struct gaim_gtkxfer_dialog));
94
95 /* Create the window. */
96 dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
97 gtk_window_set_role(GTK_WINDOW(window), "file transfer");
98 gtk_window_set_policy(GTK_WINDOW(window), TRUE, TRUE, FALSE);
99 gtk_window_set_default_size(GTK_WINDOW(window), 550, 250);
100 gtk_container_border_width(GTK_CONTAINER(window), 0);
101 gtk_widget_realize(window);
102
103 g_signal_connect(G_OBJECT(window), "delete_event",
104 G_CALLBACK(delete_win_cb), dialog);
105
106 /* Create the parent vbox for everything. */
107 vbox = gtk_vbox_new(FALSE, 6);
108 gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
109 gtk_container_add(GTK_CONTAINER(window), vbox);
110 gtk_widget_show(vbox);
111
112 /* Create the scrolled window. */
113 sw = gtk_scrolled_window_new(0, 0);
114 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
115 GTK_POLICY_AUTOMATIC,
116 GTK_POLICY_ALWAYS);
117 gtk_widget_show(sw);
118
119 /* Build the tree model */
120 /* Transfer type, User, Filename, Progress Bar, Size, Remaining */
121 dialog->model = model = gtk_list_store_new(NUM_COLUMNS,
122 GDK_TYPE_PIXBUF, G_TYPE_STRING,
123 G_TYPE_STRING, G_TYPE_DOUBLE,
124 G_TYPE_LONG, G_TYPE_LONG,
125 G_TYPE_STRING, G_TYPE_STRING);
126
127 /* Create the treeview */
128 dialog->tree = tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
129 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree), TRUE);
130 gtk_tree_selection_set_mode(
131 gtk_tree_view_get_selection(GTK_TREE_VIEW(tree)),
132 GTK_SELECTION_MULTIPLE);
133 gtk_widget_show(tree);
134
135 g_object_unref(G_OBJECT(model));
136
137
138 /* Columns */
139
140 /* Transfer Type column */
141 renderer = gtk_cell_renderer_pixbuf_new();
142 column = gtk_tree_view_column_new_with_attributes(NULL, renderer,
143 "pixbuf", COLUMN_STATUS, NULL);
144 gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
145 GTK_TREE_VIEW_COLUMN_FIXED);
146 gtk_tree_view_column_set_fixed_width(GTK_TREE_VIEW_COLUMN(column), 25);
147 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
148 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
149
150 /* User column */
151 renderer = gtk_cell_renderer_text_new();
152 column = gtk_tree_view_column_new_with_attributes(_("User"), renderer,
153 "text", COLUMN_USER, NULL);
154 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
155 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
156
157 /* Filename column */
158 renderer = gtk_cell_renderer_text_new();
159 column = gtk_tree_view_column_new_with_attributes(_("Filename"), renderer,
160 "text", COLUMN_FILENAME, NULL);
161 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
162 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
163
164 /* Progress bar column */
165 renderer = gtk_cell_renderer_progress_new();
166 column = gtk_tree_view_column_new_with_attributes(_("Progress"), renderer,
167 "percentage", COLUMN_PROGRESS, NULL);
168 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
169 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
170
171 /* File Size column */
172 renderer = gtk_cell_renderer_text_new();
173 column = gtk_tree_view_column_new_with_attributes(_("Size"), renderer,
174 "text", COLUMN_SIZE, NULL);
175 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
176 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
177
178 /* Bytes Remaining column */
179 renderer = gtk_cell_renderer_text_new();
180 column = gtk_tree_view_column_new_with_attributes(_("Remaining"),
181 renderer, "text", COLUMN_REMAINING, NULL);
182 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
183 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
184
185 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(tree));
186
187 /* ETA column */
188 renderer = gtk_cell_renderer_text_new();
189 column = gtk_tree_view_column_new_with_attributes(_("ETA"),
190 renderer, "text", COLUMN_ESTIMATE, NULL);
191 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
192 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
193
194 /* Speed column */
195 renderer = gtk_cell_renderer_text_new();
196 column = gtk_tree_view_column_new_with_attributes(_("Speed"),
197 renderer, "text", COLUMN_SPEED, NULL);
198 gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
199 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
200
201 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(tree));
202
203 gtk_container_add(GTK_CONTAINER(sw), tree);
204 gtk_widget_show(tree);
205
206 /* Create the outer frame for the scrolled window. */
207 frame = gtk_frame_new(NULL),
208 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
209 gtk_container_add(GTK_CONTAINER(frame), sw);
210 gtk_widget_show(frame);
211
212 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
213
214 /* Separator */
215 sep = gtk_hseparator_new();
216 gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
217 gtk_widget_show(sep);
218
219 /* Now the hbox for the buttons */
220 hbox = gtk_hbox_new(FALSE, 6);
221 gtk_container_set_border_width(GTK_CONTAINER(hbox), 6);
222 gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
223 gtk_widget_show(hbox);
224
225 sg = gtk_size_group_new(GTK_SIZE_GROUP_BOTH);
226
227 /* Close button */
228 button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
229 g_signal_connect(G_OBJECT(button), "clicked",
230 G_CALLBACK(close_win_cb), NULL);
231 gtk_size_group_add_widget(sg, button);
232 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
233 gtk_widget_show(button);
234
235 #if 0
236 /* Cancel button */
237 button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
238 gtk_size_group_add_widget(sg, button);
239 gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
240 gtk_widget_show(button);
241 #endif
242
243 return dialog;
244 }
245
246 static void
247 gaim_gtkxfer_destroy(struct gaim_xfer *xfer)
248 {
249 struct gaim_gtkxfer_ui_data *data;
250
251 data = xfer->ui_data;
252
253 if (data == NULL)
254 return;
255 }
256
257 static gboolean
258 choose_file_close_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
259 {
260 gaim_xfer_request_denied((struct gaim_xfer *)user_data);
261
262 return FALSE;
263 }
264
265 static void
266 choose_file_cancel_cb(GtkButton *button, gpointer user_data)
267 {
268 struct gaim_xfer *xfer = (struct gaim_xfer *)user_data;
269 struct gaim_gtkxfer_ui_data *data;
270
271 data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
272
273 gaim_xfer_request_denied(xfer);
274
275 gtk_widget_destroy(data->filesel);
276 data->filesel = NULL;
277 }
278
279 static int
280 do_overwrite_cb(struct gaim_xfer *xfer)
281 {
282 struct gaim_gtkxfer_ui_data *data;
283
284 data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
285
286 gaim_xfer_request_accepted(xfer, data->name);
287
288 /*
289 * No, we don't want to free data->name. gaim_xfer_request_accepted
290 * will deal with it.
291 */
292 data->name = NULL;
293
294 return 0;
295 }
296
297 static int
298 dont_overwrite_cb(struct gaim_xfer *xfer)
299 {
300 struct gaim_gtkxfer_ui_data *data;
301
302 data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
303
304 g_free(data->name);
305 data->name = NULL;
306
307 gaim_xfer_request_denied(xfer);
308
309 return 0;
310 }
311
312 static void
313 choose_file_ok_cb(GtkButton *button, gpointer user_data)
314 {
315 struct gaim_xfer *xfer;
316 struct gaim_gtkxfer_ui_data *data;
317 struct stat st;
318 const char *name;
319
320 xfer = (struct gaim_xfer *)user_data;
321 data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
322
323 name = gtk_file_selection_get_filename(GTK_FILE_SELECTION(data->filesel));
324
325 if (stat(name, &st) == 0) {
326 if (S_ISDIR(st.st_mode)) {
327 /* XXX */
328 gaim_xfer_request_denied(xfer);
329 }
330 else if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) {
331 data->name = g_strdup(name);
332
333 do_ask_dialog(_("That file already exists. "
334 "Would you like to overwrite it?"),
335 NULL, xfer,
336 _("Yes"), do_overwrite_cb,
337 _("No"), dont_overwrite_cb,
338 NULL, FALSE);
339 }
340 else {
341 gaim_xfer_request_accepted(xfer, g_strdup(name));
342 }
343 }
344 else {
345 /* File not found. */
346 if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE) {
347 gaim_xfer_request_accepted(xfer, g_strdup(name));
348 }
349 else {
350 do_error_dialog(_("That file does not exist."),
351 NULL, GAIM_ERROR);
352
353 gaim_xfer_request_denied(xfer);
354 }
355 }
356
357 gtk_widget_destroy(data->filesel);
358 data->filesel = NULL;
359 }
360
361 static int
362 choose_file(struct gaim_xfer *xfer)
363 {
364 char *cur_dir, *init_str;
365 struct gaim_gtkxfer_ui_data *data;
366
367 cur_dir = g_get_current_dir();
368
369 /* This is where we're setting xfer->ui_data for the first time. */
370 data = g_malloc0(sizeof(struct gaim_gtkxfer_ui_data));
371 xfer->ui_data = data;
372
373 if (gaim_xfer_get_type(xfer) == GAIM_XFER_SEND)
374 data->filesel = gtk_file_selection_new(_("Gaim - Open..."));
375 else
376 data->filesel = gtk_file_selection_new(_("Gaim - Save As..."));
377
378 if (gaim_xfer_get_filename(xfer) == NULL)
379 init_str = g_strdup(cur_dir);
380 else
381 init_str = g_build_filename(cur_dir, gaim_xfer_get_filename(xfer),
382 NULL);
383
384 g_free(cur_dir);
385
386 gtk_file_selection_set_filename(GTK_FILE_SELECTION(data->filesel),
387 init_str);
388
389 g_free(init_str);
390
391 g_signal_connect(G_OBJECT(data->filesel), "delete_event",
392 G_CALLBACK(choose_file_close_cb), xfer);
393 g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(data->filesel)->cancel_button),
394 "clicked",
395 G_CALLBACK(choose_file_cancel_cb), xfer);
396 g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(data->filesel)->ok_button),
397 "clicked",
398 G_CALLBACK(choose_file_ok_cb), xfer);
399
400 gtk_widget_show(data->filesel);
401
402 return 0;
403 }
404
405 static int
406 cancel_recv_cb(struct gaim_xfer *xfer)
407 {
408 gaim_xfer_request_denied(xfer);
409
410 return 0;
411 }
412
413 static void
414 gaim_gtkxfer_ask_recv(struct gaim_xfer *xfer)
415 {
416 static const char *size_str[4] = { "bytes", "KB", "MB", "GB" };
417 char *buf;
418 char *size_buf;
419 float size_mag;
420 size_t size;
421 int size_index = 0;
422
423 size = gaim_xfer_get_size(xfer);
424 size_mag = (float)size;
425
426 while ((size_index < 4) && (size_mag > 1024)) {
427 size_mag /= 1024;
428 size_index++;
429 }
430
431 if (size == -1)
432 size_buf = g_strdup_printf(_("Unknown size"));
433 else
434 size_buf = g_strdup_printf("%.3g %s", size_mag, size_str[size_index]);
435
436 buf = g_strdup_printf(_("%s wants to send you %s (%s)"),
437 xfer->who, gaim_xfer_get_filename(xfer), size_buf);
438
439 g_free(size_buf);
440
441 do_ask_dialog(buf, NULL, xfer,
442 _("Accept"), choose_file,
443 _("Cancel"), cancel_recv_cb,
444 NULL, FALSE);
445
446 g_free(buf);
447 }
448
449 static void
450 gaim_gtkxfer_request_file(struct gaim_xfer *xfer)
451 {
452 if (gaim_xfer_get_type(xfer) == GAIM_XFER_RECEIVE)
453 {
454 gaim_gtkxfer_ask_recv(xfer);
455 }
456 else
457 {
458 choose_file(xfer);
459 }
460 }
461
462 static void
463 gaim_gtkxfer_ask_cancel(struct gaim_xfer *xfer)
464 {
465 }
466
467 static void
468 gaim_gtkxfer_add_xfer(struct gaim_xfer *xfer)
469 {
470 struct gaim_gtkxfer_ui_data *data;
471 GaimXferType type;
472 GdkPixbuf *pixbuf;
473
474 data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
475
476 gaim_gtkxfer_dialog_show();
477
478 data->start_time = time(NULL);
479
480 type = gaim_xfer_get_type(xfer);
481
482 pixbuf = gtk_widget_render_icon(xfer_dialog->window,
483 (type == GAIM_XFER_RECEIVE
484 ? GAIM_STOCK_DOWNLOAD
485 : GAIM_STOCK_UPLOAD),
486 GTK_ICON_SIZE_MENU, NULL);
487
488 gtk_list_store_append(xfer_dialog->model, &data->iter);
489 gtk_list_store_set(xfer_dialog->model, &data->iter,
490 COLUMN_STATUS, pixbuf,
491 COLUMN_USER, xfer->who,
492 COLUMN_FILENAME, gaim_xfer_get_filename(xfer),
493 COLUMN_PROGRESS, 0.0,
494 COLUMN_SIZE, gaim_xfer_get_size(xfer),
495 COLUMN_REMAINING, gaim_xfer_get_bytes_remaining(xfer),
496 COLUMN_ESTIMATE, NULL,
497 COLUMN_SPEED, NULL,
498 -1);
499
500 gtk_tree_view_columns_autosize(GTK_TREE_VIEW(xfer_dialog->tree));
501
502 g_object_unref(pixbuf);
503 }
504
505 static void
506 gaim_gtkxfer_update_progress(struct gaim_xfer *xfer, double percent)
507 {
508 struct gaim_gtkxfer_ui_data *data;
509 time_t now;
510 char speed_buf[256];
511 char estimate_buf[256];
512 double kb_sent, kb_rem;
513 double kbps = 0.0;
514 time_t elapsed;
515 int secs_remaining;
516
517 data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
518
519 now = time(NULL);
520 kb_rem = gaim_xfer_get_bytes_remaining(xfer) / 1024.0;
521 kb_sent = gaim_xfer_get_bytes_sent(xfer) / 1024.0;
522 elapsed = (now - data->start_time);
523 kbps = (elapsed > 0 ? (kb_sent / elapsed) : 0);
524
525 secs_remaining = (int)(kb_rem / kbps);
526
527 if (secs_remaining <= 0) {
528 GdkPixbuf *pixbuf = NULL;
529
530 *speed_buf = '\0';
531 strncpy(estimate_buf, _("Done."), sizeof(estimate_buf));
532
533 pixbuf = gtk_widget_render_icon(xfer_dialog->window,
534 GAIM_STOCK_FILE_DONE,
535 GTK_ICON_SIZE_MENU, NULL);
536
537 gtk_list_store_set(xfer_dialog->model, &data->iter,
538 COLUMN_STATUS, pixbuf,
539 -1);
540
541 g_object_unref(pixbuf);
542 }
543 else {
544 int h = secs_remaining / 3600;
545 int m = (secs_remaining % 3600) / 60;
546 int s = secs_remaining % 60;
547
548 g_snprintf(estimate_buf, sizeof(estimate_buf),
549 _("%d:%02d:%02d"), h, m, s);
550 g_snprintf(speed_buf, sizeof(speed_buf),
551 _("%.2f KB/s"), kbps);
552 }
553
554 gtk_list_store_set(xfer_dialog->model, &data->iter,
555 COLUMN_REMAINING, gaim_xfer_get_bytes_remaining(xfer),
556 COLUMN_PROGRESS, percent,
557 COLUMN_ESTIMATE, estimate_buf,
558 COLUMN_SPEED, speed_buf,
559 -1);
560 }
561
562 static void
563 gaim_gtkxfer_cancel(struct gaim_xfer *xfer)
564 {
565 struct gaim_gtkxfer_ui_data *data;
566 GdkPixbuf *pixbuf;
567
568 data = (struct gaim_gtkxfer_ui_data *)xfer->ui_data;
569
570 pixbuf = gtk_widget_render_icon(xfer_dialog->window,
571 GAIM_STOCK_FILE_CANCELED,
572 GTK_ICON_SIZE_MENU, NULL);
573
574 gtk_list_store_set(xfer_dialog->model, &data->iter,
575 COLUMN_STATUS, pixbuf,
576 -1);
577
578 g_object_unref(pixbuf);
579 }
580
581 struct gaim_xfer_ui_ops ops =
582 {
583 gaim_gtkxfer_destroy,
584 gaim_gtkxfer_request_file,
585 gaim_gtkxfer_ask_cancel,
586 gaim_gtkxfer_add_xfer,
587 gaim_gtkxfer_update_progress,
588 gaim_gtkxfer_cancel
589 };
590
591 void
592 gaim_gtkxfer_dialog_show(void)
593 {
594 if (xfer_dialog == NULL)
595 xfer_dialog = build_xfer_dialog();
596
597 gtk_widget_show(xfer_dialog->window);
598 }
599
600 void
601 gaim_gtkxfer_dialog_hide(void)
602 {
603 gtk_widget_hide(xfer_dialog->window);
604 }
605
606 struct gaim_xfer_ui_ops *
607 gaim_get_gtk_xfer_ui_ops(void)
608 {
609 return &ops;
610 }