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

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

(gtk) #992: Button icons cut off in New Torrent dialog

File size: 12.2 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}
Note: See TracBrowser for help on using the repository browser.