Update 2005-12-04

This commit is contained in:
Eric Petit 2006-01-12 18:47:30 +00:00
parent 4ac92bd6d0
commit 6fb3416215
7 changed files with 634 additions and 85 deletions

View File

@ -1,6 +1,6 @@
SubDir TOP gtk ;
GTK_SRC = conf.c main.c prefs.c util.c ;
GTK_SRC = conf.c main.c prefs.c util.c gtkcellrenderertorrent.c ;
Main transmission-gtk : $(GTK_SRC) ;
LinkLibraries transmission-gtk : libtransmission.a ;

View File

@ -0,0 +1,300 @@
/* gtkcellrenderertorrent.c
* Copyright (C) 2002 Naba Kumar <kh_naba@users.sourceforge.net>
* heavily modified by Jörgen Scheibengruber <mfcn@gmx.de>
* heavily modified by Marco Pesenti Gritti <marco@gnome.org>
* heavily modified by Josh Elsasser <josh@elsasser.org> for transmission
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the GTK+ Team and others 1997-2004. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include <gtk/gtk.h>
#include "gtkcellrenderertorrent.h"
#include "util.h"
enum { PROP_0, PROP_VALUE, PROP_TEXT, PROP_LABEL };
struct _GtkCellRendererTorrentPrivate {
gfloat value;
gchar *text;
PangoAttrList *text_attrs;
gchar *label;
PangoAttrList *label_attrs;
GtkStyle *style;
};
static void
finalize(GObject *object);
static void
get_property(GObject *obj, guint id, GValue *value, GParamSpec *spec);
static void
set_property(GObject *obj, guint id, const GValue *value, GParamSpec *spec);
static void
get_size(GtkCellRenderer *cell, GtkWidget *widget, GdkRectangle *area,
gint *xoff, gint *yoff, gint *width, gint *height);
static void
render(GtkCellRenderer *cell, GdkWindow *window, GtkWidget *widget,
GdkRectangle *bg, GdkRectangle *area, GdkRectangle *exp, guint flags);
G_DEFINE_TYPE(GtkCellRendererTorrent, gtk_cell_renderer_torrent, GTK_TYPE_CELL_RENDERER);
static void
gtk_cell_renderer_torrent_class_init (GtkCellRendererTorrentClass *klass) {
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass);
object_class->finalize = finalize;
object_class->get_property = get_property;
object_class->set_property = set_property;
cell_class->get_size = get_size;
cell_class->render = render;
g_object_class_install_property(
object_class, PROP_VALUE,
g_param_spec_float("value", "Value", "Value of the torrent bar",
0.0, 1.0, 0.0, G_PARAM_READWRITE));
g_object_class_install_property(
object_class, PROP_TEXT,
g_param_spec_string ("text", "Text", "Text under the torrent bar",
/* XXX should I have NULL or "" here, and is initial strdup needed? */
NULL, G_PARAM_READWRITE));
g_object_class_install_property(
object_class, PROP_LABEL,
g_param_spec_string ("label", "Label", "Text on the torrent bar",
NULL, G_PARAM_READWRITE));
g_type_class_add_private (object_class,
sizeof (GtkCellRendererTorrentPrivate));
}
static void
gtk_cell_renderer_torrent_init(GtkCellRendererTorrent *tcell) {
tcell->priv = G_TYPE_INSTANCE_GET_PRIVATE(
tcell, GTK_TYPE_CELL_RENDERER_TORRENT, GtkCellRendererTorrentPrivate);
tcell->priv->value = 0.0;
tcell->priv->text = g_strdup("");
tcell->priv->text_attrs = NULL;
tcell->priv->label = g_strdup("");
tcell->priv->text_attrs = NULL;
tcell->priv->style = NULL;
}
GtkCellRenderer*
gtk_cell_renderer_torrent_new(void) {
return g_object_new (GTK_TYPE_CELL_RENDERER_TORRENT, NULL);
}
static void
finalize(GObject *object) {
GtkCellRendererTorrent *tcell = GTK_CELL_RENDERER_TORRENT(object);
g_free(tcell->priv->text);
g_free(tcell->priv->label);
if(NULL != tcell->priv->text_attrs)
pango_attr_list_unref(tcell->priv->text_attrs);
if(NULL != tcell->priv->label_attrs)
pango_attr_list_unref(tcell->priv->label_attrs);
if(NULL != tcell->priv->style) {
gtk_style_detach(tcell->priv->style);
gtk_style_unref(tcell->priv->style);
}
G_OBJECT_CLASS (gtk_cell_renderer_torrent_parent_class)->finalize(object);
}
static void
get_property(GObject *object, guint id, GValue *value, GParamSpec *pspec) {
GtkCellRendererTorrent *tcell = GTK_CELL_RENDERER_TORRENT (object);
switch (id) {
case PROP_VALUE:
g_value_set_float (value, tcell->priv->value);
break;
case PROP_TEXT:
g_value_set_string (value, tcell->priv->text);
break;
case PROP_LABEL:
g_value_set_string (value, tcell->priv->label);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec);
}
}
static void
set_property(GObject *obj, guint id, const GValue *value, GParamSpec *spec) {
GtkCellRendererTorrent *tcell = GTK_CELL_RENDERER_TORRENT(obj);
gchar **prop = NULL;
PangoAttrList **attrs = NULL;
/*GError *err = NULL;*/
const gchar *markup;
switch(id) {
case PROP_VALUE:
tcell->priv->value = g_value_get_float(value);
break;
case PROP_TEXT:
prop = &tcell->priv->text;
attrs = &tcell->priv->text_attrs;
/* fallthrough */
case PROP_LABEL:
if(PROP_LABEL == id) {
prop = &tcell->priv->label;
attrs = &tcell->priv->label_attrs;
}
if(NULL == (markup = g_value_get_string(value)))
markup = "";
g_free(*prop);
if(NULL != *attrs)
pango_attr_list_unref(*attrs);
*prop = g_strdup(markup);
/*
if(pango_parse_markup(markup, -1, 0, attrs, prop, NULL, &err))
break;
g_warning ("Failed to parse markup: %s", err->message);
g_error_free(err);
*/
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, spec);
}
}
static void
get_size(GtkCellRenderer *cell, GtkWidget *widget, GdkRectangle *area,
gint *xoff, gint *yoff, gint *width, gint *height) {
GtkCellRendererTorrent *tcell = GTK_CELL_RENDERER_TORRENT(cell);
/* XXX do I have to unref the context? */
PangoLayout *layout = pango_layout_new(gtk_widget_get_pango_context(widget));
PangoRectangle rect;
gint h = cell->ypad * 2;
gint w1, w2;
pango_layout_set_markup(layout, tcell->priv->label, -1);
pango_layout_get_pixel_extents(layout, NULL, &rect);
w1 = rect.width;
h += rect.height;
pango_layout_set_markup(layout, tcell->priv->text, -1);
pango_layout_get_pixel_extents(layout, NULL, &rect);
w2 = rect.width;
h += rect.height;
if(NULL != xoff)
*xoff = 0;
if(NULL != yoff)
*yoff = (area->height - h) / 2;
if(NULL != width)
*width = MAX(w1, w2) + cell->xpad * 2;
if(NULL != height)
*height = h;
g_object_unref(layout);
}
#define RECTARGS(rect) (rect).x, (rect).y, (rect).width, (rect).height
static void
render(GtkCellRenderer *cell, GdkWindow *window, GtkWidget *widget,
GdkRectangle *bg SHUTUP, GdkRectangle *area, GdkRectangle *exp SHUTUP,
guint flags) {
GtkCellRendererTorrent *tcell = GTK_CELL_RENDERER_TORRENT(cell);
PangoContext *ctx = gtk_widget_get_pango_context(widget);
PangoLayout *llayout, *tlayout;
PangoRectangle lrect, trect;
GdkRectangle bar, complete, text;
gboolean rtl;
GtkStyle *style;
/* try to use the style for GtkProgressBar */
if(NULL == tcell->priv->style)
if(NULL != (tcell->priv->style = gtk_rc_get_style_by_paths(
gtk_widget_get_settings(widget), NULL, NULL,
gtk_progress_bar_get_type())))
tcell->priv->style = gtk_style_attach(gtk_style_ref(tcell->priv->style),
window);
style = (NULL == tcell->priv->style ? widget->style : tcell->priv->style);
rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
/* get the text layouts */
llayout = pango_layout_new(ctx);
/* XXX cache parsed markup? */
pango_layout_set_markup(llayout, tcell->priv->label, -1);
pango_layout_get_pixel_extents(llayout, NULL, &lrect);
tlayout = pango_layout_new(ctx);
pango_layout_set_markup(tlayout, tcell->priv->text, -1);
pango_layout_get_pixel_extents (tlayout, NULL, &trect);
/* set up the dimensions for the bar */
bar.x = area->x + cell->xpad;
bar.y = area->y + cell->ypad +
(area->height - lrect.height - trect.height) / 2;
bar.width = area->width - cell->xpad * 2;
bar.height = lrect.height;
/* set up the dimensions for the complete portion of the bar */
complete.x = bar.x + style->xthickness;
complete.y = bar.y + style->ythickness;
complete.width = (bar.width - style->xthickness * 2) * tcell->priv->value;
complete.height = bar.height - style->ythickness * 2;
if(rtl)
complete.x += bar.width - complete.width;
/* set up the dimensions for the text under the bar */
text.x = bar.x;
text.y = bar.y + bar.height;
text.width = bar.width;
text.height = area->height - bar.height;
/* draw the background of the bar */
if(complete.width < bar.width)
gtk_paint_box(style, window, GTK_STATE_NORMAL, GTK_SHADOW_IN,
NULL, widget, "trough", RECTARGS(bar));
/* draw the complete portion of the bar */
if(0 < complete.width)
gtk_paint_box(style, window, GTK_STATE_PRELIGHT, GTK_SHADOW_OUT,
NULL, widget, "bar", RECTARGS(complete));
/* draw the text under the bar */
gtk_paint_layout(style, window, (GTK_CELL_RENDERER_SELECTED & flags ?
GTK_STATE_SELECTED : GTK_STATE_NORMAL), FALSE, &text,
widget, "cellrenderertext", text.x, text.y, tlayout);
/* free memory */
g_object_unref(llayout);
g_object_unref(tlayout);
}

View File

@ -0,0 +1,69 @@
/* gtkcellrenderertorrent.h
* Copyright (C) 2002 Naba Kumar <kh_naba@users.sourceforge.net>
* modified by Jörgen Scheibengruber <mfcn@gmx.de>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the GTK+ Team and others 1997-2004. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#ifndef __GTK_CELL_RENDERER_TORRENT_H__
#define __GTK_CELL_RENDERER_TORRENT_H__
#include <gtk/gtkcellrenderer.h>
G_BEGIN_DECLS
#define GTK_TYPE_CELL_RENDERER_TORRENT (gtk_cell_renderer_torrent_get_type ())
#define GTK_CELL_RENDERER_TORRENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_CELL_RENDERER_TORRENT, GtkCellRendererTorrent))
#define GTK_CELL_RENDERER_TORRENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_CELL_RENDERER_TORRENT, GtkCellRendererTorrentClass))
#define GTK_IS_CELL_RENDERER_TORRENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_CELL_RENDERER_TORRENT))
#define GTK_IS_CELL_RENDERER_TORRENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_CELL_RENDERER_TORRENT))
#define GTK_CELL_RENDERER_TORRENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_CELL_RENDERER_TORRENT, GtkCellRendererTorrentClass))
typedef struct _GtkCellRendererTorrent GtkCellRendererTorrent;
typedef struct _GtkCellRendererTorrentClass GtkCellRendererTorrentClass;
typedef struct _GtkCellRendererTorrentPrivate GtkCellRendererTorrentPrivate;
struct _GtkCellRendererTorrent
{
GtkCellRenderer parent_instance;
/*< private >*/
GtkCellRendererTorrentPrivate *priv;
};
struct _GtkCellRendererTorrentClass
{
GtkCellRendererClass parent_class;
/* Padding for future expansion */
void (*_gtk_reserved1) (void);
void (*_gtk_reserved2) (void);
void (*_gtk_reserved3) (void);
void (*_gtk_reserved4) (void);
};
GType gtk_cell_renderer_torrent_get_type (void) G_GNUC_CONST;
GtkCellRenderer* gtk_cell_renderer_torrent_new (void);
G_END_DECLS
#endif /* __GTK_CELL_RENDERER_TORRENT_H__ */

View File

@ -36,17 +36,20 @@
#include <gtk/gtk.h>
#include "conf.h"
#include "gtkcellrenderertorrent.h"
#include "prefs.h"
#include "transmission.h"
#include "util.h"
#define TRACKER_EXIT_TIMEOUT 30
#define TRACKER_EXIT_TIMEOUT 5
struct cbdata {
tr_handle_t *tr;
GtkWindow *wind;
GtkListStore *model;
GtkTreeView *view;
GtkStatusbar *bar;
GtkWidget **buttons;
guint timer;
};
@ -77,15 +80,23 @@ GtkWidget *
makewind_toolbar(struct cbdata *data);
GtkWidget *
makewind_list(struct cbdata *data);
void
fixbuttons(GtkTreeSelection *sel, gpointer gdata);
void
dfname(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model,
GtkTreeIter *iter, gpointer gdata);
void
dfprog(GtkTreeViewColumn *col, GtkCellRenderer *rend, GtkTreeModel *model,
GtkTreeIter *iter, gpointer gdata);
gboolean
updatemodel(gpointer gdata);
gboolean
listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata);
gboolean
listpopup(GtkWidget *widget, gpointer userdata);
listpopup(GtkWidget *widget, gpointer gdata);
void
dopopupmenu(GtkWidget *widget, GdkEventButton *event, struct cbdata *data);
dopopupmenu(GdkEventButton *event, struct cbdata *data, GtkTreeIter *iter);
void
actionclick(GtkWidget *widget, gpointer gdata);
@ -115,20 +126,22 @@ enum listfrom { FROM_BUTTON, FROM_POPUP };
#define LIST_INDEX "torrent-list-index"
struct { gint pos; const gchar *name; const gchar *id; enum listact act;
const char *ttext; const char *tpriv; }
struct { const gchar *name; const gchar *id; enum listact act; gboolean nomenu;
int avail; const char *ttext; const char *tpriv; }
actionitems[] = {
{0, "Add", GTK_STOCK_ADD, ACT_OPEN,
{"Add", GTK_STOCK_ADD, ACT_OPEN, FALSE, 0,
"Add a new torrent file", "XXX"},
{1, "Resume", GTK_STOCK_MEDIA_PLAY, ACT_START,
{"Resume", GTK_STOCK_MEDIA_PLAY, ACT_START, FALSE,
(TR_STATUS_STOPPING | TR_STATUS_PAUSE),
"Resume a torrent that has been paused", "XXX"},
{2, "Pause", GTK_STOCK_MEDIA_PAUSE, ACT_STOP,
{"Pause", GTK_STOCK_MEDIA_PAUSE, ACT_STOP, FALSE,
~(TR_STATUS_STOPPING | TR_STATUS_PAUSE),
"Pause a torrent", "XXX"},
{3, "Remove", GTK_STOCK_REMOVE, ACT_DELETE,
{"Remove", GTK_STOCK_REMOVE, ACT_DELETE, FALSE, ~0,
"Remove a torrent from the list", "XXX"},
{4, "Properties", GTK_STOCK_PROPERTIES, ACT_INFO,
{"Properties", GTK_STOCK_PROPERTIES, ACT_INFO, FALSE, ~0,
"Get additional information for a torrent", "XXX"},
{5, "Preferences", GTK_STOCK_PREFERENCES, ACT_PREF,
{"Preferences", GTK_STOCK_PREFERENCES, ACT_PREF, TRUE, 0,
"Open preferences dialog", "XXX"},
};
@ -214,6 +227,7 @@ void
makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved) {
GtkWidget *vbox = gtk_vbox_new(FALSE, 0);
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
GtkWidget *status = gtk_statusbar_new();
struct cbdata *data = g_new0(struct cbdata, 1);
GtkWidget *list;
GtkRequisition req;
@ -226,13 +240,18 @@ makewind(GtkWidget *wind, tr_handle_t *tr, GList *saved) {
/* filled in by makewind_list */
data->model = NULL;
data->view = NULL;
data->bar = GTK_STATUSBAR(status);
data->buttons = NULL;
gtk_box_pack_start(GTK_BOX(vbox), makewind_toolbar(data), FALSE, FALSE, 0);
list = makewind_list(data);
gtk_widget_size_request(list, &req);
gtk_container_add(GTK_CONTAINER(scroll), list);
gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 5);
gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0);
gtk_statusbar_push(GTK_STATUSBAR(status), 0, "");
gtk_box_pack_start(GTK_BOX(vbox), status, FALSE, FALSE, 0);
gtk_container_add(GTK_CONTAINER(wind), vbox);
gtk_window_set_default_size(GTK_WINDOW(wind), req.width, req.height);
@ -344,8 +363,11 @@ makewind_toolbar(struct cbdata *data) {
gtk_toolbar_set_tooltips(GTK_TOOLBAR(bar), TRUE);
gtk_toolbar_set_style(GTK_TOOLBAR(bar), GTK_TOOLBAR_BOTH);
data->buttons = g_new(GtkWidget*, ALEN(actionitems));
for(ii = 0; ii < ALEN(actionitems); ii++) {
item = gtk_tool_button_new_from_stock(actionitems[ii].id);
data->buttons[ii] = GTK_WIDGET(item);
gtk_tool_button_set_label(GTK_TOOL_BUTTON(item), actionitems[ii].name);
gtk_tool_item_set_tooltip(GTK_TOOL_ITEM(item), GTK_TOOLBAR(bar)->tooltips,
actionitems[ii].ttext, actionitems[ii].tpriv);
@ -354,12 +376,13 @@ makewind_toolbar(struct cbdata *data) {
g_object_set_data(G_OBJECT(item), LIST_ACTION_FROM,
GINT_TO_POINTER(FROM_BUTTON));
g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(actionclick), data);
gtk_toolbar_insert(GTK_TOOLBAR(bar), GTK_TOOL_ITEM(item), actionitems[ii].pos);
gtk_toolbar_insert(GTK_TOOLBAR(bar), item, -1);
}
return bar;
}
/* XXX check for unused data in model */
enum {MC_NAME, MC_SIZE, MC_STAT, MC_ERR, MC_PROG, MC_DRATE, MC_URATE,
MC_ETA, MC_PEERS, MC_UPEERS, MC_DPEERS, MC_PIECES, MC_DOWN, MC_UP,
MC_ROW_INDEX, MC_ROW_COUNT};
@ -368,7 +391,7 @@ GtkWidget *
makewind_list(struct cbdata *data) {
GType types[] = {
/* info->name, info->totalSize, status, error, progress */
G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INT, G_TYPE_STRING, G_TYPE_INT,
G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_INT, G_TYPE_STRING, G_TYPE_FLOAT,
/* rateDownload, rateUpload, eta, peersTotal, peersUploading */
G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT,
/* peersDownloading, pieces, downloaded, uploaded */
@ -377,10 +400,9 @@ makewind_list(struct cbdata *data) {
G_TYPE_INT};
GtkListStore *model;
GtkWidget *view;
/*GtkTreeViewColumn *col;*/
GtkTreeViewColumn *col;
GtkTreeSelection *sel;
GtkCellRenderer *rend;
GtkCellRenderer *rendprog;
GtkCellRenderer *namerend, *progrend;
assert(MC_ROW_COUNT == ALEN(types));
@ -391,53 +413,22 @@ makewind_list(struct cbdata *data) {
data->model = model;
data->view = GTK_TREE_VIEW(view);
rend = gtk_cell_renderer_text_new();
rendprog = gtk_cell_renderer_progress_new();
g_object_set(rendprog, "text", "", NULL);
namerend = gtk_cell_renderer_text_new();
col = gtk_tree_view_column_new_with_attributes("Name", namerend, NULL);
gtk_tree_view_column_set_cell_data_func(col, namerend, dfname, NULL, NULL);
gtk_tree_view_column_set_expand(col, TRUE);
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Name", rend,
"text", MC_NAME, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Size", rend,
"text", MC_SIZE, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Status", rend,
"text", MC_STAT, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Error", rend,
"text", MC_ERR, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Progress", rendprog,
"value", MC_PROG, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Download Rate", rend,
"text", MC_DRATE, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Upload Rate", rend,
"text", MC_URATE, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("ETA", rend,
"text", MC_ETA, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Peers", rend,
"text", MC_PEERS, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Seeders", rend,
"text", MC_UPEERS, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Leechers", rend,
"text", MC_DPEERS, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Downloaded", rend,
"text", MC_DOWN, NULL));
gtk_tree_view_append_column(GTK_TREE_VIEW(view),
gtk_tree_view_column_new_with_attributes("Uploaded", rend,
"text", MC_UP, NULL));
progrend = gtk_cell_renderer_torrent_new();
g_object_set(progrend, "label", "<big> fnord fnord </big>", NULL);
col = gtk_tree_view_column_new_with_attributes("Progress", progrend, NULL);
gtk_tree_view_column_set_cell_data_func(col, progrend, dfprog, NULL, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
gtk_tree_selection_set_mode(GTK_TREE_SELECTION(sel), GTK_SELECTION_SINGLE);
g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(fixbuttons), data);
g_signal_connect(G_OBJECT(view), "button-press-event",
G_CALLBACK(listclick), data);
g_signal_connect(G_OBJECT(view), "popup-menu", G_CALLBACK(listpopup), data);
@ -446,27 +437,133 @@ makewind_list(struct cbdata *data) {
return view;
}
/* disable buttons the user shouldn't be able to click on */
void
fixbuttons(GtkTreeSelection *sel, gpointer gdata) {
struct cbdata *data = gdata;
GtkTreeModel *model;
GtkTreeIter iter;
gboolean selected;
unsigned int ii;
int status;
if(NULL == sel)
sel = gtk_tree_view_get_selection(data->view);
if((selected = gtk_tree_selection_get_selected(sel, &model, &iter)))
gtk_tree_model_get(model, &iter, MC_STAT, &status, -1);
for(ii = 0; ii < ALEN(actionitems); ii++)
if(actionitems[ii].avail)
gtk_widget_set_sensitive(data->buttons[ii],
(selected && (actionitems[ii].avail & status)));
}
void
dfname(GtkTreeViewColumn *col SHUTUP, GtkCellRenderer *rend,
GtkTreeModel *model, GtkTreeIter *iter, gpointer gdata SHUTUP) {
char *name, *mb, *err, *str, *top, *bottom;
guint64 size;
gfloat prog;
int status, eta, tpeers, upeers, dpeers;
/* XXX should I worry about gtk_tree_model_get failing? */
gtk_tree_model_get(model, iter, MC_NAME, &name, MC_STAT, &status,
MC_SIZE, &size, MC_PROG, &prog, MC_ETA, &eta, MC_PEERS, &tpeers,
MC_UPEERS, &upeers, MC_DPEERS, &dpeers, -1);
if(0 > eta)
eta = 0;
if(0 > tpeers)
tpeers = 0;
if(0 > upeers)
upeers = 0;
if(0 > dpeers)
dpeers = 0;
mb = readablesize(size, 1);
prog *= 100;
if(status & TR_STATUS_CHECK)
top = g_strdup_printf("Checking existing files (%.1f%%)", prog);
else if(status & TR_STATUS_DOWNLOAD)
top = g_strdup_printf("Finishing in %02i:%02i:%02i (%.1f%%)",
eta / 60 / 60, eta / 60 % 60, eta % 60, prog);
else if(status & TR_STATUS_SEED)
top = g_strdup_printf("Seeding, uploading to %d of %d peer%s",
dpeers, tpeers, (1 == tpeers ? "" : "s"));
else if(status & TR_STATUS_STOPPING)
top = g_strdup("Stopping...");
else if(status & TR_STATUS_PAUSE)
top = g_strdup_printf("Paused (%.1f%%)", prog);
else {
top = g_strdup("");
assert("XXX unknown status");
}
if(status & TR_TRACKER_ERROR) {
gtk_tree_model_get(model, iter, MC_ERR, &err, -1);
bottom = g_strconcat("Error: ", err, NULL);
g_free(err);
}
else if(status & TR_STATUS_DOWNLOAD)
bottom = g_strdup_printf("Downloading from %i of %i peer%s",
upeers, tpeers, (1 == tpeers ? "" : "s"));
else
bottom = NULL;
str = g_markup_printf_escaped("<big>%s (%s)</big>\n<small>%s\n%s</small>",
name, mb, top, (NULL == bottom ? "" : bottom));
g_object_set(rend, "markup", str, NULL);
g_free(name);
g_free(mb);
g_free(str);
g_free(top);
g_free(bottom);
}
void
dfprog(GtkTreeViewColumn *col SHUTUP, GtkCellRenderer *rend,
GtkTreeModel *model, GtkTreeIter *iter, gpointer gdata SHUTUP) {
char *dlstr, *ulstr, *str;
gfloat prog, dl, ul;
/* XXX should I worry about gtk_tree_model_get failing? */
gtk_tree_model_get(model, iter, MC_PROG, &prog,
MC_DRATE, &dl, MC_URATE, &ul, -1);
if(0.0 > prog)
prog = 0.0;
else if(1.0 < prog)
prog = 1.0;
dlstr = readablesize(dl * 1024.0, 2);
ulstr = readablesize(ul * 1024.0, 2);
str = g_strdup_printf("<small>DL: %s/s\nUL: %s/s</small>", dlstr, ulstr);
g_object_set(rend, "text", str, "value", prog, NULL);
g_free(dlstr);
g_free(ulstr);
g_free(str);
}
gboolean
updatemodel(gpointer gdata) {
struct cbdata *data = gdata;
tr_stat_t *st;
int ii, max, prog;
int ii, max;
GtkTreeIter iter;
float up, down;
char *upstr, *downstr, *str;
max = tr_torrentStat(data->tr, &st);
for(ii = 0; ii < max; ii++) {
if(!(ii ? gtk_tree_model_iter_next(GTK_TREE_MODEL(data->model), &iter) :
gtk_tree_model_get_iter_first(GTK_TREE_MODEL(data->model), &iter)))
gtk_list_store_append(data->model, &iter);
if(0.0 > (prog = st[ii].progress * 100.0))
prog = 0;
else if(100 < prog)
prog = 100;
/* XXX find out if setting the same data emits changed signal */
gtk_list_store_set(data->model, &iter, MC_ROW_INDEX, ii,
MC_NAME, st[ii].info.name, MC_SIZE, st[ii].info.totalSize, MC_STAT, st[ii].status,
MC_ERR, st[ii].error, MC_PROG, prog, MC_DRATE, st[ii].rateDownload,
MC_URATE, st[ii].rateUpload, MC_ETA, st[ii].eta, MC_PEERS, st[ii].peersTotal,
gtk_list_store_set(
data->model, &iter, MC_ROW_INDEX, ii,
MC_NAME, st[ii].info.name, MC_SIZE, st[ii].info.totalSize,
MC_STAT, st[ii].status, MC_ERR, st[ii].error, MC_PROG, st[ii].progress,
MC_DRATE, st[ii].rateDownload, MC_URATE, st[ii].rateUpload,
MC_ETA, st[ii].eta, MC_PEERS, st[ii].peersTotal,
MC_UPEERS, st[ii].peersUploading, MC_DPEERS, st[ii].peersDownloading,
MC_DOWN, st[ii].downloaded, MC_UP, st[ii].uploaded, -1);
}
@ -477,15 +574,38 @@ updatemodel(gpointer gdata) {
while(gtk_list_store_remove(data->model, &iter))
;
/* update the status bar */
tr_torrentRates(data->tr, &up, &down);
downstr = readablesize(down * 1024.0, 2);
upstr = readablesize(up * 1024.0, 2);
str = g_strdup_printf(" Total DL: %s/s Total UL: %s/s", upstr, downstr);
gtk_statusbar_pop(data->bar, 0);
gtk_statusbar_push(data->bar, 0, str);
g_free(str);
g_free(upstr);
g_free(downstr);
/* the status of the selected item may have changed, so update the buttons */
fixbuttons(NULL, data);
return TRUE;
}
gboolean
listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata) {
listclick(GtkWidget *widget SHUTUP, GdkEventButton *event, gpointer gdata) {
struct cbdata *data = gdata;
GtkTreePath *path;
GtkTreeIter iter;
if(GDK_BUTTON_PRESS == event->type && 3 == event->button) {
dopopupmenu(widget, event, data);
if(!gtk_tree_view_get_path_at_pos(data->view, event->x, event->y, &path,
NULL, NULL, NULL))
dopopupmenu(event, data, NULL);
else {
if(gtk_tree_model_get_iter(GTK_TREE_MODEL(data->model), &iter, path))
dopopupmenu(event, data, &iter);
gtk_tree_path_free(path);
}
return TRUE;
}
@ -493,33 +613,40 @@ listclick(GtkWidget *widget, GdkEventButton *event, gpointer gdata) {
}
gboolean
listpopup(GtkWidget *widget, gpointer userdata) {
dopopupmenu(widget, NULL, userdata);
listpopup(GtkWidget *widget SHUTUP, gpointer gdata) {
struct cbdata *data = gdata;
GtkTreeSelection *sel = gtk_tree_view_get_selection(data->view);
GtkTreeModel *model;
GtkTreeIter iter;
if(!gtk_tree_selection_get_selected(sel, &model, &iter))
dopopupmenu(NULL, data, NULL);
else {
assert(model == GTK_TREE_MODEL(data->model));
dopopupmenu(NULL, data, &iter);
}
return TRUE;
}
void
dopopupmenu(GtkWidget *widget SHUTUP, GdkEventButton *event,
struct cbdata *data) {
dopopupmenu(GdkEventButton *event, struct cbdata *data, GtkTreeIter *iter) {
GtkWidget *menu = gtk_menu_new();
GtkWidget *item;
GtkTreePath *path;
GtkTreeIter iter;
unsigned int ii;
int index;
int index, status;
index = -1;
if(NULL != event && gtk_tree_view_get_path_at_pos(
data->view, event->x, event->y, &path, NULL, NULL, NULL)) {
if(gtk_tree_model_get_iter(GTK_TREE_MODEL(data->model), &iter, path))
gtk_tree_model_get(GTK_TREE_MODEL(data->model), &iter, MC_ROW_INDEX, &index, -1);
gtk_tree_path_free(path);
}
if(NULL != iter)
gtk_tree_model_get(GTK_TREE_MODEL(data->model), iter,
MC_ROW_INDEX, &index, MC_STAT, &status, -1);
/* XXX am I leaking references here? */
/* XXX can I cache this in cbdata? */
for(ii = 0; ii < ALEN(actionitems); ii++) {
if(actionitems[ii].nomenu ||
(actionitems[ii].avail &&
(0 > index || !(actionitems[ii].avail & status))))
continue;
item = gtk_menu_item_new_with_label(actionitems[ii].name);
g_object_set_data(G_OBJECT(item), LIST_ACTION,
GINT_TO_POINTER(actionitems[ii].act));
@ -583,10 +710,12 @@ actionclick(GtkWidget *widget, gpointer gdata) {
case ACT_START:
tr_torrentStart(data->tr, index);
savetorrents(data->tr, data->wind, -1, NULL);
updatemodel(data);
break;
case ACT_STOP:
tr_torrentStop(data->tr, index);
savetorrents(data->tr, data->wind, -1, NULL);
updatemodel(data);
break;
case ACT_DELETE:
/* XXX need to be able to stat just one torrent */

View File

@ -55,6 +55,23 @@ strbool(const char *str) {
return FALSE;
}
char *
readablesize(guint64 size, int decimals) {
const char *sizes[] = {"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"};
unsigned int ii;
double small = size;
for(ii = 0; ii + 1 < ALEN(sizes) && 1024.0 <= small / 1024.0; ii++)
small /= 1024.0;
if(1024.0 <= small) {
small /= 1024.0;
ii++;
}
return g_strdup_printf("%.*f %s", decimals, small, sizes[ii]);
}
gboolean
mkdir_p(const char *name, mode_t mode) {
struct stat sb;

View File

@ -43,6 +43,9 @@
gboolean
strbool(const char *str);
char *
readablesize(guint64 size, int decimals);
gboolean
mkdir_p(const char *name, mode_t mode);

View File

@ -850,11 +850,37 @@ static void sleepCallBack( void * controller, io_service_t y,
URLWithString:@"http://transmission.m0k.org/forum/"]];
}
- (BOOL) hasGrowl
{
NSFileManager * manager = [NSFileManager defaultManager];
NSString * helper = @"/Library/PreferencePanes/Growl.prefPane/"
"Contents/Resources/GrowlHelperApp.app";
if( [manager fileExistsAtPath: helper] )
{
/* Growl was installed for all users */
return YES;
}
if( [manager fileExistsAtPath: [[NSString stringWithFormat: @"~%@",
helper] stringByExpandingTildeInPath]] )
{
/* Growl was installed for this user only */
return YES;
}
return NO;
}
- (void) notifyGrowl: (NSString * ) file
{
NSString * growlScript;
NSAppleScript * appleScript;
NSDictionary * error;
if( ![self hasGrowl] )
{
return;
}
growlScript = [NSString stringWithFormat:
@"tell application \"System Events\"\n"
@ -880,6 +906,11 @@ static void sleepCallBack( void * controller, io_service_t y,
NSString * growlScript;
NSAppleScript * appleScript;
NSDictionary * error;
if( ![self hasGrowl] )
{
return;
}
growlScript = [NSString stringWithFormat:
@"tell application \"System Events\"\n"