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

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

(trunk) fix svn properties on several files. Thanks ot Elbandi for suggesting this

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