/* X-Chat
* Copyright (C) 1998-2006 Peter Zelezny.
*
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#define WANTSOCKET
#define WANTARPA
#include "../common/inet.h"
#include "fe-gtk.h"
#include "../common/hexchat.h"
#include "../common/hexchatc.h"
#include "../common/fe.h"
#include "../common/util.h"
#include "../common/network.h"
#include "gtkutil.h"
#include "palette.h"
#include "maingui.h"
enum /* DCC SEND/RECV */
{
COL_TYPE,
COL_STATUS,
COL_FILE,
COL_SIZE,
COL_POS,
COL_PERC,
COL_SPEED,
COL_ETA,
COL_NICK,
COL_DCC, /* struct DCC * */
COL_COLOR, /* GdkColor */
N_COLUMNS
};
enum /* DCC CHAT */
{
CCOL_STATUS,
CCOL_NICK,
CCOL_RECV,
CCOL_SENT,
CCOL_START,
CCOL_DCC, /* struct DCC * */
CCOL_COLOR, /* GdkColor * */
CN_COLUMNS
};
struct dccwindow
{
GtkWidget *window;
GtkWidget *list;
GtkListStore *store;
GtkTreeSelection *sel;
GtkWidget *abort_button;
GtkWidget *accept_button;
GtkWidget *resume_button;
GtkWidget *open_button;
GtkWidget *clear_button; /* clears aborted and completed requests */
GtkWidget *file_label;
GtkWidget *address_label;
};
struct my_dcc_send
{
struct session *sess;
char *nick;
int maxcps;
int passive;
};
static struct dccwindow dccfwin = {NULL, }; /* file */
static struct dccwindow dcccwin = {NULL, }; /* chat */
static GdkPixbuf *pix_up = NULL; /* down arrow */
static GdkPixbuf *pix_dn = NULL; /* up arrow */
static int win_width = 600;
static int win_height = 256;
static short view_mode; /* 1=download 2=upload 3=both */
#define VIEW_DOWNLOAD 1
#define VIEW_UPLOAD 2
#define VIEW_BOTH 3
#define KILOBYTE 1024
#define MEGABYTE (KILOBYTE * 1024)
#define GIGABYTE (MEGABYTE * 1024)
static void
proper_unit (DCC_SIZE size, char *buf, int buf_len)
{
if (size <= KILOBYTE)
{
snprintf (buf, buf_len, "%"DCC_SFMT"B", size);
}
else if (size > KILOBYTE && size <= MEGABYTE)
{
snprintf (buf, buf_len, "%"DCC_SFMT"kB", size / KILOBYTE);
}
else
{
snprintf (buf, buf_len, "%.2fMB", (float)size / MEGABYTE);
}
}
static void
dcc_send_filereq_file (struct my_dcc_send *mdc, char *file)
{
if (file)
dcc_send (mdc->sess, mdc->nick, file, mdc->maxcps, mdc->passive);
else
{
free (mdc->nick);
free (mdc);
}
}
void
fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive)
{
char tbuf[128];
struct my_dcc_send *mdc;
mdc = malloc (sizeof (*mdc));
mdc->sess = sess;
mdc->nick = strdup (nick);
mdc->maxcps = maxcps;
mdc->passive = passive;
snprintf (tbuf, sizeof tbuf, _("Send file to %s"), nick);
gtkutil_file_req (tbuf, dcc_send_filereq_file, mdc, prefs.hex_dcc_dir, NULL, FRF_MULTIPLE|FRF_FILTERISINITIAL);
}
static void
dcc_prepare_row_chat (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter,
gboolean update_only)
{
static char pos[16], siz[16];
char *date;
date = ctime (&dcc->starttime);
date[strlen (date) - 1] = 0; /* remove the \n */
proper_unit (dcc->pos, pos, sizeof (pos));
proper_unit (dcc->size, siz, sizeof (siz));
gtk_list_store_set (store, iter,
CCOL_STATUS, _(dccstat[dcc->dccstat].name),
CCOL_NICK, dcc->nick,
CCOL_RECV, pos,
CCOL_SENT, siz,
CCOL_START, date,
CCOL_DCC, dcc,
CCOL_COLOR,
dccstat[dcc->dccstat].color == 1 ?
NULL :
colors + dccstat[dcc->dccstat].color,
-1);
}
static void
dcc_prepare_row_send (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter,
gboolean update_only)
{
static char pos[16], size[16], kbs[14], perc[14], eta[14];
int to_go;
float per;
if (!pix_up)
pix_up = gtk_widget_render_icon (dccfwin.window, "gtk-go-up",
GTK_ICON_SIZE_MENU, NULL);
/* percentage ack'ed */
per = (float) ((dcc->ack * 100.00) / dcc->size);
proper_unit (dcc->size, size, sizeof (size));
proper_unit (dcc->pos, pos, sizeof (pos));
snprintf (kbs, sizeof (kbs), "%.1f", ((float)dcc->cps) / 1024);
/* proper_unit (dcc->ack, ack, sizeof (ack));*/
snprintf (perc, sizeof (perc), "%.0f%%", per);
if (dcc->cps != 0)
{
to_go = (dcc->size - dcc->ack) / dcc->cps;
snprintf (eta, sizeof (eta), "%.2d:%.2d:%.2d",
to_go / 3600, (to_go / 60) % 60, to_go % 60);
} else
strcpy (eta, "--:--:--");
if (update_only)
gtk_list_store_set (store, iter,
COL_STATUS, _(dccstat[dcc->dccstat].name),
COL_POS, pos,
COL_PERC, perc,
COL_SPEED, kbs,
COL_ETA, eta,
COL_COLOR,
dccstat[dcc->dccstat].color == 1 ?
NULL :
colors + dccstat[dcc->dccstat].color,
-1);
else
gtk_list_store_set (store, iter,
COL_TYPE, pix_up,
COL_STATUS, _(dccstat[dcc->dccstat].name),
COL_FILE, file_part (dcc->file),
COL_SIZE, size,
COL_POS, pos,
COL_PERC, perc,
COL_SPEED, kbs,
COL_ETA, eta,
COL_NICK, dcc->nick,
COL_DCC, dcc,
COL_COLOR,
dccstat[dcc->dccstat].color == 1 ?
NULL :
colors + dccstat[dcc->dccstat].color,
-1);
}
static void
dcc_prepare_row_recv (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter,
gboolean update_only)
{
static char size[16], pos[16], kbs[16], perc[14], eta[16];
float per;
int to_go;
if (!pix_dn)
pix_dn = gtk_widget_render_icon (dccfwin.window, "gtk-go-down",
GTK_ICON_SIZE_MENU, NULL);
proper_unit (dcc->size, size, sizeof (size));
if (dcc->dccstat == STAT_QUEUED)
proper_unit (dcc->resumable, pos, sizeof (pos));
else
proper_unit (dcc->pos, pos, sizeof (pos));
snprintf (kbs, sizeof (kbs), "%.1f", ((float)dcc->cps) / 1024);
/* percentage recv'ed */
per = (float) ((dcc->pos * 100.00) / dcc->size);
snprintf (perc, sizeof (perc), "%.0f%%", per);
if (dcc->cps != 0)
{
to_go = (dcc->size - dcc->pos) / dcc->cps;
snprintf (eta, sizeof (eta), "%.2d:%.2d:%.2d",
to_go / 3600, (to_go / 60) % 60, to_go % 60);
} else
strcpy (eta, "--:--:--");
if (update_only)
gtk_list_store_set (store, iter,
COL_STATUS, _(dccstat[dcc->dccstat].name),
COL_POS, pos,
COL_PERC, perc,
COL_SPEED, kbs,
COL_ETA, eta,
COL_COLOR,
dccstat[dcc->dccstat].color == 1 ?
NULL :
colors + dccstat[dcc->dccstat].color,
-1);
else
gtk_list_store_set (store, iter,
COL_TYPE, pix_dn,
COL_STATUS, _(dccstat[dcc->dccstat].name),
COL_FILE, file_part (dcc->file),
COL_SIZE, size,
COL_POS, pos,
COL_PERC, perc,
COL_SPEED, kbs,
COL_ETA, eta,
COL_NICK, dcc->nick,
COL_DCC, dcc,
COL_COLOR,
dccstat[dcc->dccstat].color == 1 ?
NULL :
colors + dccstat[dcc->dccstat].color,
-1);
}
static gboolean
dcc_find_row (struct DCC *find_dcc, GtkTreeModel *model, GtkTreeIter *iter, int col)
{
struct DCC *dcc;
if (gtk_tree_model_get_iter_first (model, iter))
{
do
{
gtk_tree_model_get (model, iter, col, &dcc, -1);
if (dcc == find_dcc)
return TRUE;
}
while (gtk_tree_model_iter_next (model, iter));
}
return FALSE;
}
static void
dcc_update_recv (struct DCC *dcc)
{
GtkTreeIter iter;
if (!dccfwin.window)
return;
if (!dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC))
return;
dcc_prepare_row_recv (dcc, dccfwin.store, &iter, TRUE);
}
static void
dcc_update_chat (struct DCC *dcc)
{
GtkTreeIter iter;
if (!dcccwin.window)
return;
if (!dcc_find_row (dcc, GTK_TREE_MODEL (dcccwin.store), &iter, CCOL_DCC))
return;
dcc_prepare_row_chat (dcc, dcccwin.store, &iter, TRUE);
}
static void
dcc_update_send (struct DCC *dcc)
{
GtkTreeIter iter;
if (!dccfwin.window)
return;
if (!dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC))
return;
dcc_prepare_row_send (dcc, dccfwin.store, &iter, TRUE);
}
static void
close_dcc_file_window (GtkWindow *win, gpointer data)
{
dccfwin.window = NULL;
}
static void
dcc_append (struct DCC *dcc, GtkListStore *store, gboolean prepend)
{
GtkTreeIter iter;
if (prepend)
gtk_list_store_prepend (store, &iter);
else
gtk_list_store_append (store, &iter);
if (dcc->type == TYPE_RECV)
dcc_prepare_row_recv (dcc, store, &iter, FALSE);
else
dcc_prepare_row_send (dcc, store, &iter, FALSE);
}
/* Returns aborted and completed transfers. */
static GSList *
dcc_get_completed (void)
{
struct DCC *dcc;
GtkTreeIter iter;
GtkTreeModel *model;
GSList *completed = NULL;
model = GTK_TREE_MODEL (dccfwin.store);
if (gtk_tree_model_get_iter_first (model, &iter))
{
do
{
gtk_tree_model_get (model, &iter, COL_DCC, &dcc, -1);
if (is_dcc_completed (dcc))
completed = g_slist_prepend (completed, dcc);
} while (gtk_tree_model_iter_next (model, &iter));
}
return completed;
}
static gboolean
dcc_completed_transfer_exists (void)
{
gboolean exist;
GSList *comp_list;
comp_list = dcc_get_completed ();
exist = comp_list != NULL;
g_slist_free (comp_list);
return exist;
}
static void
dcc_fill_window (int flags)
{
struct DCC *dcc;
GSList *list;
GtkTreeIter iter;
int i = 0;
gtk_list_store_clear (GTK_LIST_STORE (dccfwin.store));
if (flags & VIEW_UPLOAD)
{
list = dcc_list;
while (list)
{
dcc = list->data;
if (dcc->type == TYPE_SEND)
{
dcc_append (dcc, dccfwin.store, FALSE);
i++;
}
list = list->next;
}
}
if (flags & VIEW_DOWNLOAD)
{
list = dcc_list;
while (list)
{
dcc = list->data;
if (dcc->type == TYPE_RECV)
{
dcc_append (dcc, dccfwin.store, FALSE);
i++;
}
list = list->next;
}
}
/* if only one entry, select it (so Accept button can work) */
if (i == 1)
{
gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dccfwin.store), &iter);
gtk_tree_selection_select_iter (dccfwin.sel, &iter);
}
else
{
gtk_widget_set_sensitive (dccfwin.clear_button, dcc_completed_transfer_exists ());
}
}
/* return list of selected DCCs */
static GSList *
treeview_get_selected (GtkTreeModel *model, GtkTreeSelection *sel, int column)
{
GtkTreeIter iter;
GSList *list = NULL;
void *ptr;
if (gtk_tree_model_get_iter_first (model, &iter))
{
do
{
if (gtk_tree_selection_iter_is_selected (sel, &iter))
{
gtk_tree_model_get (model, &iter, column, &ptr, -1);
list = g_slist_prepend (list, ptr);
}
}
while (gtk_tree_model_iter_next (model, &iter));
}
return g_slist_reverse (list);
}
static GSList *
dcc_get_selected (void)
{
return treeview_get_selected (GTK_TREE_MODEL (dccfwin.store),
dccfwin.sel, COL_DCC);
}
static void
update_clear_button_sensitivity (void)
{
gboolean sensitive = dcc_completed_transfer_exists () && !dcc_get_selected ();
gtk_widget_set_sensitive (dccfwin.clear_button, sensitive);
}
static void
resume_clicked (GtkWidget * wid, gpointer none)
{
struct DCC *dcc;
char buf[512];
GSList *list;
list = dcc_get_selected ();
if (!list)
return;
dcc = list->data;
g_slist_free (list);
if (dcc->type == TYPE_RECV && !dcc_resume (dcc))
{
switch (dcc->resume_error)
{
case 0: /* unknown error */
fe_message (_("That file is not resumable."), FE_MSG_ERROR);
break;
case 1:
snprintf (buf, sizeof (buf),
_( "Cannot access file: %s\n"
"%s.\n"
"Resuming not possible."), dcc->destfile,
errorstring (dcc->resume_errno));
fe_message (buf, FE_MSG_ERROR);
break;
case 2:
fe_message (_("File in download directory is larger "
"than file offered. Resuming not possible."), FE_MSG_ERROR);
break;
case 3:
fe_message (_("Cannot resume the same file from two people."), FE_MSG_ERROR);
}
}
}
static void
abort_clicked (GtkWidget * wid, gpointer none)
{
struct DCC *dcc;
GSList *start, *list;
start = list = dcc_get_selected ();
for (; list; list = list->next)
{
dcc = list->data;
dcc_abort (dcc->serv->front_session, dcc);
}
g_slist_free (start);
/* Enable the clear button if it wasn't already enabled */
update_clear_button_sensitivity ();
}
static void
accept_clicked (GtkWidget * wid, gpointer none)
{
struct DCC *dcc;
GSList *start, *list;
start = list = dcc_get_selected ();
for (; list; list = list->next)
{
dcc = list->data;
if (dcc->type != TYPE_SEND)
dcc_get (dcc);
}
g_slist_free (start);
}
static void
clear_completed (GtkWidget * wid, gpointer none)
{
struct DCC *dcc;
GSList *completed;
/* Make a new list of only the completed items and abort each item.
* A new list is made because calling dcc_abort removes items from the original list,
* making it impossible to iterate over that list directly.
*/
for (completed = dcc_get_completed (); completed; completed = completed->next)
{
dcc = completed->data;
dcc_abort (dcc->serv->front_session, dcc);
}
/* The data was freed by dcc_close */
g_slist_free (completed);
update_clear_button_sensitivity ();
}
static void
browse_folder (char *dir)
{
#ifdef WIN32
/* no need for file:// in ShellExecute() */
fe_open_url (dir);
#else
char buf[512];
snprintf (buf, sizeof (buf), "file://%s", dir);
fe_open_url (buf);
#endif
}
static void
browse_dcc_folder (void)
{
if (prefs.hex_dcc_completed_dir[0])
browse_folder (prefs.hex_dcc_completed_dir);
else
browse_folder (prefs.hex_dcc_dir);
}
static void
dcc_details_populate (struct DCC *dcc)
{
char buf[128];
if (!dcc)
{
gtk_label_set_text (GTK_LABEL (dccfwin.file_label), NULL);
gtk_label_set_text (GTK_LABEL (dccfwin.address_label), NULL);
return;
}
/* full path */
if (dcc->type == TYPE_RECV)
gtk_label_set_text (GTK_LABEL (dccfwin.file_label), dcc->destfile);
else
gtk_label_set_text (GTK_LABEL (dccfwin.file_label), dcc->file);
/* address and port */
snprintf (buf, sizeof (buf), "%s : %d", net_ip (dcc->addr), dcc->port);
gtk_label_set_text (GTK_LABEL (dccfwin.address_label), buf);
}
static void
dcc_row_cb (GtkTreeSelection *sel, gpointer user_data)
{
struct DCC *dcc;
GSList *list;
list = dcc_get_selected ();
if (!list)
{
gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
gtk_widget_set_sensitive (dccfwin.abort_button, FALSE);
dcc_details_populate (NULL);
return;
}
/* if a row is selected, the clear button is disabled. */
gtk_widget_set_sensitive (dccfwin.clear_button, FALSE);
gtk_widget_set_sensitive (dccfwin.abort_button, TRUE);
if (list->next) /* multi selection */
{
gtk_widget_set_sensitive (dccfwin.accept_button, TRUE);
gtk_widget_set_sensitive (dccfwin.resume_button, TRUE);
dcc_details_populate (list->data);
}
else
{
/* turn OFF/ON appropriate buttons */
dcc = list->data;
if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_RECV)
{
gtk_widget_set_sensitive (dccfwin.accept_button, TRUE);
gtk_widget_set_sensitive (dccfwin.resume_button, TRUE);
}
else
{
gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
}
dcc_details_populate (dcc);
}
g_slist_free (list);
}
static void
dcc_dclick_cb (GtkTreeView *view, GtkTreePath *path,
GtkTreeViewColumn *column, gpointer data)
{
struct DCC *dcc;
GSList *list;
list = dcc_get_selected ();
if (!list)
return;
dcc = list->data;
g_slist_free (list);
if (dcc->type == TYPE_RECV)
{
accept_clicked (0, 0);
return;
}
switch (dcc->dccstat)
{
case STAT_FAILED:
case STAT_ABORTED:
case STAT_DONE:
dcc_abort (dcc->serv->front_session, dcc);
}
}
static void
dcc_add_column (GtkWidget *tree, int textcol, int colorcol, char *title, gboolean right_justified)
{
GtkCellRenderer *renderer;
renderer = gtk_cell_renderer_text_new ();
if (right_justified)
g_object_set (G_OBJECT (renderer), "xalign", (float) 1.0, NULL);
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, title, renderer,
"text", textcol, "foreground-gdk", colorcol,
NULL);
gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
}
static GtkWidget *
dcc_detail_label (char *text, GtkWidget *box, int num)
{
GtkWidget *label;
char buf[64];
label = gtk_label_new (NULL);
snprintf (buf, sizeof (buf), "<b>%s</b>", text);
gtk_label_set_markup (GTK_LABEL (label), buf);
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
gtk_table_attach (GTK_TABLE (box), label, 0, 1, 0 + num, 1 + num, GTK_FILL, GTK_FILL, 0, 0);
label = gtk_label_new (NULL);
gtk_label_set_selectable (GTK_LABEL (label), TRUE);
gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
gtk_table_attach (GTK_TABLE (box), label, 1, 2, 0 + num, 1 + num, GTK_FILL, GTK_FILL, 0, 0);
return label;
}
static void
dcc_exp_cb (GtkWidget *exp, GtkWidget *box)
{
if (gtk_widget_get_visible (box))
{
gtk_widget_hide (box);
}
else
{
gtk_widget_show (box);
}
}
static void
dcc_toggle (GtkWidget *item, gpointer data)
{
if (GTK_TOGGLE_BUTTON (item)->active)
{
view_mode = GPOINTER_TO_INT (data);
dcc_fill_window (GPOINTER_TO_INT (data));
}
}
static gboolean
dcc_configure_cb (GtkWindow *win, GdkEventConfigure *event, gpointer data)
{
/* remember the window size */
gtk_window_get_size (win, &win_width, &win_height);
return FALSE;
}
int
fe_dcc_open_recv_win (int passive)
{
GtkWidget *radio, *table, *vbox, *bbox, *view, *exp, *detailbox;
GtkListStore *store;
GSList *group;
if (dccfwin.window)
{
if (!passive)
mg_bring_tofront (dccfwin.window);
return TRUE;
}
dccfwin.window = mg_create_generic_tab ("Transfers", _(DISPLAY_NAME": Uploads and Downloads"),
FALSE, TRUE, close_dcc_file_window, NULL,
win_width, win_height, &vbox, 0);
gtkutil_destroy_on_esc (dccfwin.window);
gtk_container_set_border_width (GTK_CONTAINER (dccfwin.window), 3);
gtk_box_set_spacing (GTK_BOX (vbox), 3);
store = gtk_list_store_new (N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_STRING, G_TYPE_POINTER, GDK_TYPE_COLOR);
view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1);
gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
/* Up/Down Icon column */
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, NULL,
gtk_cell_renderer_pixbuf_new (),
"pixbuf", COL_TYPE, NULL);
dcc_add_column (view, COL_STATUS, COL_COLOR, _("Status"), FALSE);
dcc_add_column (view, COL_FILE, COL_COLOR, _("File"), FALSE);
dcc_add_column (view, COL_SIZE, COL_COLOR, _("Size"), TRUE);
dcc_add_column (view, COL_POS, COL_COLOR, _("Position"), TRUE);
dcc_add_column (view, COL_PERC, COL_COLOR, "%", TRUE);
dcc_add_column (view, COL_SPEED, COL_COLOR, "KB/s", TRUE);
dcc_add_column (view, COL_ETA, COL_COLOR, _("ETA"), FALSE);
dcc_add_column (view, COL_NICK, COL_COLOR, _("Nick"), FALSE);
gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_FILE), TRUE);
gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_NICK), TRUE);
dccfwin.list = view;
dccfwin.store = store;
dccfwin.sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
view_mode = VIEW_BOTH;
gtk_tree_selection_set_mode (dccfwin.sel, GTK_SELECTION_MULTIPLE);
if (!prefs.hex_gui_tab_utils)
g_signal_connect (G_OBJECT (dccfwin.window), "configure_event",
G_CALLBACK (dcc_configure_cb), 0);
g_signal_connect (G_OBJECT (dccfwin.sel), "changed",
G_CALLBACK (dcc_row_cb), NULL);
/* double click */
g_signal_connect (G_OBJECT (view), "row-activated",
G_CALLBACK (dcc_dclick_cb), NULL);
table = gtk_table_new (1, 3, FALSE);
gtk_table_set_col_spacings (GTK_TABLE (table), 16);
gtk_box_pack_start (GTK_BOX (vbox), table, 0, 0, 0);
radio = gtk_radio_button_new_with_mnemonic (NULL, _("Both"));
g_signal_connect (G_OBJECT (radio), "toggled",
G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_BOTH));
gtk_table_attach (GTK_TABLE (table), radio, 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio));
radio = gtk_radio_button_new_with_mnemonic (group, _("Uploads"));
g_signal_connect (G_OBJECT (radio), "toggled",
G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_UPLOAD));
gtk_table_attach (GTK_TABLE (table), radio, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio));
radio = gtk_radio_button_new_with_mnemonic (group, _("Downloads"));
g_signal_connect (G_OBJECT (radio), "toggled",
G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_DOWNLOAD));
gtk_table_attach (GTK_TABLE (table), radio, 2, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
exp = gtk_expander_new (_("Details"));
gtk_table_attach (GTK_TABLE (table), exp, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
detailbox = gtk_table_new (3, 3, FALSE);
gtk_table_set_col_spacings (GTK_TABLE (detailbox), 6);
gtk_table_set_row_spacings (GTK_TABLE (detailbox), 2);
gtk_container_set_border_width (GTK_CONTAINER (detailbox), 6);
g_signal_connect (G_OBJECT (exp), "activate",
G_CALLBACK (dcc_exp_cb), detailbox);
gtk_table_attach (GTK_TABLE (table), detailbox, 0, 4, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
dccfwin.file_label = dcc_detail_label (_("File:"), detailbox, 0);
dccfwin.address_label = dcc_detail_label (_("Address:"), detailbox, 1);
bbox = gtk_hbutton_box_new ();
gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 2);
dccfwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_clicked, 0, _("Abort"));
dccfwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_clicked, 0, _("Accept"));
dccfwin.resume_button = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, resume_clicked, 0, _("Resume"));
dccfwin.clear_button = gtkutil_button (bbox, GTK_STOCK_CLEAR, 0, clear_completed, 0, _("Clear"));
dccfwin.open_button = gtkutil_button (bbox, 0, 0, browse_dcc_folder, 0, _("Open Folder..."));
gtk_widget_set_sensitive (dccfwin.accept_button, FALSE);
gtk_widget_set_sensitive (dccfwin.resume_button, FALSE);
gtk_widget_set_sensitive (dccfwin.abort_button, FALSE);
dcc_fill_window (3);
gtk_widget_show_all (dccfwin.window);
gtk_widget_hide (detailbox);
return FALSE;
}
int
fe_dcc_open_send_win (int passive)
{
/* combined send/recv GUI */
return fe_dcc_open_recv_win (passive);
}
/* DCC CHAT GUIs BELOW */
static GSList *
dcc_chat_get_selected (void)
{
return treeview_get_selected (GTK_TREE_MODEL (dcccwin.store),
dcccwin.sel, CCOL_DCC);
}
static void
accept_chat_clicked (GtkWidget * wid, gpointer none)
{
struct DCC *dcc;
GSList *start, *list;
start = list = dcc_chat_get_selected ();
for (; list; list = list->next)
{
dcc = list->data;
dcc_get (dcc);
}
g_slist_free (start);
}
static void
abort_chat_clicked (GtkWidget * wid, gpointer none)
{
struct DCC *dcc;
GSList *start, *list;
start = list = dcc_chat_get_selected ();
for (; list; list = list->next)
{
dcc = list->data;
dcc_abort (dcc->serv->front_session, dcc);
}
g_slist_free (start);
}
static void
dcc_chat_close_cb (void)
{
dcccwin.window = NULL;
}
static void
dcc_chat_append (struct DCC *dcc, GtkListStore *store, gboolean prepend)
{
GtkTreeIter iter;
if (prepend)
gtk_list_store_prepend (store, &iter);
else
gtk_list_store_append (store, &iter);
dcc_prepare_row_chat (dcc, store, &iter, FALSE);
}
static void
dcc_chat_fill_win (void)
{
struct DCC *dcc;
GSList *list;
GtkTreeIter iter;
int i = 0;
gtk_list_store_clear (GTK_LIST_STORE (dcccwin.store));
list = dcc_list;
while (list)
{
dcc = list->data;
if (dcc->type == TYPE_CHATSEND || dcc->type == TYPE_CHATRECV)
{
dcc_chat_append (dcc, dcccwin.store, FALSE);
i++;
}
list = list->next;
}
/* if only one entry, select it (so Accept button can work) */
if (i == 1)
{
gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dcccwin.store), &iter);
gtk_tree_selection_select_iter (dcccwin.sel, &iter);
}
}
static void
dcc_chat_row_cb (GtkTreeSelection *sel, gpointer user_data)
{
struct DCC *dcc;
GSList *list;
list = dcc_chat_get_selected ();
if (!list)
{
gtk_widget_set_sensitive (dcccwin.accept_button, FALSE);
gtk_widget_set_sensitive (dcccwin.abort_button, FALSE);
return;
}
gtk_widget_set_sensitive (dcccwin.abort_button, TRUE);
if (list->next) /* multi selection */
gtk_widget_set_sensitive (dcccwin.accept_button, TRUE);
else
{
/* turn OFF/ON appropriate buttons */
dcc = list->data;
if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_CHATRECV)
gtk_widget_set_sensitive (dcccwin.accept_button, TRUE);
else
gtk_widget_set_sensitive (dcccwin.accept_button, FALSE);
}
g_slist_free (list);
}
static void
dcc_chat_dclick_cb (GtkTreeView *view, GtkTreePath *path,
GtkTreeViewColumn *column, gpointer data)
{
accept_chat_clicked (0, 0);
}
int
fe_dcc_open_chat_win (int passive)
{
GtkWidget *view, *vbox, *bbox;
GtkListStore *store;
if (dcccwin.window)
{
if (!passive)
mg_bring_tofront (dcccwin.window);
return TRUE;
}
dcccwin.window =
mg_create_generic_tab ("DCCChat", _(DISPLAY_NAME": DCC Chat List"),
FALSE, TRUE, dcc_chat_close_cb, NULL, 550, 180, &vbox, 0);
gtkutil_destroy_on_esc (dcccwin.window);
gtk_container_set_border_width (GTK_CONTAINER (dcccwin.window), 3);
gtk_box_set_spacing (GTK_BOX (vbox), 3);
store = gtk_list_store_new (CN_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
G_TYPE_POINTER, GDK_TYPE_COLOR);
view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1);
dcc_add_column (view, CCOL_STATUS, CCOL_COLOR, _("Status"), FALSE);
dcc_add_column (view, CCOL_NICK, CCOL_COLOR, _("Nick"), FALSE);
dcc_add_column (view, CCOL_RECV, CCOL_COLOR, _("Recv"), TRUE);
dcc_add_column (view, CCOL_SENT, CCOL_COLOR, _("Sent"), TRUE);
dcc_add_column (view, CCOL_START, CCOL_COLOR, _("Start Time"), FALSE);
gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 1), TRUE);
gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE);
dcccwin.list = view;
dcccwin.store = store;
dcccwin.sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
gtk_tree_selection_set_mode (dcccwin.sel, GTK_SELECTION_MULTIPLE);
g_signal_connect (G_OBJECT (dcccwin.sel), "changed",
G_CALLBACK (dcc_chat_row_cb), NULL);
/* double click */
g_signal_connect (G_OBJECT (view), "row-activated",
G_CALLBACK (dcc_chat_dclick_cb), NULL);
bbox = gtk_hbutton_box_new ();
gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD);
gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 2);
dcccwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_chat_clicked, 0, _("Abort"));
dcccwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_chat_clicked, 0, _("Accept"));
gtk_widget_set_sensitive (dcccwin.accept_button, FALSE);
gtk_widget_set_sensitive (dcccwin.abort_button, FALSE);
dcc_chat_fill_win ();
gtk_widget_show_all (dcccwin.window);
return FALSE;
}
void
fe_dcc_add (struct DCC *dcc)
{
switch (dcc->type)
{
case TYPE_RECV:
if (dccfwin.window && (view_mode & VIEW_DOWNLOAD))
dcc_append (dcc, dccfwin.store, TRUE);
break;
case TYPE_SEND:
if (dccfwin.window && (view_mode & VIEW_UPLOAD))
dcc_append (dcc, dccfwin.store, TRUE);
break;
default: /* chat */
if (dcccwin.window)
dcc_chat_append (dcc, dcccwin.store, TRUE);
}
}
void
fe_dcc_update (struct DCC *dcc)
{
switch (dcc->type)
{
case TYPE_SEND:
dcc_update_send (dcc);
break;
case TYPE_RECV:
dcc_update_recv (dcc);
break;
default:
dcc_update_chat (dcc);
}
update_clear_button_sensitivity ();
}
void
fe_dcc_remove (struct DCC *dcc)
{
GtkTreeIter iter;
switch (dcc->type)
{
case TYPE_SEND:
case TYPE_RECV:
if (dccfwin.window)
{
if (dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC))
gtk_list_store_remove (dccfwin.store, &iter);
}
break;
default: /* chat */
if (dcccwin.window)
{
if (dcc_find_row (dcc, GTK_TREE_MODEL (dcccwin.store), &iter, CCOL_DCC))
gtk_list_store_remove (dcccwin.store, &iter);
}
break;
}
}