source: trunk/gtk/tracker-list.c @ 7404

Last change on this file since 7404 was 7404, checked in by charles, 12 years ago

updated email address

File size: 15.0 KB
Line 
1/*
2 * This file Copyright (C) 2007-2008 Charles Kerr <charles@transmissionbt.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: details.c 5987 2008-06-01 01:40:32Z charles $
11 */
12
13#include <errno.h>
14#include <stddef.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <glib/gi18n.h>
18#include <gtk/gtk.h>
19
20#include <libtransmission/transmission.h>
21#include <libtransmission/utils.h> /* tr_httpIsValidURL */
22
23#include "actions.h"
24#include "details.h"
25#include "file-list.h"
26#include "tracker-list.h"
27#include "tr-torrent.h"
28#include "hig.h"
29#include "util.h"
30
31#define UPDATE_INTERVAL_MSEC 2000
32#define PAGE_KEY "page"
33
34struct tracker_page
35{
36    TrTorrent *         gtor;
37
38    GtkTreeView *       view;
39    GtkListStore *      store;
40    GtkTreeSelection *  sel;
41
42    GtkWidget *         add_button;
43    GtkWidget *         remove_button;
44    GtkWidget *         save_button;
45    GtkWidget *         revert_button;
46
47    GtkWidget *         last_scrape_time_lb;
48    GtkWidget *         last_scrape_response_lb;
49    GtkWidget *         next_scrape_countdown_lb;
50
51    GtkWidget *         last_announce_time_lb;
52    GtkWidget *         last_announce_response_lb;
53    GtkWidget *         next_announce_countdown_lb;
54    GtkWidget *         manual_announce_countdown_lb;
55};
56
57enum
58{
59    TR_COL_TIER,
60    TR_COL_ANNOUNCE,
61    TR_N_COLS
62};
63
64static void
65setTrackerChangeState( struct tracker_page * page,
66                       gboolean              changed )
67{
68    if( page->save_button )
69        gtk_widget_set_sensitive( page->save_button, changed );
70
71    if( page->revert_button )
72        gtk_widget_set_sensitive( page->revert_button, changed );
73}
74
75static GtkTreeModel*
76tracker_model_new( tr_torrent * tor )
77{
78    int             i;
79    const tr_info * inf = tr_torrentInfo( tor );
80    GtkListStore *  store = gtk_list_store_new( TR_N_COLS, G_TYPE_INT,
81                                                G_TYPE_STRING );
82
83    for( i = 0; inf && i < inf->trackerCount; ++i )
84    {
85        GtkTreeIter             iter;
86        const tr_tracker_info * tinf = inf->trackers + i;
87        gtk_list_store_append( store, &iter );
88        gtk_list_store_set( store, &iter,
89                            TR_COL_TIER, tinf->tier + 1,
90                            TR_COL_ANNOUNCE, tinf->announce,
91                            -1 );
92    }
93
94    gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( store ),
95                                          TR_COL_TIER,
96                                          GTK_SORT_ASCENDING );
97
98    return GTK_TREE_MODEL( store );
99}
100
101static void
102onTrackerSelectionChanged( GtkTreeSelection * sel,
103                           gpointer           gpage )
104{
105    struct tracker_page * page = gpage;
106    const gboolean has_selection =
107        gtk_tree_selection_count_selected_rows( sel ) > 0;
108    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
109    const int trackerCount = gtk_tree_model_iter_n_children( model, NULL );
110    const gboolean ok_to_remove = !page->gtor || ( trackerCount > 1 );
111    gtk_widget_set_sensitive( page->remove_button, has_selection && ok_to_remove );
112}
113
114static void
115onTrackerRemoveClicked( GtkButton * w UNUSED,
116                        gpointer      gpage )
117{
118    struct tracker_page * page = gpage;
119    GtkTreeModel * model;
120    GList * l;
121    GList * list = gtk_tree_selection_get_selected_rows( page->sel, &model );
122
123    /* convert the list to references */
124    for( l=list; l; l=l->next ) {
125        GtkTreePath * path = l->data;
126        l->data = gtk_tree_row_reference_new( model, path );
127        gtk_tree_path_free( path );
128    }
129
130    /* remove the selected rows */
131    for( l=list; l; l=l->next ) {
132        GtkTreePath * path = gtk_tree_row_reference_get_path( l->data );
133        GtkTreeIter iter;
134        gtk_tree_model_get_iter( model, &iter, path );
135        gtk_list_store_remove( page->store, &iter );
136        gtk_tree_path_free( path );
137        gtk_tree_row_reference_free( l->data );
138    }
139
140    setTrackerChangeState( page, TRUE );
141
142    /* cleanup */
143    g_list_free( list );
144}
145
146static void
147onTrackerAddClicked( GtkButton * w UNUSED,
148                     gpointer      gpage )
149{
150    GtkTreeIter           iter;
151    struct tracker_page * page = gpage;
152    GtkTreePath *         path;
153
154    gtk_list_store_append( page->store, &iter );
155    setTrackerChangeState( page, TRUE );
156    gtk_list_store_set( page->store, &iter,
157                        TR_COL_TIER, 1,
158                        TR_COL_ANNOUNCE, "",
159                        -1 );
160    path = gtk_tree_model_get_path( GTK_TREE_MODEL( page->store ), &iter );
161    gtk_tree_view_set_cursor( page->view, path,
162                              gtk_tree_view_get_column( page->view,
163                                                        TR_COL_ANNOUNCE ),
164                              TRUE );
165    gtk_tree_path_free( path );
166}
167
168static void
169onTrackerSaveClicked( GtkButton * w UNUSED,
170                      gpointer      gpage )
171{
172    struct tracker_page * page = gpage;
173    GtkTreeModel *        model = GTK_TREE_MODEL( page->store );
174    const int             n = gtk_tree_model_iter_n_children( model, NULL );
175
176    if( n > 0 ) /* must have at least one tracker */
177    {
178        int               i = 0;
179        GtkTreeIter       iter;
180        tr_tracker_info * trackers;
181
182        /* build the tracker list */
183        trackers = g_new0( tr_tracker_info, n );
184        if( gtk_tree_model_get_iter_first( model, &iter ) ) do
185            {
186                gtk_tree_model_get( model, &iter, TR_COL_TIER,
187                                    &trackers[i].tier,
188                                    TR_COL_ANNOUNCE, &trackers[i].announce,
189                                    -1 );
190                ++i;
191            }
192            while( gtk_tree_model_iter_next( model, &iter ) );
193
194        g_assert( i == n );
195
196        /* set the tracker list */
197        tr_torrentSetAnnounceList( tr_torrent_handle( page->gtor ),
198                                   trackers, n );
199
200
201        setTrackerChangeState( page, FALSE );
202
203        /* cleanup */
204        for( i = 0; i < n; ++i )
205            g_free( trackers[i].announce );
206        g_free( trackers );
207    }
208}
209
210static void
211onTrackerRevertClicked( GtkButton * w UNUSED,
212                        gpointer      gpage )
213{
214    struct tracker_page * page = gpage;
215    GtkTreeModel *        model =
216        tracker_model_new( tr_torrent_handle( page->gtor ) );
217
218    gtk_tree_view_set_model( page->view, model );
219    page->store = GTK_LIST_STORE( model );
220    g_object_unref( G_OBJECT( model ) );
221    setTrackerChangeState( page, FALSE );
222}
223
224static void
225onAnnounceEdited( GtkCellRendererText * renderer UNUSED,
226                  gchar *                        path_string,
227                  gchar *                        new_text,
228                  gpointer                       gpage )
229{
230    struct tracker_page * page = gpage;
231    GtkTreeModel *        model = GTK_TREE_MODEL( page->store );
232    GtkTreeIter           iter;
233    GtkTreePath *         path = gtk_tree_path_new_from_string( path_string );
234
235    if( gtk_tree_model_get_iter( model, &iter, path ) )
236    {
237        char * old_text;
238        gtk_tree_model_get( model, &iter, TR_COL_ANNOUNCE, &old_text, -1 );
239        if( tr_httpIsValidURL( new_text ) )
240        {
241            if( strcmp( old_text, new_text ) )
242            {
243                gtk_list_store_set( page->store, &iter, TR_COL_ANNOUNCE,
244                                    new_text,
245                                    -1 );
246                setTrackerChangeState( page, TRUE );
247            }
248        }
249        else if( !tr_httpIsValidURL( old_text ) )
250        {
251            /* both old and new are invalid...
252               they must've typed in an invalid URL
253               after hitting the "Add" button */
254            onTrackerRemoveClicked( NULL, page );
255            setTrackerChangeState( page, TRUE );
256        }
257        g_free( old_text );
258    }
259    gtk_tree_path_free( path );
260}
261
262static void
263onTierEdited( GtkCellRendererText  * renderer UNUSED,
264              gchar *                         path_string,
265              gchar *                         new_text,
266              gpointer                        gpage )
267{
268    struct tracker_page * page = gpage;
269    GtkTreeModel *        model = GTK_TREE_MODEL( page->store );
270    GtkTreeIter           iter;
271    GtkTreePath *         path;
272    char *                end;
273    int                   new_tier;
274
275    errno = 0;
276    new_tier = strtol( new_text, &end, 10 );
277    if( new_tier < 1 || *end || errno )
278        return;
279
280    path = gtk_tree_path_new_from_string( path_string );
281    if( gtk_tree_model_get_iter( model, &iter, path ) )
282    {
283        int old_tier;
284        gtk_tree_model_get( model, &iter, TR_COL_TIER, &old_tier, -1 );
285        if( old_tier != new_tier )
286        {
287            gtk_list_store_set( page->store, &iter, TR_COL_TIER, new_tier,
288                                -1 );
289            setTrackerChangeState( page, TRUE );
290        }
291    }
292    gtk_tree_path_free( path );
293}
294
295GtkWidget*
296tracker_list_new( TrTorrent * gtor )
297{
298    GtkWidget *           w;
299    GtkWidget *           buttons;
300    GtkWidget *           box;
301    GtkWidget *           top;
302    GtkWidget *           fr;
303    GtkTreeModel *        m;
304    GtkCellRenderer *     r;
305    GtkTreeViewColumn *   c;
306    GtkTreeSelection *    sel;
307    struct tracker_page * page = g_new0( struct tracker_page, 1 );
308
309    page->gtor = gtor;
310
311    top = gtk_hbox_new( FALSE, GUI_PAD );
312    box = gtk_vbox_new( FALSE, GUI_PAD );
313    buttons = gtk_vbox_new( TRUE, GUI_PAD );
314
315    m = tracker_model_new( tr_torrent_handle( gtor ) );
316    page->store = GTK_LIST_STORE( m );
317    w = gtk_tree_view_new_with_model( m );
318    g_signal_connect( w, "button-release-event",
319                      G_CALLBACK( on_tree_view_button_released ), NULL );
320    page->view = GTK_TREE_VIEW( w );
321    gtk_tree_view_set_enable_search( page->view, FALSE );
322    r = gtk_cell_renderer_text_new( );
323    g_object_set( G_OBJECT( r ),
324                  "editable", TRUE,
325                  NULL );
326    g_signal_connect( r, "edited",
327                      G_CALLBACK( onTierEdited ), page );
328    c = gtk_tree_view_column_new_with_attributes( _( "Tier" ), r,
329                                                  "text", TR_COL_TIER,
330                                                  NULL );
331    gtk_tree_view_column_set_sort_column_id( c, TR_COL_TIER );
332    gtk_tree_view_append_column( page->view, c );
333    r = gtk_cell_renderer_text_new( );
334    g_object_set( G_OBJECT( r ),
335                  "editable", TRUE,
336                  "ellipsize", PANGO_ELLIPSIZE_END,
337                  NULL );
338    g_signal_connect( r, "edited",
339                      G_CALLBACK( onAnnounceEdited ), page );
340    c = gtk_tree_view_column_new_with_attributes( _( "Announce URL" ), r,
341                                                  "text", TR_COL_ANNOUNCE,
342                                                  NULL );
343    gtk_tree_view_column_set_sort_column_id( c, TR_COL_ANNOUNCE );
344    gtk_tree_view_append_column( page->view, c );
345    w = gtk_scrolled_window_new( NULL, NULL );
346    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ),
347                                    GTK_POLICY_NEVER,
348                                    GTK_POLICY_AUTOMATIC );
349    sel = gtk_tree_view_get_selection( page->view );
350    page->sel = sel;
351    g_signal_connect( sel, "changed",
352                      G_CALLBACK( onTrackerSelectionChanged ), page );
353    gtk_tree_selection_set_mode( sel, GTK_SELECTION_MULTIPLE );
354    gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( page->view ) );
355    gtk_widget_set_size_request( w, -1, 133 );
356    fr = gtk_frame_new( NULL );
357    gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
358    gtk_container_add( GTK_CONTAINER( fr ), w );
359    g_object_unref( G_OBJECT( m ) );
360
361    w = gtk_button_new_from_stock( GTK_STOCK_ADD );
362    g_signal_connect( w, "clicked", G_CALLBACK( onTrackerAddClicked ), page );
363    gtk_box_pack_start( GTK_BOX( buttons ), w, TRUE, TRUE, 0 );
364    page->add_button = w;
365    w = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
366    g_signal_connect( w, "clicked", G_CALLBACK(
367                          onTrackerRemoveClicked ), page );
368    gtk_box_pack_start( GTK_BOX( buttons ), w, TRUE, TRUE, 0 );
369    page->remove_button = w;
370    if( gtor )
371    {
372        w = gtk_button_new_from_stock( GTK_STOCK_SAVE );
373        g_signal_connect( w, "clicked", G_CALLBACK(
374                              onTrackerSaveClicked ), page );
375        gtk_widget_set_sensitive( w, FALSE );
376        gtk_box_pack_start( GTK_BOX( buttons ), w, TRUE, TRUE, 0 );
377        page->save_button = w;
378
379        w = gtk_button_new_from_stock( GTK_STOCK_REVERT_TO_SAVED );
380        g_signal_connect( w, "clicked", G_CALLBACK(
381                              onTrackerRevertClicked ), page );
382        gtk_widget_set_sensitive( w, FALSE );
383        gtk_box_pack_start( GTK_BOX( buttons ), w, TRUE, TRUE, 0 );
384        page->revert_button = w;
385    }
386
387    gtk_box_pack_start( GTK_BOX( box ), buttons, FALSE, FALSE, 0 );
388    gtk_box_pack_start( GTK_BOX( top ), fr, TRUE, TRUE, 0 );
389    gtk_box_pack_start( GTK_BOX( top ), box, FALSE, FALSE, 0 );
390
391    onTrackerSelectionChanged( sel, page );
392
393    g_object_set_data_full( G_OBJECT( top ), PAGE_KEY, page, g_free );
394    return top;
395}
396
397tr_tracker_info*
398tracker_list_get_trackers( GtkWidget * list,
399                           int *       trackerCount )
400{
401    struct tracker_page * page = g_object_get_data( G_OBJECT(
402                                                        list ), PAGE_KEY );
403    GtkTreeModel *        model = GTK_TREE_MODEL( page->store );
404    const int             n = gtk_tree_model_iter_n_children( model, NULL );
405    tr_tracker_info *     trackers;
406    int                   i = 0;
407    GtkTreeIter           iter;
408
409    /* build the tracker list */
410    trackers = g_new0( tr_tracker_info, n );
411    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
412        {
413            int tier;
414            gtk_tree_model_get( model, &iter,
415                                TR_COL_TIER, &tier,
416                                TR_COL_ANNOUNCE, &trackers[i].announce,
417                                -1 );
418            /* tracker_info.tier is zero-based, but the display is 1-based */
419            trackers[i].tier = tier - 1;
420            ++i;
421        }
422        while( gtk_tree_model_iter_next( model, &iter ) );
423
424    g_assert( i == n );
425
426    *trackerCount = n;
427
428    return trackers;
429}
430
431void
432tracker_list_add_trackers( GtkWidget             * list,
433                           const tr_tracker_info * trackers,
434                           int                     trackerCount )
435{
436    int i;
437    struct tracker_page * page = g_object_get_data( G_OBJECT( list ), PAGE_KEY );
438    GtkListStore * store = page->store;
439
440    for( i=0; i<trackerCount; ++i )
441    {
442        GtkTreeIter             iter;
443        gtk_list_store_append( store, &iter );
444        gtk_list_store_set( store, &iter,
445                            TR_COL_TIER, trackers[i].tier + 1,
446                            TR_COL_ANNOUNCE, trackers[i].announce,
447                            -1 );
448    }
449}
Note: See TracBrowser for help on using the repository browser.