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

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

(gtk) when editing the tracker list of an existing torrent, don't let the user delete the last tracker.

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