1160 lines
28 KiB
C
1160 lines
28 KiB
C
/* 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
|
|
update_clear_button_sensitivity (void)
|
|
{
|
|
gboolean sensitive = dcc_completed_transfer_exists ();
|
|
gtk_widget_set_sensitive (dccfwin.clear_button, sensitive);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
update_clear_button_sensitivity ();
|
|
}
|
|
|
|
/* 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
|
|
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;
|
|
}
|
|
|
|
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_get_active (GTK_TOGGLE_BUTTON (item)))
|
|
{
|
|
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);
|
|
}
|
|
|
|
if (dccfwin.window)
|
|
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;
|
|
}
|
|
}
|