transmission/gtk/tracker-list.c

437 lines
14 KiB
C

/*
* This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.com>
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id: details.c 5987 2008-06-01 01:40:32Z charles $
*/
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h> /* tr_httpIsValidURL */
#include "actions.h"
#include "details.h"
#include "file-list.h"
#include "tracker-list.h"
#include "tr-torrent.h"
#include "hig.h"
#include "util.h"
#define UPDATE_INTERVAL_MSEC 2000
#define PAGE_KEY "page"
struct tracker_page
{
TrTorrent * gtor;
GtkTreeView * view;
GtkListStore * store;
GtkTreeSelection * sel;
GtkWidget * add_button;
GtkWidget * remove_button;
GtkWidget * save_button;
GtkWidget * revert_button;
GtkWidget * last_scrape_time_lb;
GtkWidget * last_scrape_response_lb;
GtkWidget * next_scrape_countdown_lb;
GtkWidget * last_announce_time_lb;
GtkWidget * last_announce_response_lb;
GtkWidget * next_announce_countdown_lb;
GtkWidget * manual_announce_countdown_lb;
};
enum
{
TR_COL_TIER,
TR_COL_ANNOUNCE,
TR_N_COLS
};
static void
setTrackerChangeState( struct tracker_page * page,
gboolean changed )
{
if( page->save_button )
gtk_widget_set_sensitive( page->save_button, changed );
if( page->revert_button )
gtk_widget_set_sensitive( page->revert_button, changed );
}
static GtkTreeModel*
tracker_model_new( tr_torrent * tor )
{
int i;
const tr_info * inf = tr_torrentInfo( tor );
GtkListStore * store = gtk_list_store_new( TR_N_COLS, G_TYPE_INT,
G_TYPE_STRING );
for( i = 0; inf && i < inf->trackerCount; ++i )
{
GtkTreeIter iter;
const tr_tracker_info * tinf = inf->trackers + i;
gtk_list_store_append( store, &iter );
gtk_list_store_set( store, &iter,
TR_COL_TIER, tinf->tier + 1,
TR_COL_ANNOUNCE, tinf->announce,
-1 );
}
gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( store ),
TR_COL_TIER,
GTK_SORT_ASCENDING );
return GTK_TREE_MODEL( store );
}
static void
onTrackerSelectionChanged( GtkTreeSelection * sel,
gpointer gpage )
{
struct tracker_page * page = gpage;
gboolean has_selection = gtk_tree_selection_get_selected(
sel, NULL, NULL );
gboolean ok_to_remove = !page->gtor
|| gtk_tree_model_iter_n_children(
GTK_TREE_MODEL(
page->store ),
NULL )
> 1;
gtk_widget_set_sensitive( page->remove_button,
has_selection && ok_to_remove );
}
static void
onTrackerRemoveClicked( GtkButton * w UNUSED,
gpointer gpage )
{
struct tracker_page * page = gpage;
GtkTreeIter iter;
if( gtk_tree_selection_get_selected( page->sel, NULL, &iter ) )
{
gtk_list_store_remove( page->store, &iter );
setTrackerChangeState( page, TRUE );
}
}
static void
onTrackerAddClicked( GtkButton * w UNUSED,
gpointer gpage )
{
GtkTreeIter iter;
struct tracker_page * page = gpage;
GtkTreePath * path;
gtk_list_store_append( page->store, &iter );
setTrackerChangeState( page, TRUE );
gtk_list_store_set( page->store, &iter,
TR_COL_TIER, 1,
TR_COL_ANNOUNCE, "http://",
-1 );
path = gtk_tree_model_get_path( GTK_TREE_MODEL( page->store ), &iter );
gtk_tree_view_set_cursor( page->view,
path,
gtk_tree_view_get_column( page->view,
TR_COL_ANNOUNCE ),
TRUE );
gtk_tree_path_free( path );
}
static void
onTrackerSaveClicked( GtkButton * w UNUSED,
gpointer gpage )
{
struct tracker_page * page = gpage;
GtkTreeModel * model = GTK_TREE_MODEL( page->store );
const int n = gtk_tree_model_iter_n_children( model, NULL );
if( n > 0 ) /* must have at least one tracker */
{
int i = 0;
GtkTreeIter iter;
tr_tracker_info * trackers;
/* build the tracker list */
trackers = g_new0( tr_tracker_info, n );
if( gtk_tree_model_get_iter_first( model, &iter ) ) do
{
gtk_tree_model_get( model, &iter, TR_COL_TIER,
&trackers[i].tier,
TR_COL_ANNOUNCE, &trackers[i].announce,
-1 );
++i;
}
while( gtk_tree_model_iter_next( model, &iter ) );
g_assert( i == n );
/* set the tracker list */
tr_torrentSetAnnounceList( tr_torrent_handle( page->gtor ),
trackers, n );
setTrackerChangeState( page, FALSE );
/* cleanup */
for( i = 0; i < n; ++i )
g_free( trackers[i].announce );
g_free( trackers );
}
}
static void
onTrackerRevertClicked( GtkButton * w UNUSED,
gpointer gpage )
{
struct tracker_page * page = gpage;
GtkTreeModel * model =
tracker_model_new( tr_torrent_handle( page->gtor ) );
gtk_tree_view_set_model( page->view, model );
page->store = GTK_LIST_STORE( model );
g_object_unref( G_OBJECT( model ) );
setTrackerChangeState( page, FALSE );
}
static void
onAnnounceEdited( GtkCellRendererText * renderer UNUSED,
gchar * path_string,
gchar * new_text,
gpointer gpage )
{
struct tracker_page * page = gpage;
GtkTreeModel * model = GTK_TREE_MODEL( page->store );
GtkTreeIter iter;
GtkTreePath * path = gtk_tree_path_new_from_string( path_string );
if( gtk_tree_model_get_iter( model, &iter, path ) )
{
char * old_text;
gtk_tree_model_get( model, &iter, TR_COL_ANNOUNCE, &old_text, -1 );
if( tr_httpIsValidURL( new_text ) )
{
if( strcmp( old_text, new_text ) )
{
gtk_list_store_set( page->store, &iter, TR_COL_ANNOUNCE,
new_text,
-1 );
setTrackerChangeState( page, TRUE );
}
}
else if( !tr_httpIsValidURL( old_text ) )
{
/* both old and new are invalid...
they must've typed in an invalid URL
after hitting the "Add" button */
onTrackerRemoveClicked( NULL, page );
setTrackerChangeState( page, TRUE );
}
g_free( old_text );
}
gtk_tree_path_free( path );
}
static void
onTierEdited( GtkCellRendererText * renderer UNUSED,
gchar * path_string,
gchar * new_text,
gpointer gpage )
{
struct tracker_page * page = gpage;
GtkTreeModel * model = GTK_TREE_MODEL( page->store );
GtkTreeIter iter;
GtkTreePath * path;
char * end;
int new_tier;
errno = 0;
new_tier = strtol( new_text, &end, 10 );
if( new_tier < 1 || *end || errno )
return;
path = gtk_tree_path_new_from_string( path_string );
if( gtk_tree_model_get_iter( model, &iter, path ) )
{
int old_tier;
gtk_tree_model_get( model, &iter, TR_COL_TIER, &old_tier, -1 );
if( old_tier != new_tier )
{
gtk_list_store_set( page->store, &iter, TR_COL_TIER, new_tier,
-1 );
setTrackerChangeState( page, TRUE );
}
}
gtk_tree_path_free( path );
}
GtkWidget*
tracker_list_new( TrTorrent * gtor )
{
GtkWidget * w;
GtkWidget * buttons;
GtkWidget * box;
GtkWidget * top;
GtkWidget * fr;
GtkTreeModel * m;
GtkCellRenderer * r;
GtkTreeViewColumn * c;
GtkTreeSelection * sel;
struct tracker_page * page = g_new0( struct tracker_page, 1 );
page->gtor = gtor;
top = gtk_hbox_new( FALSE, GUI_PAD );
box = gtk_vbox_new( FALSE, GUI_PAD );
buttons = gtk_vbox_new( TRUE, GUI_PAD );
m = tracker_model_new( tr_torrent_handle( gtor ) );
page->store = GTK_LIST_STORE( m );
w = gtk_tree_view_new_with_model( m );
g_signal_connect( w, "button-release-event",
G_CALLBACK( on_tree_view_button_released ), NULL );
page->view = GTK_TREE_VIEW( w );
gtk_tree_view_set_enable_search( page->view, FALSE );
r = gtk_cell_renderer_text_new( );
g_object_set( G_OBJECT( r ),
"editable", TRUE,
NULL );
g_signal_connect( r, "edited",
G_CALLBACK( onTierEdited ), page );
c = gtk_tree_view_column_new_with_attributes( _( "Tier" ), r,
"text", TR_COL_TIER,
NULL );
gtk_tree_view_column_set_sort_column_id( c, TR_COL_TIER );
gtk_tree_view_append_column( page->view, c );
r = gtk_cell_renderer_text_new( );
g_object_set( G_OBJECT( r ),
"editable", TRUE,
"ellipsize", PANGO_ELLIPSIZE_END,
NULL );
g_signal_connect( r, "edited",
G_CALLBACK( onAnnounceEdited ), page );
c = gtk_tree_view_column_new_with_attributes( _( "Announce URL" ), r,
"text", TR_COL_ANNOUNCE,
NULL );
gtk_tree_view_column_set_sort_column_id( c, TR_COL_ANNOUNCE );
gtk_tree_view_append_column( page->view, c );
w = gtk_scrolled_window_new( NULL, NULL );
gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ),
GTK_POLICY_NEVER,
GTK_POLICY_AUTOMATIC );
sel = gtk_tree_view_get_selection( page->view );
page->sel = sel;
g_signal_connect( sel, "changed",
G_CALLBACK( onTrackerSelectionChanged ), page );
gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( page->view ) );
gtk_widget_set_size_request( w, -1, 133 );
fr = gtk_frame_new( NULL );
gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
gtk_container_add( GTK_CONTAINER( fr ), w );
g_object_unref( G_OBJECT( m ) );
w = gtk_button_new_from_stock( GTK_STOCK_ADD );
g_signal_connect( w, "clicked", G_CALLBACK( onTrackerAddClicked ), page );
gtk_box_pack_start_defaults( GTK_BOX( buttons ), w );
page->add_button = w;
w = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
g_signal_connect( w, "clicked", G_CALLBACK(
onTrackerRemoveClicked ), page );
gtk_box_pack_start_defaults( GTK_BOX( buttons ), w );
page->remove_button = w;
if( gtor )
{
w = gtk_button_new_from_stock( GTK_STOCK_SAVE );
g_signal_connect( w, "clicked", G_CALLBACK(
onTrackerSaveClicked ), page );
gtk_widget_set_sensitive( w, FALSE );
gtk_box_pack_start_defaults( GTK_BOX( buttons ), w );
page->save_button = w;
w = gtk_button_new_from_stock( GTK_STOCK_REVERT_TO_SAVED );
g_signal_connect( w, "clicked", G_CALLBACK(
onTrackerRevertClicked ), page );
gtk_widget_set_sensitive( w, FALSE );
gtk_box_pack_start_defaults( GTK_BOX( buttons ), w );
page->revert_button = w;
}
gtk_box_pack_start( GTK_BOX( box ), buttons, FALSE, FALSE, 0 );
gtk_box_pack_start_defaults( GTK_BOX( top ), fr );
gtk_box_pack_start( GTK_BOX( top ), box, FALSE, FALSE, 0 );
onTrackerSelectionChanged( sel, page );
g_object_set_data_full( G_OBJECT( top ), PAGE_KEY, page, g_free );
return top;
}
tr_tracker_info*
tracker_list_get_trackers( GtkWidget * list,
int * trackerCount )
{
struct tracker_page * page = g_object_get_data( G_OBJECT(
list ), PAGE_KEY );
GtkTreeModel * model = GTK_TREE_MODEL( page->store );
const int n = gtk_tree_model_iter_n_children( model, NULL );
tr_tracker_info * trackers;
int i = 0;
GtkTreeIter iter;
/* build the tracker list */
trackers = g_new0( tr_tracker_info, n );
if( gtk_tree_model_get_iter_first( model, &iter ) ) do
{
int tier;
gtk_tree_model_get( model, &iter,
TR_COL_TIER, &tier,
TR_COL_ANNOUNCE, &trackers[i].announce,
-1 );
/* tracker_info.tier is zero-based, but the display is 1-based */
trackers[i].tier = tier - 1;
++i;
}
while( gtk_tree_model_iter_next( model, &iter ) );
g_assert( i == n );
*trackerCount = n;
return trackers;
}
void
tracker_list_add_trackers( GtkWidget * list,
const tr_tracker_info * trackers,
int trackerCount )
{
int i;
struct tracker_page * page = g_object_get_data( G_OBJECT( list ), PAGE_KEY );
GtkListStore * store = page->store;
for( i=0; i<trackerCount; ++i )
{
GtkTreeIter iter;
gtk_list_store_append( store, &iter );
gtk_list_store_set( store, &iter,
TR_COL_TIER, trackers[i].tier + 1,
TR_COL_ANNOUNCE, trackers[i].announce,
-1 );
}
}