source: trunk/gtk/makemeta-ui.c @ 11599

Last change on this file since 11599 was 11599, checked in by charles, 11 years ago

(trunk) Join the 21st century and use only 1 space at the end sentences. This commit is nearly as important as the semi-annual ones that remove trailing spaces from the ends of lines of code... :)

  • Property svn:keywords set to Date Rev Author Id
File size: 18.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: makemeta-ui.c 11599 2010-12-27 19:18:17Z charles $
11 */
12
13#include <string.h>
14
15#include <glib/gi18n.h>
16#include <gtk/gtk.h>
17
18#include <libtransmission/transmission.h>
19#include <libtransmission/makemeta.h>
20#include <libtransmission/utils.h> /* tr_formatter_mem_B() */
21
22#include "conf.h"
23#include "hig.h"
24#include "makemeta-ui.h"
25#include "tr-core.h"
26#include "tr-prefs.h"
27#include "util.h"
28
29#define FILE_CHOSEN_KEY "file-is-chosen"
30
31typedef struct
32{
33    char * target;
34    guint progress_tag;
35    GtkWidget * file_radio;
36    GtkWidget * file_chooser;
37    GtkWidget * folder_radio;
38    GtkWidget * folder_chooser;
39    GtkWidget * pieces_lb;
40    GtkWidget * destination_chooser;
41    GtkWidget * comment_check;
42    GtkWidget * comment_entry;
43    GtkWidget * private_check;
44    GtkWidget * progress_label;
45    GtkWidget * progress_bar;
46    GtkWidget * progress_dialog;
47    GtkWidget * dialog;
48    GtkTextBuffer * announce_text_buffer;
49    TrCore * core;
50    tr_metainfo_builder *  builder;
51}
52MakeMetaUI;
53
54static void
55freeMetaUI( gpointer p )
56{
57    MakeMetaUI * ui = p;
58
59    tr_metaInfoBuilderFree( ui->builder );
60    g_free( ui->target );
61    memset( ui, ~0, sizeof( MakeMetaUI ) );
62    g_free( ui );
63}
64
65static gboolean
66onProgressDialogRefresh( gpointer data )
67{
68    char * str = NULL;
69    MakeMetaUI * ui = data;
70    const tr_metainfo_builder * b = ui->builder;
71    GtkDialog * d = GTK_DIALOG( ui->progress_dialog );
72    GtkProgressBar * p = GTK_PROGRESS_BAR( ui->progress_bar );
73    const double fraction = b->pieceCount ? ((double)b->pieceIndex / b->pieceCount) : 0;
74    char * base = g_path_get_basename( b->top );
75
76    /* progress label */
77    if( !b->isDone )
78        str = g_strdup_printf( _( "Creating \"%s\"" ), base );
79    else if( b->result == TR_MAKEMETA_OK )
80        str = g_strdup_printf( _( "Created \"%s\"!" ), base );
81    else if( b->result == TR_MAKEMETA_URL )
82        str = g_strdup_printf( _( "Error: invalid announce URL \"%s\"" ), b->errfile );
83    else if( b->result == TR_MAKEMETA_CANCELLED )
84        str = g_strdup_printf( _( "Cancelled" ) );
85    else if( b->result == TR_MAKEMETA_IO_READ )
86        str = g_strdup_printf( _( "Error reading \"%s\": %s" ), b->errfile, g_strerror( b->my_errno ) );
87    else if( b->result == TR_MAKEMETA_IO_WRITE )
88        str = g_strdup_printf( _( "Error writing \"%s\": %s" ), b->errfile, g_strerror( b->my_errno ) );
89    else
90        g_assert_not_reached( );
91
92    if( str != NULL ) {
93        gtk_label_set_text( GTK_LABEL( ui->progress_label ), str );
94        g_free( str );
95    }
96
97    /* progress bar */
98    if( !b->pieceIndex )
99        str = g_strdup( "" );
100    else {
101        char sizebuf[128];
102        tr_strlsize( sizebuf, (uint64_t)b->pieceIndex *
103                              (uint64_t)b->pieceSize, sizeof( sizebuf ) );
104        /* how much data we've scanned through to generate checksums */
105        str = g_strdup_printf( _( "Scanned %s" ), sizebuf );
106    }
107    gtk_progress_bar_set_fraction( p, fraction );
108    gtk_progress_bar_set_text( p, str );
109    g_free( str );
110
111    /* buttons */
112    gtk_dialog_set_response_sensitive( d, GTK_RESPONSE_CANCEL, !b->isDone );
113    gtk_dialog_set_response_sensitive( d, GTK_RESPONSE_CLOSE, b->isDone );
114    gtk_dialog_set_response_sensitive( d, GTK_RESPONSE_ACCEPT, b->isDone && !b->result );
115
116    g_free( base );
117    return TRUE;
118}
119
120static void
121onProgressDialogDestroyed( gpointer data, GObject * dead UNUSED )
122{
123    MakeMetaUI * ui = data;
124    g_source_remove( ui->progress_tag );
125}
126
127static void
128addTorrent( MakeMetaUI * ui )
129{
130    char * path;
131    const tr_metainfo_builder * b = ui->builder;
132    tr_ctor * ctor = tr_ctorNew( tr_core_session( ui->core ) );
133
134    tr_ctorSetMetainfoFromFile( ctor, ui->target );
135
136    path = g_path_get_dirname( b->top );
137    tr_ctorSetDownloadDir( ctor, TR_FORCE, path );
138    g_free( path );
139
140    tr_core_add_ctor( ui->core, ctor );
141}
142
143static void
144onProgressDialogResponse( GtkDialog * d, int response, gpointer data )
145{
146    MakeMetaUI * ui = data;
147
148    switch( response )
149    {
150        case GTK_RESPONSE_CANCEL:
151            ui->builder->abortFlag = TRUE;
152            gtk_widget_destroy( GTK_WIDGET( d ) );
153            break;
154        case GTK_RESPONSE_ACCEPT:
155            addTorrent( ui );
156            /* fall-through */
157        case GTK_RESPONSE_CLOSE:
158            gtk_widget_destroy( ui->builder->result ? GTK_WIDGET( d ) : ui->dialog );
159            break;
160        default:
161            g_assert( 0 && "unhandled response" );
162    }
163}
164
165static void
166makeProgressDialog( GtkWidget * parent, MakeMetaUI * ui )
167{
168    GtkWidget *d, *l, *w, *v, *fr;
169
170    d = gtk_dialog_new_with_buttons( _( "New Torrent" ),
171            GTK_WINDOW( parent ),
172            GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
173            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
174            GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
175            GTK_STOCK_ADD, GTK_RESPONSE_ACCEPT,
176            NULL );
177    ui->progress_dialog = d;
178    g_signal_connect( d, "response", G_CALLBACK( onProgressDialogResponse ), ui );
179
180    fr = gtk_frame_new( NULL );
181    gtk_container_set_border_width( GTK_CONTAINER( fr ), GUI_PAD_BIG );
182    gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_NONE );
183    v = gtk_vbox_new( TRUE, GUI_PAD );
184    gtk_container_add( GTK_CONTAINER( fr ), v );
185
186    l = gtk_label_new( _( "Creating torrent..." ) );
187    gtk_misc_set_alignment( GTK_MISC( l ), 0.0, 0.5 );
188    gtk_label_set_justify( GTK_LABEL( l ), GTK_JUSTIFY_LEFT );
189    ui->progress_label = l;
190    gtk_box_pack_start( GTK_BOX( v ), l, FALSE, FALSE, 0 );
191
192    w = gtk_progress_bar_new( );
193    ui->progress_bar = w;
194    gtk_box_pack_start( GTK_BOX( v ), w, FALSE, FALSE, 0 );
195
196    ui->progress_tag = gtr_timeout_add_seconds( SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS, onProgressDialogRefresh, ui );
197    g_object_weak_ref( G_OBJECT( d ), onProgressDialogDestroyed, ui );
198    onProgressDialogRefresh( ui );
199
200    gtr_dialog_set_content( GTK_DIALOG( d ), fr );
201    gtk_widget_show( d );
202}
203
204static void
205onResponse( GtkDialog* d, int response, gpointer user_data )
206{
207    MakeMetaUI * ui = user_data;
208
209    if( response == GTK_RESPONSE_ACCEPT )
210    {
211        if( ui->builder != NULL )
212        {
213            int i;
214            int n;
215            int tier;
216            GtkTextIter start, end;
217            char * dir;
218            char * base;
219            char * tracker_text;
220            char ** tracker_strings;
221            GtkEntry * c_entry = GTK_ENTRY( ui->comment_entry );
222            GtkToggleButton * p_check = GTK_TOGGLE_BUTTON( ui->private_check );
223            GtkToggleButton * c_check = GTK_TOGGLE_BUTTON( ui->comment_check );
224            const char * comment = gtk_entry_get_text( c_entry );
225            const gboolean isPrivate = gtk_toggle_button_get_active( p_check );
226            const gboolean useComment = gtk_toggle_button_get_active( c_check );
227            tr_tracker_info * trackers;
228
229            /* destination file */
230            dir = gtk_file_chooser_get_filename(
231                      GTK_FILE_CHOOSER( ui->destination_chooser ) );
232            base = g_path_get_basename( ui->builder->top );
233            g_free( ui->target );
234            ui->target = g_strdup_printf( "%s/%s.torrent", dir, base );
235
236            /* build the array of trackers */
237            gtk_text_buffer_get_bounds( ui->announce_text_buffer, &start, &end );
238            tracker_text = gtk_text_buffer_get_text( ui->announce_text_buffer,
239                                                     &start, &end, FALSE );
240            tracker_strings = g_strsplit( tracker_text, "\n", 0 );
241            for( i=0; tracker_strings[i]; )
242                ++i;
243            trackers = g_new0( tr_tracker_info, i );
244            for( i=n=tier=0; tracker_strings[i]; ++i ) {
245                const char * str = tracker_strings[i];
246                if( !*str )
247                    ++tier;
248                else {
249                    trackers[n].tier = tier;
250                    trackers[n].announce = tracker_strings[i];
251                    ++n;
252                }
253            }
254
255            /* build the .torrent */
256            makeProgressDialog( GTK_WIDGET( d ), ui );
257            tr_makeMetaInfo( ui->builder, ui->target, trackers, n,
258                             useComment ? comment : NULL, isPrivate );
259
260            /* cleanup */
261            g_free( trackers );
262            g_strfreev( tracker_strings );
263            g_free( tracker_text );
264            g_free( base );
265            g_free( dir );
266        }
267    }
268    else if( response == GTK_RESPONSE_CLOSE )
269    {
270        gtk_widget_destroy( GTK_WIDGET( d ) );
271    }
272}
273
274/***
275****
276***/
277
278static void
279onSourceToggled( GtkToggleButton * tb, gpointer user_data )
280{
281    gtk_widget_set_sensitive( GTK_WIDGET( user_data ),
282                              gtk_toggle_button_get_active( tb ) );
283}
284
285static void
286updatePiecesLabel( MakeMetaUI * ui )
287{
288    const tr_metainfo_builder * builder = ui->builder;
289    const char * filename = builder ? builder->top : NULL;
290    GString * gstr = g_string_new( NULL );
291
292    g_string_append( gstr, "<i>" );
293    if( !filename )
294    {
295        g_string_append( gstr, _( "No source selected" ) );
296    }
297    else
298    {
299        char buf[128];
300        tr_strlsize( buf, builder->totalSize, sizeof( buf ) );
301        g_string_append_printf( gstr, gtr_ngettext( "%1$s; %2$'d File",
302                                                    "%1$s; %2$'d Files",
303                                                    builder->fileCount ),
304                                buf, builder->fileCount );
305        g_string_append( gstr, "; " );
306
307        tr_formatter_mem_B( buf, builder->pieceSize, sizeof( buf ) );
308        g_string_append_printf( gstr, gtr_ngettext( "%1$'d Piece @ %2$s",
309                                                    "%1$'d Pieces @ %2$s",
310                                                    builder->pieceCount ),
311                                      builder->pieceCount, buf );
312    }
313    g_string_append( gstr, "</i>" );
314    gtk_label_set_markup ( GTK_LABEL( ui->pieces_lb ), gstr->str );
315    g_string_free( gstr, TRUE );
316}
317
318static void
319setFilename( MakeMetaUI * ui, const char * filename )
320{
321    if( ui->builder ) {
322        tr_metaInfoBuilderFree( ui->builder );
323        ui->builder = NULL;
324    }
325
326    if( filename )
327        ui->builder = tr_metaInfoBuilderCreate( filename );
328
329    updatePiecesLabel( ui );
330}
331
332static void
333onChooserChosen( GtkFileChooser * chooser, gpointer user_data )
334{
335    char * filename;
336    MakeMetaUI * ui = user_data;
337
338    g_object_set_data( G_OBJECT( chooser ), FILE_CHOSEN_KEY,
339                       GINT_TO_POINTER( TRUE ) );
340
341    filename = gtk_file_chooser_get_filename( chooser );
342    setFilename( ui, filename );
343    g_free( filename );
344}
345
346static void
347onSourceToggled2( GtkToggleButton * tb, GtkWidget * chooser, MakeMetaUI * ui )
348{
349    if( gtk_toggle_button_get_active( tb ) )
350    {
351        if( g_object_get_data( G_OBJECT( chooser ), FILE_CHOSEN_KEY ) != NULL )
352            onChooserChosen( GTK_FILE_CHOOSER( chooser ), ui );
353        else
354            setFilename( ui, NULL );
355    }
356}
357static void
358onFolderToggled( GtkToggleButton * tb, gpointer data )
359{
360    MakeMetaUI * ui = data;
361    onSourceToggled2( tb, ui->folder_chooser, ui );
362}
363static void
364onFileToggled( GtkToggleButton * tb, gpointer data )
365{
366    MakeMetaUI * ui = data;
367    onSourceToggled2( tb, ui->file_chooser, ui );
368}
369
370static const char *
371getDefaultSavePath( void )
372{
373    const char * path;
374#if GLIB_CHECK_VERSION( 2,14,0 )
375    path = g_get_user_special_dir( G_USER_DIRECTORY_DESKTOP );
376#else
377    path = g_get_home_dir( );
378#endif
379    return path;
380}
381
382static void
383on_drag_data_received( GtkWidget         * widget           UNUSED,
384                       GdkDragContext    * drag_context,
385                       gint                x                UNUSED,
386                       gint                y                UNUSED,
387                       GtkSelectionData  * selection_data,
388                       guint               info             UNUSED,
389                       guint               time_,
390                       gpointer            user_data )
391{
392    gboolean success = FALSE;
393    MakeMetaUI * ui = user_data;
394    char ** uris = gtk_selection_data_get_uris( selection_data );
395
396    if( uris && uris[0] )
397    {
398        const char * uri = uris[ 0 ];
399        gchar * filename = g_filename_from_uri( uri, NULL, NULL );
400
401        if( g_file_test( filename, G_FILE_TEST_IS_DIR ) )
402        {
403            /* a directory was dragged onto the dialog... */
404            gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( ui->folder_radio ), TRUE );
405            gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( ui->folder_chooser ), filename );
406            success = TRUE;
407        }
408        else if( g_file_test( filename, G_FILE_TEST_IS_REGULAR ) )
409        {
410            /* a file was dragged on to the dialog... */
411            gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( ui->file_radio ), TRUE );
412            gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( ui->file_chooser ), filename );
413            success = TRUE;
414        }
415
416        g_free( filename );
417    }
418
419    g_strfreev( uris );
420    gtk_drag_finish( drag_context, success, FALSE, time_ );
421}
422
423GtkWidget*
424gtr_torrent_creation_dialog_new( GtkWindow  * parent, TrCore * core )
425{
426    int row = 0;
427    const char * str;
428    GtkWidget * d, *t, *w, *l, *fr, *sw, *v;
429    GSList * slist;
430    MakeMetaUI * ui = g_new0 ( MakeMetaUI, 1 );
431
432    ui->core = core;
433
434    d = gtk_dialog_new_with_buttons( _( "New Torrent" ),
435                                     parent,
436                                     GTK_DIALOG_DESTROY_WITH_PARENT,
437                                     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
438                                     GTK_STOCK_NEW, GTK_RESPONSE_ACCEPT,
439                                     NULL );
440    ui->dialog = d;
441    g_signal_connect( d, "response", G_CALLBACK( onResponse ), ui );
442    g_object_set_data_full( G_OBJECT( d ), "ui", ui, freeMetaUI );
443
444    t = hig_workarea_create ( );
445
446    hig_workarea_add_section_title ( t, &row, _( "Files" ) );
447
448        str = _( "Sa_ve to:" );
449        w = gtk_file_chooser_button_new( NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
450        gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( w ), getDefaultSavePath( ) );
451        ui->destination_chooser = w;
452        hig_workarea_add_row( t, &row, str, w, NULL );
453
454        l = gtk_radio_button_new_with_mnemonic( NULL, _( "Source F_older:" ) );
455        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( l ), FALSE );
456        w = gtk_file_chooser_button_new( NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
457        g_signal_connect( l, "toggled", G_CALLBACK( onFolderToggled ), ui );
458        g_signal_connect( l, "toggled", G_CALLBACK( onSourceToggled ), w );
459        g_signal_connect( w, "selection-changed", G_CALLBACK( onChooserChosen ), ui );
460        ui->folder_radio = l;
461        ui->folder_chooser = w;
462        gtk_widget_set_sensitive( GTK_WIDGET( w ), FALSE );
463        hig_workarea_add_row_w( t, &row, l, w, NULL );
464
465        slist = gtk_radio_button_get_group( GTK_RADIO_BUTTON( l ) ),
466        l = gtk_radio_button_new_with_mnemonic( slist, _( "Source _File:" ) );
467        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( l ), TRUE );
468        w = gtk_file_chooser_button_new( NULL, GTK_FILE_CHOOSER_ACTION_OPEN );
469        g_signal_connect( l, "toggled", G_CALLBACK( onFileToggled ), ui );
470        g_signal_connect( l, "toggled", G_CALLBACK( onSourceToggled ), w );
471        g_signal_connect( w, "selection-changed", G_CALLBACK( onChooserChosen ), ui );
472        ui->file_radio = l;
473        ui->file_chooser = w;
474        hig_workarea_add_row_w( t, &row, l, w, NULL );
475
476        w = gtk_label_new( NULL );
477        ui->pieces_lb = w;
478        gtk_label_set_markup( GTK_LABEL( w ), _( "<i>No source selected</i>" ) );
479        hig_workarea_add_row( t, &row, NULL, w, NULL );
480
481    hig_workarea_add_section_divider( t, &row );
482    hig_workarea_add_section_title ( t, &row, _( "Properties" ) );
483
484        str = _( "_Trackers:" );
485        v = gtk_vbox_new( FALSE, GUI_PAD_SMALL );
486        ui->announce_text_buffer = gtk_text_buffer_new( NULL );
487        w = gtk_text_view_new_with_buffer( ui->announce_text_buffer );
488        gtk_widget_set_size_request( w, -1, 80 );
489        sw = gtk_scrolled_window_new( NULL, NULL );
490        gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sw ),
491                                        GTK_POLICY_AUTOMATIC,
492                                        GTK_POLICY_AUTOMATIC );
493        gtk_container_add( GTK_CONTAINER( sw ), w );
494        fr = gtk_frame_new( NULL );
495        gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
496        gtk_container_add( GTK_CONTAINER( fr ), sw );
497        gtk_box_pack_start( GTK_BOX( v ), fr, TRUE, TRUE, 0 );
498        l = gtk_label_new( NULL );
499        gtk_label_set_markup( GTK_LABEL( l ), _( "To add a backup URL, add it on the line after the primary URL.\n"
500                                                 "To add another primary URL, add it after a blank line." ) );
501        gtk_label_set_justify( GTK_LABEL( l ), GTK_JUSTIFY_LEFT );
502        gtk_misc_set_alignment( GTK_MISC( l ), 0.0, 0.5 );
503        gtk_box_pack_start( GTK_BOX( v ), l, FALSE, FALSE, 0 );
504        hig_workarea_add_tall_row( t, &row, str, v, NULL );
505
506        l = gtk_check_button_new_with_mnemonic( _( "Co_mment:" ) );
507        ui->comment_check = l;
508        gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( l ), FALSE );
509        w = gtk_entry_new( );
510        ui->comment_entry = w;
511        gtk_widget_set_sensitive( GTK_WIDGET( w ), FALSE );
512        g_signal_connect( l, "toggled", G_CALLBACK( onSourceToggled ), w );
513        hig_workarea_add_row_w( t, &row, l, w, NULL );
514
515        w = hig_workarea_add_wide_checkbutton( t, &row, _( "_Private torrent" ), FALSE );
516        ui->private_check = w;
517
518    hig_workarea_finish( t, &row );
519    gtr_dialog_set_content( GTK_DIALOG( d ), t );
520
521    gtk_drag_dest_set( d, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY );
522    gtk_drag_dest_add_uri_targets( d );
523    g_signal_connect( d, "drag-data-received", G_CALLBACK( on_drag_data_received ), ui );
524
525    return d;
526}
Note: See TracBrowser for help on using the repository browser.