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