1 /* X-Chat
2 * Copyright (C) 1998-2006 Peter Zelezny.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17 */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <time.h>
25
26 #define WANTSOCKET
27 #define WANTARPA
28 #include "../common/inet.h"
29 #include "fe-gtk.h"
30
31 #include "../common/hexchat.h"
32 #include "../common/hexchatc.h"
33 #include "../common/fe.h"
34 #include "../common/util.h"
35 #include "../common/network.h"
36 #include "gtkutil.h"
37 #include "palette.h"
38 #include "maingui.h"
39
40
41 enum /* DCC SEND/RECV */
42 {
43 COL_TYPE,
44 COL_STATUS,
45 COL_FILE,
46 COL_SIZE,
47 COL_POS,
48 COL_PERC,
49 COL_SPEED,
50 COL_ETA,
51 COL_NICK,
52 COL_DCC, /* struct DCC * */
53 COL_COLOR, /* GdkColor */
54 N_COLUMNS
55 };
56
57 enum /* DCC CHAT */
58 {
59 CCOL_STATUS,
60 CCOL_NICK,
61 CCOL_RECV,
62 CCOL_SENT,
63 CCOL_START,
64 CCOL_DCC, /* struct DCC * */
65 CCOL_COLOR, /* GdkColor * */
66 CN_COLUMNS
67 };
68
69 struct dccwindow
70 {
71 GtkWidget *window;
72
73 GtkWidget *list;
74 GtkListStore *store;
75 GtkTreeSelection *sel;
76
77 GtkWidget *abort_button;
78 GtkWidget *accept_button;
79 GtkWidget *resume_button;
80 GtkWidget *open_button;
81 GtkWidget *clear_button; /* clears aborted and completed requests */
82
83 GtkWidget *file_label;
84 GtkWidget *address_label;
85 };
86
87 struct my_dcc_send
88 {
89 struct session *sess;
90 char *nick;
91 gint64 maxcps;
92 int passive;
93 };
94
95 static struct dccwindow dccfwin = {NULL, }; /* file */
96 static struct dccwindow dcccwin = {NULL, }; /* chat */
97 static GdkPixbuf *pix_up = NULL; /* down arrow */
98 static GdkPixbuf *pix_dn = NULL; /* up arrow */
99 static int win_width = 600;
100 static int win_height = 256;
101 static short view_mode; /* 1=download 2=upload 3=both */
102 #define VIEW_DOWNLOAD 1
103 #define VIEW_UPLOAD 2
104 #define VIEW_BOTH 3
105
106
107 static void
proper_unit(guint64 size,char * buf,size_t buf_len)108 proper_unit (guint64 size, char *buf, size_t buf_len)
109 {
110 gchar *formatted_str;
111 GFormatSizeFlags format_flags = G_FORMAT_SIZE_DEFAULT;
112
113 #ifndef __APPLE__ /* OS X uses SI */
114 #ifndef WIN32 /* Windows uses IEC size (with SI format) */
115 if (prefs.hex_gui_filesize_iec) /* Linux can't decide... */
116 #endif
117 format_flags = G_FORMAT_SIZE_IEC_UNITS;
118 #endif
119
120 formatted_str = g_format_size_full (size, format_flags);
121 g_strlcpy (buf, formatted_str, buf_len);
122
123 g_free (formatted_str);
124 }
125
126 static void
dcc_send_filereq_file(struct my_dcc_send * mdc,char * file)127 dcc_send_filereq_file (struct my_dcc_send *mdc, char *file)
128 {
129 if (file)
130 dcc_send (mdc->sess, mdc->nick, file, mdc->maxcps, mdc->passive);
131 else
132 {
133 g_free (mdc->nick);
134 g_free (mdc);
135 }
136 }
137
138 void
fe_dcc_send_filereq(struct session * sess,char * nick,int maxcps,int passive)139 fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive)
140 {
141 char* tbuf = g_strdup_printf (_("Send file to %s"), nick);
142
143 struct my_dcc_send *mdc = g_new (struct my_dcc_send, 1);
144 mdc->sess = sess;
145 mdc->nick = g_strdup (nick);
146 mdc->maxcps = maxcps;
147 mdc->passive = passive;
148
149 gtkutil_file_req (tbuf, dcc_send_filereq_file, mdc, prefs.hex_dcc_dir, NULL, FRF_MULTIPLE|FRF_FILTERISINITIAL);
150
151 g_free (tbuf);
152 }
153
154 static void
dcc_prepare_row_chat(struct DCC * dcc,GtkListStore * store,GtkTreeIter * iter,gboolean update_only)155 dcc_prepare_row_chat (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter,
156 gboolean update_only)
157 {
158 static char pos[16], size[16];
159 char *date;
160
161 date = ctime (&dcc->starttime);
162 date[strlen (date) - 1] = 0; /* remove the \n */
163
164 proper_unit (dcc->pos, pos, sizeof (pos));
165 proper_unit (dcc->size, size, sizeof (size));
166
167 gtk_list_store_set (store, iter,
168 CCOL_STATUS, _(dccstat[dcc->dccstat].name),
169 CCOL_NICK, dcc->nick,
170 CCOL_RECV, pos,
171 CCOL_SENT, size,
172 CCOL_START, date,
173 CCOL_DCC, dcc,
174 CCOL_COLOR,
175 dccstat[dcc->dccstat].color == 1 ?
176 NULL :
177 colors + dccstat[dcc->dccstat].color,
178 -1);
179 }
180
181 static void
dcc_prepare_row_send(struct DCC * dcc,GtkListStore * store,GtkTreeIter * iter,gboolean update_only)182 dcc_prepare_row_send (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter,
183 gboolean update_only)
184 {
185 static char pos[16], size[16], kbs[14], perc[14], eta[14];
186 int to_go;
187 float per;
188
189 if (!pix_up)
190 pix_up = gtk_widget_render_icon (dccfwin.window, "gtk-go-up",
191 GTK_ICON_SIZE_MENU, NULL);
192
193 /* percentage ack'ed */
194 per = (float) ((dcc->ack * 100.00) / dcc->size);
195 proper_unit (dcc->size, size, sizeof (size));
196 proper_unit (dcc->pos, pos, sizeof (pos));
197 g_snprintf (kbs, sizeof (kbs), "%.1f", ((float)dcc->cps) / 1024);
198 g_snprintf (perc, sizeof (perc), "%.0f%%", per);
199 if (dcc->cps != 0)
200 {
201 to_go = (dcc->size - dcc->ack) / dcc->cps;
202 g_snprintf (eta, sizeof (eta), "%.2d:%.2d:%.2d",
203 to_go / 3600, (to_go / 60) % 60, to_go % 60);
204 } else
205 strcpy (eta, "--:--:--");
206
207 if (update_only)
208 gtk_list_store_set (store, iter,
209 COL_STATUS, _(dccstat[dcc->dccstat].name),
210 COL_POS, pos,
211 COL_PERC, perc,
212 COL_SPEED, kbs,
213 COL_ETA, eta,
214 COL_COLOR,
215 dccstat[dcc->dccstat].color == 1 ?
216 NULL :
217 colors + dccstat[dcc->dccstat].color,
218 -1);
219 else
220 gtk_list_store_set (store, iter,
221 COL_TYPE, pix_up,
222 COL_STATUS, _(dccstat[dcc->dccstat].name),
223 COL_FILE, file_part (dcc->file),
224 COL_SIZE, size,
225 COL_POS, pos,
226 COL_PERC, perc,
227 COL_SPEED, kbs,
228 COL_ETA, eta,
229 COL_NICK, dcc->nick,
230 COL_DCC, dcc,
231 COL_COLOR,
232 dccstat[dcc->dccstat].color == 1 ?
233 NULL :
234 colors + dccstat[dcc->dccstat].color,
235 -1);
236 }
237
238 static void
dcc_prepare_row_recv(struct DCC * dcc,GtkListStore * store,GtkTreeIter * iter,gboolean update_only)239 dcc_prepare_row_recv (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter,
240 gboolean update_only)
241 {
242 static char size[16], pos[16], kbs[16], perc[14], eta[16];
243 float per;
244 int to_go;
245
246 if (!pix_dn)
247 pix_dn = gtk_widget_render_icon (dccfwin.window, "gtk-go-down",
248 GTK_ICON_SIZE_MENU, NULL);
249
250 proper_unit (dcc->size, size, sizeof (size));
251 if (dcc->dccstat == STAT_QUEUED)
252 proper_unit (dcc->resumable, pos, sizeof (pos));
253 else
254 proper_unit (dcc->pos, pos, sizeof (pos));
255 g_snprintf (kbs, sizeof (kbs), "%.1f", ((float)dcc->cps) / 1024);
256 /* percentage recv'ed */
257 per = (float) ((dcc->pos * 100.00) / dcc->size);
258 g_snprintf (perc, sizeof (perc), "%.0f%%", per);
259 if (dcc->cps != 0)
260 {
261 to_go = (dcc->size - dcc->pos) / dcc->cps;
262 g_snprintf (eta, sizeof (eta), "%.2d:%.2d:%.2d",
263 to_go / 3600, (to_go / 60) % 60, to_go % 60);
264 } else
265 strcpy (eta, "--:--:--");
266
267 if (update_only)
268 gtk_list_store_set (store, iter,
269 COL_STATUS, _(dccstat[dcc->dccstat].name),
270 COL_POS, pos,
271 COL_PERC, perc,
272 COL_SPEED, kbs,
273 COL_ETA, eta,
274 COL_COLOR,
275 dccstat[dcc->dccstat].color == 1 ?
276 NULL :
277 colors + dccstat[dcc->dccstat].color,
278 -1);
279 else
280 gtk_list_store_set (store, iter,
281 COL_TYPE, pix_dn,
282 COL_STATUS, _(dccstat[dcc->dccstat].name),
283 COL_FILE, file_part (dcc->file),
284 COL_SIZE, size,
285 COL_POS, pos,
286 COL_PERC, perc,
287 COL_SPEED, kbs,
288 COL_ETA, eta,
289 COL_NICK, dcc->nick,
290 COL_DCC, dcc,
291 COL_COLOR,
292 dccstat[dcc->dccstat].color == 1 ?
293 NULL :
294 colors + dccstat[dcc->dccstat].color,
295 -1);
296 }
297
298 static gboolean
dcc_find_row(struct DCC * find_dcc,GtkTreeModel * model,GtkTreeIter * iter,int col)299 dcc_find_row (struct DCC *find_dcc, GtkTreeModel *model, GtkTreeIter *iter, int col)
300 {
301 struct DCC *dcc;
302
303 if (gtk_tree_model_get_iter_first (model, iter))
304 {
305 do
306 {
307 gtk_tree_model_get (model, iter, col, &dcc, -1);
308 if (dcc == find_dcc)
309 return TRUE;
310 }
311 while (gtk_tree_model_iter_next (model, iter));
312 }
313
314 return FALSE;
315 }
316
317 static void
dcc_update_recv(struct DCC * dcc)318 dcc_update_recv (struct DCC *dcc)
319 {
320 GtkTreeIter iter;
321
322 if (!dccfwin.window)
323 return;
324
325 if (!dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC))
326 return;
327
328 dcc_prepare_row_recv (dcc, dccfwin.store, &iter, TRUE);
329 }
330
331 static void
dcc_update_chat(struct DCC * dcc)332 dcc_update_chat (struct DCC *dcc)
333 {
334 GtkTreeIter iter;
335
336 if (!dcccwin.window)
337 return;
338
339 if (!dcc_find_row (dcc, GTK_TREE_MODEL (dcccwin.store), &iter, CCOL_DCC))
340 return;
341
342 dcc_prepare_row_chat (dcc, dcccwin.store, &iter, TRUE);
343 }
344
345 static void
dcc_update_send(struct DCC * dcc)346 dcc_update_send (struct DCC *dcc)
347 {
348 GtkTreeIter iter;
349
350 if (!dccfwin.window)
351 return;
352
353 if (!dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC))
354 return;
355
356 dcc_prepare_row_send (dcc, dccfwin.store, &iter, TRUE);
357 }
358
359 static void
close_dcc_file_window(GtkWindow * win,gpointer data)360 close_dcc_file_window (GtkWindow *win, gpointer data)
361 {
362 dccfwin.window = NULL;
363 }
364
365 static void
dcc_append(struct DCC * dcc,GtkListStore * store,gboolean prepend)366 dcc_append (struct DCC *dcc, GtkListStore *store, gboolean prepend)
367 {
368 GtkTreeIter iter;
369
370 if (prepend)
371 gtk_list_store_prepend (store, &iter);
372 else
373 gtk_list_store_append (store, &iter);
374
375 if (dcc->type == TYPE_RECV)
376 dcc_prepare_row_recv (dcc, store, &iter, FALSE);
377 else
378 dcc_prepare_row_send (dcc, store, &iter, FALSE);
379 }
380
381 /* Returns aborted and completed transfers. */
382 static GSList *
dcc_get_completed(void)383 dcc_get_completed (void)
384 {
385 struct DCC *dcc;
386 GtkTreeIter iter;
387 GtkTreeModel *model;
388 GSList *completed = NULL;
389
390 model = GTK_TREE_MODEL (dccfwin.store);
391 if (gtk_tree_model_get_iter_first (model, &iter))
392 {
393 do
394 {
395 gtk_tree_model_get (model, &iter, COL_DCC, &dcc, -1);
396 if (is_dcc_completed (dcc))
397 completed = g_slist_prepend (completed, dcc);
398
399 } while (gtk_tree_model_iter_next (model, &iter));
400 }
401
402 return completed;
403 }
404
405 static gboolean
dcc_completed_transfer_exists(void)406 dcc_completed_transfer_exists (void)
407 {
408 gboolean exist;
409 GSList *comp_list;
410
411 comp_list = dcc_get_completed ();
412 exist = comp_list != NULL;
413
414 g_slist_free (comp_list);
415 return exist;
416 }
417
418 static void
update_clear_button_sensitivity(void)419 update_clear_button_sensitivity (void)
420 {
421 gboolean sensitive = dcc_completed_transfer_exists ();
422 gtk_widget_set_sensitive (dccfwin.clear_button, sensitive);
423 }
424
425 static void
dcc_fill_window(int flags)426 dcc_fill_window (int flags)
427 {
428 struct DCC *dcc;
429 GSList *list;
430 GtkTreeIter iter;
431 int i = 0;
432
433 gtk_list_store_clear (GTK_LIST_STORE (dccfwin.store));
434
435 if (flags & VIEW_UPLOAD)
436 {
437 list = dcc_list;
438 while (list)
439 {
440 dcc = list->data;
441 if (dcc->type == TYPE_SEND)
442 {
443 dcc_append (dcc, dccfwin.store, FALSE);
444 i++;
445 }
446 list = list->next;
447 }
448 }
449
450 if (flags & VIEW_DOWNLOAD)
451 {
452 list = dcc_list;
453 while (list)
454 {
455 dcc = list->data;
456 if (dcc->type == TYPE_RECV)
457 {
458 dcc_append (dcc, dccfwin.store, FALSE);
459 i++;
460 }
461 list = list->next;
462 }
463 }
464
465 /* if only one entry, select it (so Accept button can work) */
466 if (i == 1)
467 {
468 gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dccfwin.store), &iter);
469 gtk_tree_selection_select_iter (dccfwin.sel, &iter);
470 }
471
472 update_clear_button_sensitivity ();
473 }
474
475 /* return list of selected DCCs */
476
477 static GSList *
treeview_get_selected(GtkTreeModel * model,GtkTreeSelection * sel,int column)478 treeview_get_selected (GtkTreeModel *model, GtkTreeSelection *sel, int column)
479 {
480 GtkTreeIter iter;
481 GSList *list = NULL;
482 void *ptr;
483
484 if (gtk_tree_model_get_iter_first (model, &iter))
485 {
486 do
487 {
488 if (gtk_tree_selection_iter_is_selected (sel, &iter))
489 {
490 gtk_tree_model_get (model, &iter, column, &ptr, -1);
491 list = g_slist_prepend (list, ptr);
492 }
493 }
494 while (gtk_tree_model_iter_next (model, &iter));
495 }
496
497 return g_slist_reverse (list);
498 }
499
500 static GSList *
dcc_get_selected(void)501 dcc_get_selected (void)
502 {
503 return treeview_get_selected (GTK_TREE_MODEL (dccfwin.store),
504 dccfwin.sel, COL_DCC);
505 }
506
507 static void
resume_clicked(GtkWidget * wid,gpointer none)508 resume_clicked (GtkWidget * wid, gpointer none)
509 {
510 struct DCC *dcc;
511 char buf[512];
512 GSList *list;
513
514 list = dcc_get_selected ();
515 if (!list)
516 return;
517 dcc = list->data;
518 g_slist_free (list);
519
520 if (dcc->type == TYPE_RECV && !dcc_resume (dcc))
521 {
522 switch (dcc->resume_error)
523 {
524 case 0: /* unknown error */
525 fe_message (_("That file is not resumable."), FE_MSG_ERROR);
526 break;
527 case 1:
528 g_snprintf (buf, sizeof (buf),
529 _( "Cannot access file: %s\n"
530 "%s.\n"
531 "Resuming not possible."), dcc->destfile,
532 errorstring (dcc->resume_errno));
533 fe_message (buf, FE_MSG_ERROR);
534 break;
535 case 2:
536 fe_message (_("File in download directory is larger "
537 "than file offered. Resuming not possible."), FE_MSG_ERROR);
538 break;
539 case 3:
540 fe_message (_("Cannot resume the same file from two people."), FE_MSG_ERROR);
541 }
542 }
543 }
544
545 static void
abort_clicked(GtkWidget * wid,gpointer none)546 abort_clicked (GtkWidget * wid, gpointer none)
547 {
548 struct DCC *dcc;
549 GSList *start, *list;
550
551 start = list = dcc_get_selected ();
552 for (; list; list = list->next)
553 {
554 dcc = list->data;
555 dcc_abort (dcc->serv->front_session, dcc);
556 }
557 g_slist_free (start);
558
559 /* Enable the clear button if it wasn't already enabled */
560 update_clear_button_sensitivity ();
561 }
562
563 static void
accept_clicked(GtkWidget * wid,gpointer none)564 accept_clicked (GtkWidget * wid, gpointer none)
565 {
566 struct DCC *dcc;
567 GSList *start, *list;
568
569 start = list = dcc_get_selected ();
570 for (; list; list = list->next)
571 {
572 dcc = list->data;
573 if (dcc->type != TYPE_SEND)
574 dcc_get (dcc);
575 }
576 g_slist_free (start);
577 }
578
579 static void
clear_completed(GtkWidget * wid,gpointer none)580 clear_completed (GtkWidget * wid, gpointer none)
581 {
582 struct DCC *dcc;
583 GSList *completed;
584
585 /* Make a new list of only the completed items and abort each item.
586 * A new list is made because calling dcc_abort removes items from the original list,
587 * making it impossible to iterate over that list directly.
588 */
589 for (completed = dcc_get_completed (); completed; completed = completed->next)
590 {
591 dcc = completed->data;
592 dcc_abort (dcc->serv->front_session, dcc);
593 }
594
595 /* The data was freed by dcc_close */
596 g_slist_free (completed);
597 update_clear_button_sensitivity ();
598 }
599
600 static void
browse_folder(char * dir)601 browse_folder (char *dir)
602 {
603 #ifdef WIN32
604 /* no need for file:// in ShellExecute() */
605 fe_open_url (dir);
606 #else
607 char buf[512];
608
609 g_snprintf (buf, sizeof (buf), "file://%s", dir);
610 fe_open_url (buf);
611 #endif
612 }
613
614 static void
browse_dcc_folder(void)615 browse_dcc_folder (void)
616 {
617 if (prefs.hex_dcc_completed_dir[0])
618 browse_folder (prefs.hex_dcc_completed_dir);
619 else
620 browse_folder (prefs.hex_dcc_dir);
621 }
622
623 static void
dcc_details_populate(struct DCC * dcc)624 dcc_details_populate (struct DCC *dcc)
625 {
626 char buf[128];
627
628 if (!dcc)
629 {
630 gtk_label_set_text (GTK_LABEL (dccfwin.file_label), NULL);
631 gtk_label_set_text (GTK_LABEL (dccfwin.address_label), NULL);
632 return;
633 }
634
635 /* full path */
636 if (dcc->type == TYPE_RECV)
637 gtk_label_set_text (GTK_LABEL (dccfwin.file_label), dcc->destfile);
638 else
639 gtk_label_set_text (GTK_LABEL (dccfwin.file_label), dcc->file);
640
641 /* address and port */
642 g_snprintf (buf, sizeof (buf), "%s : %d", net_ip (dcc->addr), dcc->port);
643 gtk_label_set_text (GTK_LABEL (dccfwin.address_label), buf);
644 }
645
646 static void
dcc_row_cb(GtkTreeSelection * sel,gpointer user_data)647 dcc_row_cb (GtkTreeSelection *sel, gpointer user_data)
648 {
649 struct DCC *dcc;
650 GSList *list;
651
652 list = dcc_get_selected ();
653 if (!list)
654 {
655 gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
656 gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
657 gtk_widget_set_sensitive (dccfwin.abort_button, FALSE);
658 dcc_details_populate (NULL);
659 return;
660 }
661
662 gtk_widget_set_sensitive (dccfwin.abort_button, TRUE);
663
664 if (list->next) /* multi selection */
665 {
666 gtk_widget_set_sensitive (dccfwin.accept_button, TRUE);
667 gtk_widget_set_sensitive (dccfwin.resume_button, TRUE);
668 dcc_details_populate (list->data);
669 }
670 else
671 {
672 /* turn OFF/ON appropriate buttons */
673 dcc = list->data;
674 if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_RECV)
675 {
676 gtk_widget_set_sensitive (dccfwin.accept_button, TRUE);
677 gtk_widget_set_sensitive (dccfwin.resume_button, TRUE);
678 }
679 else
680 {
681 gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
682 gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
683 }
684
685 dcc_details_populate (dcc);
686 }
687
688 g_slist_free (list);
689 }
690
691 static void
dcc_dclick_cb(GtkTreeView * view,GtkTreePath * path,GtkTreeViewColumn * column,gpointer data)692 dcc_dclick_cb (GtkTreeView *view, GtkTreePath *path,
693 GtkTreeViewColumn *column, gpointer data)
694 {
695 struct DCC *dcc;
696 GSList *list;
697
698 list = dcc_get_selected ();
699 if (!list)
700 return;
701 dcc = list->data;
702 g_slist_free (list);
703
704 if (dcc->type == TYPE_RECV)
705 {
706 accept_clicked (0, 0);
707 return;
708 }
709
710 switch (dcc->dccstat)
711 {
712 case STAT_FAILED:
713 case STAT_ABORTED:
714 case STAT_DONE:
715 dcc_abort (dcc->serv->front_session, dcc);
716 break;
717 case STAT_QUEUED:
718 case STAT_ACTIVE:
719 case STAT_CONNECTING:
720 break;
721 }
722 }
723
724 static void
dcc_add_column(GtkWidget * tree,int textcol,int colorcol,char * title,gboolean right_justified)725 dcc_add_column (GtkWidget *tree, int textcol, int colorcol, char *title, gboolean right_justified)
726 {
727 GtkCellRenderer *renderer;
728
729 renderer = gtk_cell_renderer_text_new ();
730 if (right_justified)
731 g_object_set (G_OBJECT (renderer), "xalign", (float) 1.0, NULL);
732 gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, title, renderer,
733 "text", textcol, "foreground-gdk", colorcol,
734 NULL);
735 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
736 }
737
738 static GtkWidget *
dcc_detail_label(char * text,GtkWidget * box,int num)739 dcc_detail_label (char *text, GtkWidget *box, int num)
740 {
741 GtkWidget *label;
742 char buf[64];
743
744 label = gtk_label_new (NULL);
745 g_snprintf (buf, sizeof (buf), "<b>%s</b>", text);
746 gtk_label_set_markup (GTK_LABEL (label), buf);
747 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
748 gtk_table_attach (GTK_TABLE (box), label, 0, 1, 0 + num, 1 + num, GTK_FILL, GTK_FILL, 0, 0);
749
750 label = gtk_label_new (NULL);
751 gtk_label_set_selectable (GTK_LABEL (label), TRUE);
752 gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
753 gtk_table_attach (GTK_TABLE (box), label, 1, 2, 0 + num, 1 + num, GTK_FILL, GTK_FILL, 0, 0);
754
755 return label;
756 }
757
758 static void
dcc_exp_cb(GtkWidget * exp,GtkWidget * box)759 dcc_exp_cb (GtkWidget *exp, GtkWidget *box)
760 {
761 if (gtk_widget_get_visible (box))
762 {
763 gtk_widget_hide (box);
764 }
765 else
766 {
767 gtk_widget_show (box);
768 }
769 }
770
771 static void
dcc_toggle(GtkWidget * item,gpointer data)772 dcc_toggle (GtkWidget *item, gpointer data)
773 {
774 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (item)))
775 {
776 view_mode = GPOINTER_TO_INT (data);
777 dcc_fill_window (GPOINTER_TO_INT (data));
778 }
779 }
780
781 static gboolean
dcc_configure_cb(GtkWindow * win,GdkEventConfigure * event,gpointer data)782 dcc_configure_cb (GtkWindow *win, GdkEventConfigure *event, gpointer data)
783 {
784 /* remember the window size */
785 gtk_window_get_size (win, &win_width, &win_height);
786 return FALSE;
787 }
788
789 int
fe_dcc_open_recv_win(int passive)790 fe_dcc_open_recv_win (int passive)
791 {
792 GtkWidget *radio, *table, *vbox, *bbox, *view, *exp, *detailbox;
793 GtkListStore *store;
794 GSList *group;
795 char buf[128];
796
797 if (dccfwin.window)
798 {
799 if (!passive)
800 mg_bring_tofront (dccfwin.window);
801 return TRUE;
802 }
803 g_snprintf(buf, sizeof(buf), _("Uploads and Downloads - %s"), _(DISPLAY_NAME));
804 dccfwin.window = mg_create_generic_tab ("Transfers", buf, FALSE, TRUE, close_dcc_file_window,
805 NULL, win_width, win_height, &vbox, 0);
806 gtkutil_destroy_on_esc (dccfwin.window);
807 gtk_container_set_border_width (GTK_CONTAINER (dccfwin.window), 3);
808 gtk_box_set_spacing (GTK_BOX (vbox), 3);
809
810 store = gtk_list_store_new (N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING,
811 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
812 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
813 G_TYPE_STRING, G_TYPE_POINTER, GDK_TYPE_COLOR);
814 view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1);
815 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
816 /* Up/Down Icon column */
817 gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, NULL,
818 gtk_cell_renderer_pixbuf_new (),
819 "pixbuf", COL_TYPE, NULL);
820 dcc_add_column (view, COL_STATUS, COL_COLOR, _("Status"), FALSE);
821 dcc_add_column (view, COL_FILE, COL_COLOR, _("File"), FALSE);
822 dcc_add_column (view, COL_SIZE, COL_COLOR, _("Size"), TRUE);
823 dcc_add_column (view, COL_POS, COL_COLOR, _("Position"), TRUE);
824 dcc_add_column (view, COL_PERC, COL_COLOR, "%", TRUE);
825 dcc_add_column (view, COL_SPEED, COL_COLOR, "KB/s", TRUE);
826 dcc_add_column (view, COL_ETA, COL_COLOR, _("ETA"), FALSE);
827 dcc_add_column (view, COL_NICK, COL_COLOR, _("Nick"), FALSE);
828
829 gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_FILE), TRUE);
830 gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_NICK), TRUE);
831
832 dccfwin.list = view;
833 dccfwin.store = store;
834 dccfwin.sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
835 view_mode = VIEW_BOTH;
836 gtk_tree_selection_set_mode (dccfwin.sel, GTK_SELECTION_MULTIPLE);
837
838 if (!prefs.hex_gui_tab_utils)
839 g_signal_connect (G_OBJECT (dccfwin.window), "configure_event",
840 G_CALLBACK (dcc_configure_cb), 0);
841 g_signal_connect (G_OBJECT (dccfwin.sel), "changed",
842 G_CALLBACK (dcc_row_cb), NULL);
843 /* double click */
844 g_signal_connect (G_OBJECT (view), "row-activated",
845 G_CALLBACK (dcc_dclick_cb), NULL);
846
847 table = gtk_table_new (1, 3, FALSE);
848 gtk_table_set_col_spacings (GTK_TABLE (table), 16);
849 gtk_box_pack_start (GTK_BOX (vbox), table, 0, 0, 0);
850
851 radio = gtk_radio_button_new_with_mnemonic (NULL, _("Both"));
852 g_signal_connect (G_OBJECT (radio), "toggled",
853 G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_BOTH));
854 gtk_table_attach (GTK_TABLE (table), radio, 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
855 group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio));
856
857 radio = gtk_radio_button_new_with_mnemonic (group, _("Uploads"));
858 g_signal_connect (G_OBJECT (radio), "toggled",
859 G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_UPLOAD));
860 gtk_table_attach (GTK_TABLE (table), radio, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
861 group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio));
862
863 radio = gtk_radio_button_new_with_mnemonic (group, _("Downloads"));
864 g_signal_connect (G_OBJECT (radio), "toggled",
865 G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_DOWNLOAD));
866 gtk_table_attach (GTK_TABLE (table), radio, 2, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
867
868 exp = gtk_expander_new (_("Details"));
869 gtk_table_attach (GTK_TABLE (table), exp, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
870
871 detailbox = gtk_table_new (3, 3, FALSE);
872 gtk_table_set_col_spacings (GTK_TABLE (detailbox), 6);
873 gtk_table_set_row_spacings (GTK_TABLE (detailbox), 2);
874 gtk_container_set_border_width (GTK_CONTAINER (detailbox), 6);
875 g_signal_connect (G_OBJECT (exp), "activate",
876 G_CALLBACK (dcc_exp_cb), detailbox);
877 gtk_table_attach (GTK_TABLE (table), detailbox, 0, 4, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
878
879 dccfwin.file_label = dcc_detail_label (_("File:"), detailbox, 0);
880 dccfwin.address_label = dcc_detail_label (_("Address:"), detailbox, 1);
881
882 bbox = gtk_hbutton_box_new ();
883 gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
884 gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 2);
885
886 dccfwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_clicked, 0, _("Abort"));
887 dccfwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_clicked, 0, _("Accept"));
888 dccfwin.resume_button = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, resume_clicked, 0, _("Resume"));
889 dccfwin.clear_button = gtkutil_button (bbox, GTK_STOCK_CLEAR, 0, clear_completed, 0, _("Clear"));
890 dccfwin.open_button = gtkutil_button (bbox, 0, 0, browse_dcc_folder, 0, _("Open Folder..."));
891 gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
892 gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
893 gtk_widget_set_sensitive (dccfwin.abort_button, FALSE);
894
895 dcc_fill_window (3);
896 gtk_widget_show_all (dccfwin.window);
897 gtk_widget_hide (detailbox);
898
899 return FALSE;
900 }
901
902 int
fe_dcc_open_send_win(int passive)903 fe_dcc_open_send_win (int passive)
904 {
905 /* combined send/recv GUI */
906 return fe_dcc_open_recv_win (passive);
907 }
908
909
910 /* DCC CHAT GUIs BELOW */
911
912 static GSList *
dcc_chat_get_selected(void)913 dcc_chat_get_selected (void)
914 {
915 return treeview_get_selected (GTK_TREE_MODEL (dcccwin.store),
916 dcccwin.sel, CCOL_DCC);
917 }
918
919 static void
accept_chat_clicked(GtkWidget * wid,gpointer none)920 accept_chat_clicked (GtkWidget * wid, gpointer none)
921 {
922 struct DCC *dcc;
923 GSList *start, *list;
924
925 start = list = dcc_chat_get_selected ();
926 for (; list; list = list->next)
927 {
928 dcc = list->data;
929 dcc_get (dcc);
930 }
931 g_slist_free (start);
932 }
933
934 static void
abort_chat_clicked(GtkWidget * wid,gpointer none)935 abort_chat_clicked (GtkWidget * wid, gpointer none)
936 {
937 struct DCC *dcc;
938 GSList *start, *list;
939
940 start = list = dcc_chat_get_selected ();
941 for (; list; list = list->next)
942 {
943 dcc = list->data;
944 dcc_abort (dcc->serv->front_session, dcc);
945 }
946 g_slist_free (start);
947 }
948
949 static void
dcc_chat_close_cb(void)950 dcc_chat_close_cb (void)
951 {
952 dcccwin.window = NULL;
953 }
954
955 static void
dcc_chat_append(struct DCC * dcc,GtkListStore * store,gboolean prepend)956 dcc_chat_append (struct DCC *dcc, GtkListStore *store, gboolean prepend)
957 {
958 GtkTreeIter iter;
959
960 if (prepend)
961 gtk_list_store_prepend (store, &iter);
962 else
963 gtk_list_store_append (store, &iter);
964
965 dcc_prepare_row_chat (dcc, store, &iter, FALSE);
966 }
967
968 static void
dcc_chat_fill_win(void)969 dcc_chat_fill_win (void)
970 {
971 struct DCC *dcc;
972 GSList *list;
973 GtkTreeIter iter;
974 int i = 0;
975
976 gtk_list_store_clear (GTK_LIST_STORE (dcccwin.store));
977
978 list = dcc_list;
979 while (list)
980 {
981 dcc = list->data;
982 if (dcc->type == TYPE_CHATSEND || dcc->type == TYPE_CHATRECV)
983 {
984 dcc_chat_append (dcc, dcccwin.store, FALSE);
985 i++;
986 }
987 list = list->next;
988 }
989
990 /* if only one entry, select it (so Accept button can work) */
991 if (i == 1)
992 {
993 gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dcccwin.store), &iter);
994 gtk_tree_selection_select_iter (dcccwin.sel, &iter);
995 }
996 }
997
998 static void
dcc_chat_row_cb(GtkTreeSelection * sel,gpointer user_data)999 dcc_chat_row_cb (GtkTreeSelection *sel, gpointer user_data)
1000 {
1001 struct DCC *dcc;
1002 GSList *list;
1003
1004 list = dcc_chat_get_selected ();
1005 if (!list)
1006 {
1007 gtk_widget_set_sensitive (dcccwin.accept_button, FALSE);
1008 gtk_widget_set_sensitive (dcccwin.abort_button, FALSE);
1009 return;
1010 }
1011
1012 gtk_widget_set_sensitive (dcccwin.abort_button, TRUE);
1013
1014 if (list->next) /* multi selection */
1015 gtk_widget_set_sensitive (dcccwin.accept_button, TRUE);
1016 else
1017 {
1018 /* turn OFF/ON appropriate buttons */
1019 dcc = list->data;
1020 if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_CHATRECV)
1021 gtk_widget_set_sensitive (dcccwin.accept_button, TRUE);
1022 else
1023 gtk_widget_set_sensitive (dcccwin.accept_button, FALSE);
1024 }
1025
1026 g_slist_free (list);
1027 }
1028
1029 static void
dcc_chat_dclick_cb(GtkTreeView * view,GtkTreePath * path,GtkTreeViewColumn * column,gpointer data)1030 dcc_chat_dclick_cb (GtkTreeView *view, GtkTreePath *path,
1031 GtkTreeViewColumn *column, gpointer data)
1032 {
1033 accept_chat_clicked (0, 0);
1034 }
1035
1036 int
fe_dcc_open_chat_win(int passive)1037 fe_dcc_open_chat_win (int passive)
1038 {
1039 GtkWidget *view, *vbox, *bbox;
1040 GtkListStore *store;
1041 char buf[128];
1042
1043 if (dcccwin.window)
1044 {
1045 if (!passive)
1046 mg_bring_tofront (dcccwin.window);
1047 return TRUE;
1048 }
1049
1050 g_snprintf(buf, sizeof(buf), _("DCC Chat List - %s"), _(DISPLAY_NAME));
1051 dcccwin.window =
1052 mg_create_generic_tab ("DCCChat", buf, FALSE, TRUE, dcc_chat_close_cb,
1053 NULL, 550, 180, &vbox, 0);
1054 gtkutil_destroy_on_esc (dcccwin.window);
1055 gtk_container_set_border_width (GTK_CONTAINER (dcccwin.window), 3);
1056 gtk_box_set_spacing (GTK_BOX (vbox), 3);
1057
1058 store = gtk_list_store_new (CN_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
1059 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
1060 G_TYPE_POINTER, GDK_TYPE_COLOR);
1061 view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1);
1062
1063 dcc_add_column (view, CCOL_STATUS, CCOL_COLOR, _("Status"), FALSE);
1064 dcc_add_column (view, CCOL_NICK, CCOL_COLOR, _("Nick"), FALSE);
1065 dcc_add_column (view, CCOL_RECV, CCOL_COLOR, _("Recv"), TRUE);
1066 dcc_add_column (view, CCOL_SENT, CCOL_COLOR, _("Sent"), TRUE);
1067 dcc_add_column (view, CCOL_START, CCOL_COLOR, _("Start Time"), FALSE);
1068
1069 gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 1), TRUE);
1070 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
1071
1072 dcccwin.list = view;
1073 dcccwin.store = store;
1074 dcccwin.sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1075 gtk_tree_selection_set_mode (dcccwin.sel, GTK_SELECTION_MULTIPLE);
1076
1077 g_signal_connect (G_OBJECT (dcccwin.sel), "changed",
1078 G_CALLBACK (dcc_chat_row_cb), NULL);
1079 /* double click */
1080 g_signal_connect (G_OBJECT (view), "row-activated",
1081 G_CALLBACK (dcc_chat_dclick_cb), NULL);
1082
1083 bbox = gtk_hbutton_box_new ();
1084 gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
1085 gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 2);
1086
1087 dcccwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_chat_clicked, 0, _("Abort"));
1088 dcccwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_chat_clicked, 0, _("Accept"));
1089 gtk_widget_set_sensitive (dcccwin.accept_button, FALSE);
1090 gtk_widget_set_sensitive (dcccwin.abort_button, FALSE);
1091
1092 dcc_chat_fill_win ();
1093 gtk_widget_show_all (dcccwin.window);
1094
1095 return FALSE;
1096 }
1097
1098 void
fe_dcc_add(struct DCC * dcc)1099 fe_dcc_add (struct DCC *dcc)
1100 {
1101 switch (dcc->type)
1102 {
1103 case TYPE_RECV:
1104 if (dccfwin.window && (view_mode & VIEW_DOWNLOAD))
1105 dcc_append (dcc, dccfwin.store, TRUE);
1106 break;
1107
1108 case TYPE_SEND:
1109 if (dccfwin.window && (view_mode & VIEW_UPLOAD))
1110 dcc_append (dcc, dccfwin.store, TRUE);
1111 break;
1112
1113 default: /* chat */
1114 if (dcccwin.window)
1115 dcc_chat_append (dcc, dcccwin.store, TRUE);
1116 }
1117 }
1118
1119 void
fe_dcc_update(struct DCC * dcc)1120 fe_dcc_update (struct DCC *dcc)
1121 {
1122 switch (dcc->type)
1123 {
1124 case TYPE_SEND:
1125 dcc_update_send (dcc);
1126 break;
1127
1128 case TYPE_RECV:
1129 dcc_update_recv (dcc);
1130 break;
1131
1132 default:
1133 dcc_update_chat (dcc);
1134 }
1135
1136 if (dccfwin.window)
1137 update_clear_button_sensitivity();
1138 }
1139
1140 void
fe_dcc_remove(struct DCC * dcc)1141 fe_dcc_remove (struct DCC *dcc)
1142 {
1143 GtkTreeIter iter;
1144
1145 switch (dcc->type)
1146 {
1147 case TYPE_SEND:
1148 case TYPE_RECV:
1149 if (dccfwin.window)
1150 {
1151 if (dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC))
1152 gtk_list_store_remove (dccfwin.store, &iter);
1153 }
1154 break;
1155
1156 default: /* chat */
1157 if (dcccwin.window)
1158 {
1159 if (dcc_find_row (dcc, GTK_TREE_MODEL (dcccwin.store), &iter, CCOL_DCC))
1160 gtk_list_store_remove (dcccwin.store, &iter);
1161 }
1162 break;
1163 }
1164 }
1165