source: branches/1.4x/gtk/tracker-list.c @ 7463

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

(1.4x gtk) #1585: use g_timeout_add_seconds() where appropriate to group timers together for fewer scheduled wakeups

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