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

Last change on this file since 5991 was 5991, checked in by charles, 14 years ago

(gtk) unclutter the "new torrent" dialog a bit.

File size: 12.7 KB
Line 
1/*
2 * This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.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
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, gboolean changed )
66{
67    if( page->save_button )
68        gtk_widget_set_sensitive( page->save_button, changed );
69
70    if( page->revert_button )
71        gtk_widget_set_sensitive( page->revert_button, changed );
72}
73
74static GtkTreeModel*
75tracker_model_new( tr_torrent * tor )
76{
77    int i;
78    const tr_info * inf = tr_torrentInfo( tor );
79    GtkListStore * store = gtk_list_store_new( TR_N_COLS, G_TYPE_INT, G_TYPE_STRING );
80
81    for( i=0; inf && i<inf->trackerCount; ++i )
82    {
83        GtkTreeIter iter;
84        const tr_tracker_info * tinf = inf->trackers + i;
85        gtk_list_store_append( store, &iter );
86        gtk_list_store_set( store, &iter, TR_COL_TIER, tinf->tier + 1,
87                                          TR_COL_ANNOUNCE, tinf->announce,
88                                          -1 );
89    }
90
91    gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( store ),
92                                          TR_COL_TIER,
93                                          GTK_SORT_ASCENDING );
94
95    return GTK_TREE_MODEL( store );
96}
97
98static void
99onTrackerSelectionChanged( GtkTreeSelection  * sel,
100                           gpointer            gpage )
101{
102    struct tracker_page * page = gpage;
103    gboolean has_selection = gtk_tree_selection_get_selected( sel, NULL, NULL );
104    gtk_widget_set_sensitive( page->remove_button, has_selection );
105}
106
107static void
108onTrackerRemoveClicked( GtkButton * w UNUSED, gpointer gpage )
109{
110    struct tracker_page * page = gpage;
111    GtkTreeIter iter;
112    if( gtk_tree_selection_get_selected( page->sel, NULL, &iter ) ) {
113        gtk_list_store_remove( page->store, &iter );
114        setTrackerChangeState( page, TRUE );
115    }
116}
117
118static void
119onTrackerAddClicked( GtkButton * w UNUSED, gpointer gpage )
120{
121    GtkTreeIter iter;
122    struct tracker_page * page = gpage;
123    GtkTreePath * path;
124    gtk_list_store_append( page->store, &iter );
125    setTrackerChangeState( page, TRUE );
126    gtk_list_store_set( page->store, &iter, TR_COL_TIER, 1,
127                                      TR_COL_ANNOUNCE, _( "http://" ),
128                                      -1 );
129    path = gtk_tree_model_get_path( GTK_TREE_MODEL( page->store ), &iter );
130    gtk_tree_view_set_cursor( page->view,
131                              path,
132                              gtk_tree_view_get_column( page->view, TR_COL_ANNOUNCE ),
133                              TRUE );
134    gtk_tree_path_free( path );
135}
136
137static void
138onTrackerSaveClicked( GtkButton * w UNUSED, gpointer gpage )
139{
140    struct tracker_page * page = gpage;
141    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
142    const int n = gtk_tree_model_iter_n_children( model, NULL );
143
144    if( n > 0 ) /* must have at least one tracker */
145    {
146        int i = 0;
147        GtkTreeIter iter;
148        tr_tracker_info * trackers;
149
150        /* build the tracker list */
151        trackers = g_new0( tr_tracker_info, n );
152        if( gtk_tree_model_get_iter_first( model, &iter ) ) do {
153            gtk_tree_model_get( model, &iter, TR_COL_TIER, &trackers[i].tier,
154                                              TR_COL_ANNOUNCE, &trackers[i].announce,
155                                              -1 );
156            ++i;
157        } while( gtk_tree_model_iter_next( model, &iter ) );
158        g_assert( i == n );
159
160        /* set the tracker list */
161        tr_torrentSetAnnounceList( tr_torrent_handle( page->gtor ),
162                                   trackers, n );
163
164
165        setTrackerChangeState( page, FALSE );
166
167        /* cleanup */
168        for( i=0; i<n; ++i )
169            g_free( trackers[i].announce );
170        g_free( trackers );
171    }
172}
173
174static void
175onTrackerRevertClicked( GtkButton * w UNUSED, gpointer gpage )
176{
177    struct tracker_page * page = gpage;
178    GtkTreeModel * model = tracker_model_new( tr_torrent_handle( page->gtor ) );
179    gtk_tree_view_set_model( page->view, model );
180    page->store = GTK_LIST_STORE( model );
181    g_object_unref( G_OBJECT( model ) );
182    setTrackerChangeState( page, FALSE );
183}
184
185static void
186onAnnounceEdited( GtkCellRendererText * renderer UNUSED,
187                  gchar               * path_string,
188                  gchar               * new_text,
189                  gpointer              gpage )
190{
191    struct tracker_page * page = gpage;
192    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
193    GtkTreeIter iter;
194    GtkTreePath * path = gtk_tree_path_new_from_string( path_string ) ;
195    if( gtk_tree_model_get_iter( model, &iter, path ) )
196    {
197        char * old_text;
198        gtk_tree_model_get( model, &iter, TR_COL_ANNOUNCE, &old_text, -1 );
199        if( tr_httpIsValidURL( new_text ) )
200        {
201            if( strcmp( old_text, new_text ) )
202            {
203                gtk_list_store_set( page->store, &iter, TR_COL_ANNOUNCE, new_text, -1 );
204                setTrackerChangeState( page, TRUE );
205            }
206        }
207        else if( !tr_httpIsValidURL( old_text ) )
208        {
209            /* both old and new are invalid...
210               they must've typed in an invalid URL
211               after hitting the "Add" button */
212            onTrackerRemoveClicked( NULL, page );
213            setTrackerChangeState( page, TRUE );
214        }
215        g_free( old_text );
216    }
217    gtk_tree_path_free( path );
218}
219
220static void
221onTierEdited( GtkCellRendererText  * renderer UNUSED,
222              gchar                * path_string,
223              gchar                * new_text,
224              gpointer               gpage )
225{
226    struct tracker_page * page = gpage;
227    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
228    GtkTreeIter iter;
229    GtkTreePath * path;
230    char * end;
231    int new_tier;
232
233    errno = 0;
234    new_tier = strtol( new_text, &end, 10 );
235    if( new_tier<1 || *end || errno )
236        return;
237 
238    path = gtk_tree_path_new_from_string( path_string ) ;
239    if( gtk_tree_model_get_iter( model, &iter, path ) )
240    {
241        int old_tier;
242        gtk_tree_model_get( model, &iter, TR_COL_TIER, &old_tier, -1 );
243        if( old_tier != new_tier )
244        {
245            gtk_list_store_set( page->store, &iter, TR_COL_TIER, new_tier, -1 );
246            setTrackerChangeState( page, TRUE );
247        }
248    }
249    gtk_tree_path_free( path );
250}
251
252GtkWidget*
253tracker_list_new( TrTorrent * gtor, GtkPositionType buttonPos )
254{
255    GtkWidget * w;
256    GtkWidget * buttons;
257    GtkWidget * box;
258    GtkWidget * top;
259    GtkWidget * fr;
260    GtkTreeModel * m;
261    GtkCellRenderer * r;
262    GtkTreeViewColumn * c;
263    GtkTreeSelection * sel;
264    struct tracker_page * page = g_new0( struct tracker_page, 1 );
265
266    page->gtor = gtor;
267
268    if( buttonPos == GTK_POS_LEFT || buttonPos == GTK_POS_RIGHT )
269    {
270        top = gtk_hbox_new( FALSE, GUI_PAD );
271        box = gtk_vbox_new( FALSE, GUI_PAD );
272        buttons = gtk_vbox_new( TRUE, GUI_PAD );
273    }
274    else
275    {
276        top = gtk_vbox_new( FALSE, GUI_PAD );
277        box = gtk_hbox_new( FALSE, 0 );
278        buttons = gtk_hbox_new( TRUE, GUI_PAD );
279    }
280
281    m = tracker_model_new( tr_torrent_handle( gtor ) );
282    page->store = GTK_LIST_STORE( m );
283    w = gtk_tree_view_new_with_model( m );
284    page->view = GTK_TREE_VIEW( w );
285    gtk_tree_view_set_enable_search( page->view, FALSE );
286    r = gtk_cell_renderer_text_new( );
287    g_object_set( G_OBJECT( r ),
288            "editable", TRUE,
289            NULL );
290    g_signal_connect( r, "edited",
291                      G_CALLBACK( onTierEdited ), page );
292    c = gtk_tree_view_column_new_with_attributes( _( "Tier" ), r,
293            "text", TR_COL_TIER,
294            NULL );
295    gtk_tree_view_column_set_sort_column_id( c, TR_COL_TIER );
296    gtk_tree_view_append_column( page->view, c );
297    r = gtk_cell_renderer_text_new( );
298    g_object_set( G_OBJECT( r ),
299            "editable", TRUE,
300            "ellipsize", PANGO_ELLIPSIZE_END,
301            NULL );
302    g_signal_connect( r, "edited",
303                      G_CALLBACK( onAnnounceEdited ), page );
304    c = gtk_tree_view_column_new_with_attributes( _( "Announce URL" ), r,
305            "text", TR_COL_ANNOUNCE,
306            NULL );
307    gtk_tree_view_column_set_sort_column_id( c, TR_COL_ANNOUNCE );
308    gtk_tree_view_append_column( page->view, c );
309    w = gtk_scrolled_window_new( NULL, NULL );
310    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( w ),
311                                    GTK_POLICY_NEVER,
312                                    GTK_POLICY_AUTOMATIC );
313    sel = gtk_tree_view_get_selection( page->view );
314    page->sel = sel;
315    g_signal_connect( sel, "changed",
316                      G_CALLBACK( onTrackerSelectionChanged ), page );
317    gtk_container_add( GTK_CONTAINER( w ), GTK_WIDGET( page->view ) );
318    gtk_widget_set_size_request( w, -1, 133 );
319    fr = gtk_frame_new( NULL );
320    gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
321    gtk_container_add( GTK_CONTAINER( fr ), w );
322    g_object_unref( G_OBJECT( m ) );
323                 
324    w = gtk_button_new_from_stock( GTK_STOCK_ADD );
325    g_signal_connect( w, "clicked", G_CALLBACK( onTrackerAddClicked ), page );
326    gtk_box_pack_start_defaults( GTK_BOX( buttons ), w );
327    page->add_button = w;
328    w = gtk_button_new_from_stock( GTK_STOCK_REMOVE );
329    g_signal_connect( w, "clicked", G_CALLBACK( onTrackerRemoveClicked ), page );
330    gtk_box_pack_start_defaults( GTK_BOX( buttons ), w );
331    page->remove_button = w;
332    if( gtor )
333    {
334        w = gtk_button_new_from_stock( GTK_STOCK_SAVE );
335        g_signal_connect( w, "clicked", G_CALLBACK( onTrackerSaveClicked ), page );
336        gtk_widget_set_sensitive( w, FALSE );
337        gtk_box_pack_start_defaults( GTK_BOX( buttons ), w );
338        page->save_button = w;
339
340        w = gtk_button_new_from_stock( GTK_STOCK_REVERT_TO_SAVED );
341        g_signal_connect( w, "clicked", G_CALLBACK( onTrackerRevertClicked ), page );
342        gtk_widget_set_sensitive( w, FALSE );
343        gtk_box_pack_start_defaults( GTK_BOX( buttons ), w );
344        page->revert_button = w;
345    }
346
347   
348    if( buttonPos == GTK_POS_LEFT || buttonPos == GTK_POS_RIGHT ) {
349        gtk_box_pack_start( GTK_BOX( box ), buttons, FALSE, FALSE, 0 );
350    } else {
351        gtk_box_pack_end( GTK_BOX( box ), buttons, FALSE, FALSE, 0 );
352    }
353
354    if( buttonPos == GTK_POS_LEFT || buttonPos == GTK_POS_TOP ) {
355        gtk_box_pack_start( GTK_BOX( top ), box, FALSE, FALSE, 0 );
356        gtk_box_pack_start_defaults( GTK_BOX( top ), fr );
357    } else {
358        gtk_box_pack_start_defaults( GTK_BOX( top ), fr );
359        gtk_box_pack_start( GTK_BOX( top ), box, FALSE, FALSE, 0 );
360    }
361
362    onTrackerSelectionChanged( sel, page );
363
364    g_object_set_data_full( G_OBJECT( top ), "page", page, g_free );
365    return top;
366}
367
368tr_tracker_info*
369tracker_list_get_trackers( GtkWidget * list,
370                           int       * trackerCount )
371{
372    struct tracker_page * page = g_object_get_data( G_OBJECT( list ), "page" );
373    GtkTreeModel * model = GTK_TREE_MODEL( page->store );
374    const int n = gtk_tree_model_iter_n_children( model, NULL );
375    tr_tracker_info * trackers;
376    int i = 0;
377    GtkTreeIter iter;
378
379    /* build the tracker list */
380    trackers = g_new0( tr_tracker_info, n );
381    if( gtk_tree_model_get_iter_first( model, &iter ) ) do {
382        gtk_tree_model_get( model, &iter, TR_COL_TIER, &trackers[i].tier,
383                                          TR_COL_ANNOUNCE, &trackers[i].announce,
384                                          -1 );
385        ++i;
386    } while( gtk_tree_model_iter_next( model, &iter ) );
387    g_assert( i == n );
388
389    *trackerCount = n;
390
391    return trackers;
392}
393
Note: See TracBrowser for help on using the repository browser.