transmission/gtk/tr-window.c

917 lines
30 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/******************************************************************************
* $Id$
*
* Copyright (c) Transmission authors and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#include <string.h> /* strlen() */
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h> /* tr_formatter_speed_KBps () */
#include "actions.h"
#include "conf.h"
#include "filter.h"
#include "hig.h"
#include "torrent-cell-renderer.h"
#include "tr-prefs.h"
#include "tr-window.h"
#include "util.h"
typedef struct
{
GtkWidget * speedlimit_on_item[2];
GtkWidget * speedlimit_off_item[2];
GtkWidget * ratio_on_item;
GtkWidget * ratio_off_item;
GtkWidget * scroll;
GtkWidget * view;
GtkWidget * toolbar;
GtkWidget * filter;
GtkWidget * status;
GtkWidget * status_menu;
GtkLabel * ul_lb;
GtkLabel * dl_lb;
GtkLabel * stats_lb;
GtkWidget * alt_speed_image;
GtkWidget * alt_speed_button;
GtkWidget * options_menu;
GtkTreeSelection * selection;
GtkCellRenderer * renderer;
GtkTreeViewColumn * column;
GtkTreeModel * filter_model;
TrCore * core;
gulong pref_handler_id;
}
PrivateData;
static G_DEFINE_QUARK (private-data, private_data)
static PrivateData*
get_private_data (GtkWindow * w)
{
return g_object_get_qdata (G_OBJECT (w), private_data_quark ());
}
/***
****
***/
static void
on_popup_menu (GtkWidget * self UNUSED,
GdkEventButton * event)
{
GtkWidget * menu = gtr_action_get_widget ("/main-window-popup");
gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
(event ? event->button : 0),
(event ? event->time : 0));
}
static void
view_row_activated (GtkTreeView * tree_view UNUSED,
GtkTreePath * path UNUSED,
GtkTreeViewColumn * column UNUSED,
gpointer user_data UNUSED)
{
gtr_action_activate ("show-torrent-properties");
}
static gboolean
tree_view_search_equal_func (GtkTreeModel * model,
gint column UNUSED,
const gchar * key,
GtkTreeIter * iter,
gpointer search_data UNUSED)
{
gboolean match;
char * lower;
const char * name = NULL;
lower = g_strstrip (g_utf8_strdown (key, -1));
gtk_tree_model_get (model, iter, MC_NAME_COLLATED, &name, -1);
match = strstr (name, lower) != NULL;
g_free (lower);
return !match;
}
static GtkWidget*
makeview (PrivateData * p)
{
GtkWidget * view;
GtkTreeViewColumn * col;
GtkTreeSelection * sel;
GtkCellRenderer * r;
GtkTreeView * tree_view;
view = gtk_tree_view_new ();
tree_view = GTK_TREE_VIEW (view);
gtk_tree_view_set_search_column (tree_view, MC_NAME_COLLATED);
gtk_tree_view_set_search_equal_func (tree_view,
tree_view_search_equal_func,
NULL, NULL);
gtk_tree_view_set_headers_visible (tree_view, FALSE);
gtk_tree_view_set_fixed_height_mode (tree_view, TRUE);
p->selection = gtk_tree_view_get_selection (tree_view);
p->column = col = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
"title", _("Torrent"),
"resizable", TRUE,
"sizing", GTK_TREE_VIEW_COLUMN_FIXED,
NULL));
p->renderer = r = torrent_cell_renderer_new ();
gtk_tree_view_column_pack_start (col, r, FALSE);
gtk_tree_view_column_add_attribute (col, r, "torrent", MC_TORRENT);
gtk_tree_view_column_add_attribute (col, r, "piece-upload-speed", MC_SPEED_UP);
gtk_tree_view_column_add_attribute (col, r, "piece-download-speed", MC_SPEED_DOWN);
gtk_tree_view_append_column (tree_view, col);
g_object_set (r, "xpad", GUI_PAD_SMALL, "ypad", GUI_PAD_SMALL, NULL);
gtk_tree_view_set_rules_hint (tree_view, TRUE);
sel = gtk_tree_view_get_selection (tree_view);
gtk_tree_selection_set_mode (GTK_TREE_SELECTION (sel),
GTK_SELECTION_MULTIPLE);
g_signal_connect (view, "popup-menu",
G_CALLBACK (on_popup_menu), NULL);
g_signal_connect (view, "button-press-event",
G_CALLBACK (on_tree_view_button_pressed),
(void *) on_popup_menu);
g_signal_connect (view, "button-release-event",
G_CALLBACK (on_tree_view_button_released), NULL);
g_signal_connect (view, "row-activated",
G_CALLBACK (view_row_activated), NULL);
gtk_tree_view_set_model (tree_view, p->filter_model);
g_object_unref (p->filter_model);
return view;
}
static void syncAltSpeedButton (PrivateData * p);
static void
prefsChanged (TrCore * core UNUSED,
const tr_quark key,
gpointer wind)
{
gboolean isEnabled;
PrivateData * p = get_private_data (GTK_WINDOW (wind));
switch (key)
{
case TR_KEY_compact_view:
g_object_set (p->renderer, "compact", gtr_pref_flag_get (key), NULL);
/* since the cell size has changed, we need gtktreeview to revalidate
* its fixed-height mode values. Unfortunately there's not an API call
* for that, but it *does* revalidate when it thinks the style's been tweaked */
g_signal_emit_by_name (p->view, "style-updated", NULL, NULL);
break;
case TR_KEY_show_statusbar:
isEnabled = gtr_pref_flag_get (key);
g_object_set (p->status, "visible", isEnabled, NULL);
break;
case TR_KEY_show_filterbar:
isEnabled = gtr_pref_flag_get (key);
g_object_set (p->filter, "visible", isEnabled, NULL);
break;
case TR_KEY_show_toolbar:
isEnabled = gtr_pref_flag_get (key);
g_object_set (p->toolbar, "visible", isEnabled, NULL);
break;
case TR_KEY_statusbar_stats:
gtr_window_refresh (wind);
break;
case TR_KEY_alt_speed_enabled:
case TR_KEY_alt_speed_up:
case TR_KEY_alt_speed_down:
syncAltSpeedButton (p);
break;
default:
break;
}
}
static void
privateFree (gpointer vprivate)
{
PrivateData * p = vprivate;
g_signal_handler_disconnect (p->core, p->pref_handler_id);
g_free (p);
}
static void
onYinYangReleased (GtkWidget * w UNUSED, gpointer vprivate)
{
PrivateData * p = vprivate;
gtk_menu_popup (GTK_MENU (p->status_menu),
NULL, NULL, NULL, NULL, 0,
gtk_get_current_event_time ());
}
#define STATS_MODE "stats-mode"
static struct
{
const char * val, *i18n;
} stats_modes[] = {
{ "total-ratio", N_("Total Ratio") },
{ "session-ratio", N_("Session Ratio") },
{ "total-transfer", N_("Total Transfer") },
{ "session-transfer", N_("Session Transfer") }
};
static void
status_menu_toggled_cb (GtkCheckMenuItem * menu_item,
gpointer vprivate)
{
if (gtk_check_menu_item_get_active (menu_item))
{
PrivateData * p = vprivate;
const char * val = g_object_get_data (G_OBJECT (menu_item), STATS_MODE);
gtr_core_set_pref (p->core, TR_KEY_statusbar_stats, val);
}
}
static void
syncAltSpeedButton (PrivateData * p)
{
char u[32];
char d[32];
char * str;
const char * fmt;
const gboolean b = gtr_pref_flag_get (TR_KEY_alt_speed_enabled);
const char * stock = b ? "alt-speed-on" : "alt-speed-off";
GtkWidget * w = p->alt_speed_button;
tr_formatter_speed_KBps (u, gtr_pref_int_get (TR_KEY_alt_speed_up), sizeof (u));
tr_formatter_speed_KBps (d, gtr_pref_int_get (TR_KEY_alt_speed_down), sizeof (d));
fmt = b ? _("Click to disable Alternative Speed Limits\n (%1$s down, %2$s up)")
: _("Click to enable Alternative Speed Limits\n (%1$s down, %2$s up)");
str = g_strdup_printf (fmt, d, u);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), b);
gtk_image_set_from_stock (GTK_IMAGE (p->alt_speed_image), stock, -1);
gtk_button_set_alignment (GTK_BUTTON (w), 0.5, 0.5);
gtk_widget_set_tooltip_text (w, str);
g_free (str);
}
static void
alt_speed_toggled_cb (GtkToggleButton * button, gpointer vprivate)
{
PrivateData * p = vprivate;
const gboolean b = gtk_toggle_button_get_active (button);
gtr_core_set_pref_bool (p->core, TR_KEY_alt_speed_enabled, b);
}
/***
**** FILTER
***/
static void
findMaxAnnounceTime (GtkTreeModel * model,
GtkTreePath * path UNUSED,
GtkTreeIter * iter,
gpointer gmaxTime)
{
tr_torrent * tor;
const tr_stat * torStat;
time_t * maxTime = gmaxTime;
gtk_tree_model_get (model, iter, MC_TORRENT, &tor, -1);
torStat = tr_torrentStatCached (tor);
*maxTime = MAX (*maxTime, torStat->manualAnnounceTime);
}
static gboolean
onAskTrackerQueryTooltip (GtkWidget * widget UNUSED,
gint x UNUSED,
gint y UNUSED,
gboolean keyboard_tip UNUSED,
GtkTooltip * tooltip,
gpointer gdata)
{
gboolean handled;
time_t maxTime = 0;
PrivateData * p = gdata;
const time_t now = time (NULL);
gtk_tree_selection_selected_foreach (p->selection,
findMaxAnnounceTime,
&maxTime);
if (maxTime <= now)
{
handled = FALSE;
}
else
{
char buf[512];
char timebuf[64];
const int seconds = maxTime - now;
tr_strltime (timebuf, seconds, sizeof (timebuf));
g_snprintf (buf, sizeof (buf),
_("Tracker will allow requests in %s"), timebuf);
gtk_tooltip_set_text (tooltip, buf);
handled = TRUE;
}
return handled;
}
static gboolean
onAltSpeedToggledIdle (gpointer vp)
{
PrivateData * p = vp;
gboolean b = tr_sessionUsesAltSpeed (gtr_core_session (p->core));
gtr_core_set_pref_bool (p->core, TR_KEY_alt_speed_enabled, b);
return FALSE;
}
static void
onAltSpeedToggled (tr_session * s UNUSED, bool isEnabled UNUSED, bool byUser UNUSED, void * p)
{
gdk_threads_add_idle (onAltSpeedToggledIdle, p);
}
/***
**** Speed limit menu
***/
#define DIRECTION_KEY "direction-key"
#define ENABLED_KEY "enabled-key"
#define SPEED_KEY "speed-key"
static void
onSpeedToggled (GtkCheckMenuItem * check, gpointer vp)
{
PrivateData * p = vp;
GObject * o = G_OBJECT (check);
gboolean isEnabled = g_object_get_data (o, ENABLED_KEY) != 0;
tr_direction dir = GPOINTER_TO_INT (g_object_get_data (o, DIRECTION_KEY));
const tr_quark key = dir == TR_UP ? TR_KEY_speed_limit_up_enabled
: TR_KEY_speed_limit_down_enabled;
if (gtk_check_menu_item_get_active (check))
gtr_core_set_pref_bool (p->core, key, isEnabled);
}
static void
onSpeedSet (GtkCheckMenuItem * check, gpointer vp)
{
tr_quark key;
PrivateData * p = vp;
GObject * o = G_OBJECT (check);
const int KBps = GPOINTER_TO_INT (g_object_get_data (o, SPEED_KEY));
tr_direction dir = GPOINTER_TO_INT (g_object_get_data (o, DIRECTION_KEY));
key = dir==TR_UP ? TR_KEY_speed_limit_up : TR_KEY_speed_limit_down;
gtr_core_set_pref_int (p->core, key, KBps);
key = dir==TR_UP ? TR_KEY_speed_limit_up_enabled : TR_KEY_speed_limit_down_enabled;
gtr_core_set_pref_bool (p->core, key, TRUE);
}
static GtkWidget*
createSpeedMenu (PrivateData * p, tr_direction dir)
{
int i, n;
GObject * o;
GtkWidget * w;
GtkWidget * m;
GtkMenuShell * menu_shell;
const int speeds_KBps[] = { 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 500, 750 };
m = gtk_menu_new ();
menu_shell = GTK_MENU_SHELL (m);
w = gtk_radio_menu_item_new_with_label (NULL, _("Unlimited"));
o = G_OBJECT (w);
p->speedlimit_off_item[dir] = w;
g_object_set_data (o, DIRECTION_KEY, GINT_TO_POINTER (dir));
g_object_set_data (o, ENABLED_KEY, GINT_TO_POINTER (FALSE));
g_signal_connect (w, "toggled", G_CALLBACK (onSpeedToggled), p);
gtk_menu_shell_append (menu_shell, w);
w = gtk_radio_menu_item_new_with_label_from_widget (GTK_RADIO_MENU_ITEM (w), "");
o = G_OBJECT (w);
p->speedlimit_on_item[dir] = w;
g_object_set_data (o, DIRECTION_KEY, GINT_TO_POINTER (dir));
g_object_set_data (o, ENABLED_KEY, GINT_TO_POINTER (TRUE));
g_signal_connect (w, "toggled", G_CALLBACK (onSpeedToggled), p);
gtk_menu_shell_append (menu_shell, w);
w = gtk_separator_menu_item_new ();
gtk_menu_shell_append (menu_shell, w);
for (i=0, n=G_N_ELEMENTS (speeds_KBps); i<n; ++i)
{
char buf[128];
tr_formatter_speed_KBps (buf, speeds_KBps[i], sizeof (buf));
w = gtk_menu_item_new_with_label (buf);
o = G_OBJECT (w);
g_object_set_data (o, DIRECTION_KEY, GINT_TO_POINTER (dir));
g_object_set_data (o, SPEED_KEY, GINT_TO_POINTER (speeds_KBps[i]));
g_signal_connect (w, "activate", G_CALLBACK (onSpeedSet), p);
gtk_menu_shell_append (menu_shell, w);
}
return m;
}
/***
**** Speed limit menu
***/
#define RATIO_KEY "stock-ratio-index"
static const double stockRatios[] = { 0.25, 0.5, 0.75, 1, 1.5, 2, 3 };
static void
onRatioToggled (GtkCheckMenuItem * check, gpointer vp)
{
PrivateData * p = vp;
if (gtk_check_menu_item_get_active (check))
{
gboolean f = g_object_get_data (G_OBJECT (check), ENABLED_KEY) != 0;
gtr_core_set_pref_bool (p->core, TR_KEY_ratio_limit_enabled, f);
}
}
static void
onRatioSet (GtkCheckMenuItem * check, gpointer vp)
{
PrivateData * p = vp;
int i = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (check), RATIO_KEY));
const double ratio = stockRatios[i];
gtr_core_set_pref_double (p->core, TR_KEY_ratio_limit, ratio);
gtr_core_set_pref_bool (p->core, TR_KEY_ratio_limit_enabled, TRUE);
}
static GtkWidget*
createRatioMenu (PrivateData * p)
{
int i, n;
GtkWidget *m, *w;
GtkMenuShell * menu_shell;
m = gtk_menu_new ();
menu_shell = GTK_MENU_SHELL (m);
w = gtk_radio_menu_item_new_with_label (NULL, _("Seed Forever"));
p->ratio_off_item = w;
g_object_set_data (G_OBJECT (w), ENABLED_KEY, GINT_TO_POINTER (FALSE));
g_signal_connect (w, "toggled", G_CALLBACK (onRatioToggled), p);
gtk_menu_shell_append (menu_shell, w);
w = gtk_radio_menu_item_new_with_label_from_widget (GTK_RADIO_MENU_ITEM (w), "");
p->ratio_on_item = w;
g_object_set_data (G_OBJECT (w), ENABLED_KEY, GINT_TO_POINTER (TRUE));
g_signal_connect (w, "toggled", G_CALLBACK (onRatioToggled), p);
gtk_menu_shell_append (menu_shell, w);
w = gtk_separator_menu_item_new ();
gtk_menu_shell_append (menu_shell, w);
for (i=0, n=G_N_ELEMENTS (stockRatios); i<n; ++i)
{
char buf[128];
tr_strlratio (buf, stockRatios[i], sizeof (buf));
w = gtk_menu_item_new_with_label (buf);
g_object_set_data (G_OBJECT (w), RATIO_KEY, GINT_TO_POINTER (i));
g_signal_connect (w, "activate", G_CALLBACK (onRatioSet), p);
gtk_menu_shell_append (menu_shell, w);
}
return m;
}
/***
**** Option menu
***/
static GtkWidget*
createOptionsMenu (PrivateData * p)
{
GtkWidget * m;
GtkWidget * top = gtk_menu_new ();
GtkMenuShell * menu_shell = GTK_MENU_SHELL (top);
m = gtk_menu_item_new_with_label (_("Limit Download Speed"));
gtk_menu_item_set_submenu (GTK_MENU_ITEM (m), createSpeedMenu (p, TR_DOWN));
gtk_menu_shell_append (menu_shell, m);
m = gtk_menu_item_new_with_label (_("Limit Upload Speed"));
gtk_menu_item_set_submenu (GTK_MENU_ITEM (m), createSpeedMenu (p, TR_UP));
gtk_menu_shell_append (menu_shell, m);
m = gtk_separator_menu_item_new ();
gtk_menu_shell_append (menu_shell, m);
m = gtk_menu_item_new_with_label (_("Stop Seeding at Ratio"));
gtk_menu_item_set_submenu (GTK_MENU_ITEM (m), createRatioMenu (p));
gtk_menu_shell_append (menu_shell, m);
gtk_widget_show_all (top);
return top;
}
static void
onOptionsClicked (GtkButton * button UNUSED, gpointer vp)
{
char buf1[512];
char buf2[512];
gboolean b;
GtkWidget * w;
PrivateData * p = vp;
w = p->speedlimit_on_item[TR_DOWN];
tr_formatter_speed_KBps (buf1, gtr_pref_int_get (TR_KEY_speed_limit_down), sizeof (buf1));
gtr_label_set_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN (w))), buf1);
b = gtr_pref_flag_get (TR_KEY_speed_limit_down_enabled);
w = b ? p->speedlimit_on_item[TR_DOWN] : p->speedlimit_off_item[TR_DOWN];
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), TRUE);
w = p->speedlimit_on_item[TR_UP];
tr_formatter_speed_KBps (buf1, gtr_pref_int_get (TR_KEY_speed_limit_up), sizeof (buf1));
gtr_label_set_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN (w))), buf1);
b = gtr_pref_flag_get (TR_KEY_speed_limit_up_enabled);
w = b ? p->speedlimit_on_item[TR_UP] : p->speedlimit_off_item[TR_UP];
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), TRUE);
tr_strlratio (buf1, gtr_pref_double_get (TR_KEY_ratio_limit), sizeof (buf1));
g_snprintf (buf2, sizeof (buf2), _("Stop at Ratio (%s)"), buf1);
gtr_label_set_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN (p->ratio_on_item))), buf2);
b = gtr_pref_flag_get (TR_KEY_ratio_limit_enabled);
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (b ? p->ratio_on_item : p->ratio_off_item), TRUE);
gtk_menu_popup (GTK_MENU (p->options_menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time ());
}
/***
**** PUBLIC
***/
GtkWidget *
gtr_window_new (GtkApplication * app, GtkUIManager * ui_mgr, TrCore * core)
{
int i, n;
const char * pch, * style;
PrivateData * p;
GtkWidget * sibling = NULL;
GtkWidget * ul_lb, * dl_lb;
GtkWidget * mainmenu, *toolbar, *filter, *list, *status;
GtkWidget * vbox, *w, *self, *menu;
GtkWidget * grid_w;
GtkWindow * win;
GtkCssProvider * css_provider;
GSList * l;
GtkGrid * grid;
p = g_new0 (PrivateData, 1);
/* make the window */
self = gtk_application_window_new (app);
g_object_set_qdata_full (G_OBJECT (self), private_data_quark (), p, privateFree);
win = GTK_WINDOW (self);
gtk_window_set_title (win, g_get_application_name ());
gtk_window_set_role (win, "tr-main");
gtk_window_set_default_size (win,
gtr_pref_int_get (TR_KEY_main_window_width),
gtr_pref_int_get (TR_KEY_main_window_height));
gtk_window_move (win, gtr_pref_int_get (TR_KEY_main_window_x),
gtr_pref_int_get (TR_KEY_main_window_y));
if (gtr_pref_flag_get (TR_KEY_main_window_is_maximized))
gtk_window_maximize (win);
gtk_window_add_accel_group (win, gtk_ui_manager_get_accel_group (ui_mgr));
/* Add style provider to the window. */
/* Please move it to separate .css file if youre adding more styles here. */
style = ".tr-workarea {border-width: 1px 0; border-style: solid; border-radius: 0;}";
css_provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (css_provider, style,
strlen (style), NULL);
gtk_style_context_add_provider_for_screen(gdk_screen_get_default (),
GTK_STYLE_PROVIDER (css_provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
/* window's main container */
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add (GTK_CONTAINER (self), vbox);
/* main menu */
mainmenu = gtr_action_get_widget ("/main-window-menu");
w = gtr_action_get_widget ("/main-window-menu/torrent-menu/torrent-reannounce");
g_signal_connect (w, "query-tooltip", G_CALLBACK (onAskTrackerQueryTooltip), p);
/* toolbar */
toolbar = p->toolbar = gtr_action_get_widget ("/main-window-toolbar");
gtk_style_context_add_class (gtk_widget_get_style_context (toolbar),
GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
gtr_action_set_important ("open-torrent-toolbar", TRUE);
gtr_action_set_important ("show-torrent-properties", TRUE);
/* filter */
w = filter = p->filter = gtr_filter_bar_new (gtr_core_session (core),
gtr_core_model (core),
&p->filter_model);
gtk_container_set_border_width (GTK_CONTAINER (w), GUI_PAD_SMALL);
/* status menu */
menu = p->status_menu = gtk_menu_new ();
l = NULL;
pch = gtr_pref_string_get (TR_KEY_statusbar_stats);
for (i=0, n=G_N_ELEMENTS (stats_modes); i<n; ++i)
{
const char * val = stats_modes[i].val;
w = gtk_radio_menu_item_new_with_label (l, _ (stats_modes[i].i18n));
l = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (w));
gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), !g_strcmp0 (val, pch));
g_object_set_data (G_OBJECT (w), STATS_MODE, (gpointer)stats_modes[i].val);
g_signal_connect (w, "toggled", G_CALLBACK (status_menu_toggled_cb), p);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), w);
gtk_widget_show (w);
}
/**
*** Statusbar
**/
grid_w = status = p->status = gtk_grid_new ();
grid = GTK_GRID (grid_w);
gtk_container_set_border_width (GTK_CONTAINER (grid), GUI_PAD_SMALL);
/* gear */
w = gtk_button_new ();
gtk_container_add (GTK_CONTAINER (w), gtk_image_new_from_stock ("utilities", -1));
gtk_widget_set_tooltip_text (w, _("Options"));
gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
gtk_button_set_relief (GTK_BUTTON (w), GTK_RELIEF_NONE);
p->options_menu = createOptionsMenu (p);
g_signal_connect (w, "clicked", G_CALLBACK (onOptionsClicked), p);
sibling = w;
/* turtle */
p->alt_speed_image = gtk_image_new ();
w = p->alt_speed_button = gtk_toggle_button_new ();
gtk_button_set_image (GTK_BUTTON (w), p->alt_speed_image);
gtk_button_set_relief (GTK_BUTTON (w), GTK_RELIEF_NONE);
g_signal_connect (w, "toggled", G_CALLBACK (alt_speed_toggled_cb), p);
gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
sibling = w;
/* spacer */
w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
gtk_widget_set_hexpand (w, TRUE);
gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
sibling = w;
/* download */
w = dl_lb = gtk_label_new (NULL);
p->dl_lb = GTK_LABEL (w);
gtk_label_set_single_line_mode (p->dl_lb, TRUE);
gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
sibling = w;
/* upload */
w = ul_lb = gtk_label_new (NULL);
g_object_set (G_OBJECT(w), "margin-left", GUI_PAD, NULL);
p->ul_lb = GTK_LABEL (w);
gtk_label_set_single_line_mode (p->ul_lb, TRUE);
gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
sibling = w;
/* ratio */
w = gtk_label_new (NULL);
g_object_set (G_OBJECT(w), "margin-left", GUI_PAD_BIG, NULL);
p->stats_lb = GTK_LABEL (w);
gtk_label_set_single_line_mode (p->stats_lb, TRUE);
gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
sibling = w;
w = gtk_button_new ();
gtk_widget_set_tooltip_text (w, _("Statistics"));
gtk_container_add (GTK_CONTAINER (w), gtk_image_new_from_stock ("ratio", -1));
gtk_button_set_relief (GTK_BUTTON (w), GTK_RELIEF_NONE);
g_signal_connect (w, "clicked", G_CALLBACK (onYinYangReleased), p);
gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
sibling = w;
/* workarea */
p->view = makeview (p);
w = list = p->scroll = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (w),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (w),
GTK_SHADOW_OUT);
gtk_style_context_add_class (gtk_widget_get_style_context (w), "tr-workarea");
gtk_container_add (GTK_CONTAINER (w), p->view);
/* lay out the widgets */
gtk_box_pack_start (GTK_BOX (vbox), mainmenu, FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox), toolbar, FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox), filter, FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (vbox), list, TRUE, TRUE, 0);
gtk_box_pack_start (GTK_BOX (vbox), status, FALSE, FALSE, 0);
{
/* this is to determine the maximum width/height for the label */
int w=0, h=0;
PangoLayout * pango_layout;
pango_layout = gtk_widget_create_pango_layout (ul_lb, "999.99 kB/s");
pango_layout_get_pixel_size (pango_layout, &w, &h);
gtk_widget_set_size_request (ul_lb, w, h);
gtk_widget_set_size_request (dl_lb, w, h);
gtk_misc_set_alignment (GTK_MISC (ul_lb), 1.0, 0.5);
gtk_misc_set_alignment (GTK_MISC (dl_lb), 1.0, 0.5);
g_object_unref (G_OBJECT (pango_layout));
}
/* show all but the window */
gtk_widget_show_all (vbox);
/* listen for prefs changes that affect the window */
p->core = core;
prefsChanged (core, TR_KEY_compact_view, self);
prefsChanged (core, TR_KEY_show_filterbar, self);
prefsChanged (core, TR_KEY_show_statusbar, self);
prefsChanged (core, TR_KEY_statusbar_stats, self);
prefsChanged (core, TR_KEY_show_toolbar, self);
prefsChanged (core, TR_KEY_alt_speed_enabled, self);
p->pref_handler_id = g_signal_connect (core, "prefs-changed",
G_CALLBACK (prefsChanged), self);
tr_sessionSetAltSpeedFunc (gtr_core_session (core), onAltSpeedToggled, p);
gtr_window_refresh (GTK_WINDOW(self));
return self;
}
static void
updateStats (PrivateData * p)
{
const char * pch;
char up[32];
char down[32];
char ratio[32];
char buf[512];
struct tr_session_stats stats;
tr_session * session = gtr_core_session (p->core);
/* update the stats */
pch = gtr_pref_string_get (TR_KEY_statusbar_stats);
if (!g_strcmp0 (pch, "session-ratio"))
{
tr_sessionGetStats (session, &stats);
tr_strlratio (ratio, stats.ratio, sizeof (ratio));
g_snprintf (buf, sizeof (buf), _("Ratio: %s"), ratio);
}
else if (!g_strcmp0 (pch, "session-transfer"))
{
tr_sessionGetStats (session, &stats);
tr_strlsize (up, stats.uploadedBytes, sizeof (up));
tr_strlsize (down, stats.downloadedBytes, sizeof (down));
/* Translators: "size|" is here for disambiguation. Please remove it from your translation.
%1$s is the size of the data we've downloaded
%2$s is the size of the data we've uploaded */
g_snprintf (buf, sizeof (buf),
Q_("Down: %1$s, Up: %2$s"), down, up);
}
else if (!g_strcmp0 (pch, "total-transfer"))
{
tr_sessionGetCumulativeStats (session, &stats);
tr_strlsize (up, stats.uploadedBytes, sizeof (up));
tr_strlsize (down, stats.downloadedBytes, sizeof (down));
/* Translators: "size|" is here for disambiguation. Please remove it from your translation.
%1$s is the size of the data we've downloaded
%2$s is the size of the data we've uploaded */
g_snprintf (buf, sizeof (buf),
Q_( "size|Down: %1$s, Up: %2$s"), down, up);
}
else /* default is total-ratio */
{
tr_sessionGetCumulativeStats (session, &stats);
tr_strlratio (ratio, stats.ratio, sizeof (ratio));
g_snprintf (buf, sizeof (buf), _("Ratio: %s"), ratio);
}
gtr_label_set_text (p->stats_lb, buf);
}
static void
updateSpeeds (PrivateData * p)
{
tr_session * session = gtr_core_session (p->core);
if (session != NULL)
{
char text_str[256];
char speed_str[128];
double upSpeed = 0;
double downSpeed = 0;
int upCount = 0;
int downCount = 0;
GtkTreeIter iter;
GtkTreeModel * model = gtr_core_model (p->core);
if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do
{
int uc, dc;
double us, ds;
gtk_tree_model_get (model, &iter, MC_SPEED_UP, &us,
MC_SPEED_DOWN, &ds,
MC_ACTIVE_PEERS_UP, &uc,
MC_ACTIVE_PEERS_DOWN, &dc,
-1);
upSpeed += us;
upCount += uc;
downSpeed += ds;
downCount += dc;
}
while (gtk_tree_model_iter_next (model, &iter));
tr_formatter_speed_KBps (speed_str, downSpeed, sizeof (speed_str));
g_snprintf (text_str, sizeof(text_str), "%s %s", speed_str, gtr_get_unicode_string (GTR_UNICODE_DOWN));
gtr_label_set_text (p->dl_lb, text_str);
gtk_widget_set_visible (GTK_WIDGET (p->dl_lb), (downCount>0));
tr_formatter_speed_KBps (speed_str, upSpeed, sizeof (speed_str));
g_snprintf (text_str, sizeof(text_str), "%s %s", speed_str, gtr_get_unicode_string (GTR_UNICODE_UP));
gtr_label_set_text (p->ul_lb, text_str);
gtk_widget_set_visible (GTK_WIDGET (p->ul_lb), ((downCount>0) || (upCount>0)));
}
}
void
gtr_window_refresh (GtkWindow * self)
{
PrivateData * p = get_private_data (self);
if (p && p->core && gtr_core_session (p->core))
{
updateSpeeds (p);
updateStats (p);
}
}
GtkTreeSelection*
gtr_window_get_selection (GtkWindow * w)
{
return get_private_data (w)->selection;
}
void
gtr_window_set_busy (GtkWindow * win, gboolean isBusy)
{
GtkWidget * w = GTK_WIDGET (win);
if (w && gtk_widget_get_realized (w))
{
GdkDisplay * display = gtk_widget_get_display (w);
GdkCursor * cursor = isBusy ? gdk_cursor_new_for_display (display, GDK_WATCH) : NULL;
gdk_window_set_cursor (gtk_widget_get_window (w), cursor);
gdk_display_flush (display);
g_clear_object (&cursor);
}
}