1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-27 01:57:52 +00:00
transmission/gtk/makemeta-ui.c
Jordan Lee aea77bc9f6 (trunk gtk) make "gtr_label_set_text" public and use it everywhere instead of gtk_label_set_text()
Some of the refresh events to the main window's torrent list are caused by main window relayout caused by the toolbar's GtkLabels recalculating their size after being updated once per second. To prevent relayout in some trivial cases, I'm replacing the gtk_label_set_text() calls with gtr_label_set_text() because the latter doesn't update the label widget if the old and new text strings are the same.

There are other changes that can handle more important cases -- I'll test those out next.
2011-01-21 16:32:27 +00:00

526 lines
18 KiB
C

/*
* This file Copyright (C) Mnemosyne LLC
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id$
*/
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <libtransmission/transmission.h>
#include <libtransmission/makemeta.h>
#include <libtransmission/utils.h> /* tr_formatter_mem_B() */
#include "conf.h"
#include "hig.h"
#include "makemeta-ui.h"
#include "tr-core.h"
#include "tr-prefs.h"
#include "util.h"
#define FILE_CHOSEN_KEY "file-is-chosen"
typedef struct
{
char * target;
guint progress_tag;
GtkWidget * file_radio;
GtkWidget * file_chooser;
GtkWidget * folder_radio;
GtkWidget * folder_chooser;
GtkWidget * pieces_lb;
GtkWidget * destination_chooser;
GtkWidget * comment_check;
GtkWidget * comment_entry;
GtkWidget * private_check;
GtkWidget * progress_label;
GtkWidget * progress_bar;
GtkWidget * progress_dialog;
GtkWidget * dialog;
GtkTextBuffer * announce_text_buffer;
TrCore * core;
tr_metainfo_builder * builder;
}
MakeMetaUI;
static void
freeMetaUI( gpointer p )
{
MakeMetaUI * ui = p;
tr_metaInfoBuilderFree( ui->builder );
g_free( ui->target );
memset( ui, ~0, sizeof( MakeMetaUI ) );
g_free( ui );
}
static gboolean
onProgressDialogRefresh( gpointer data )
{
char * str = NULL;
MakeMetaUI * ui = data;
const tr_metainfo_builder * b = ui->builder;
GtkDialog * d = GTK_DIALOG( ui->progress_dialog );
GtkProgressBar * p = GTK_PROGRESS_BAR( ui->progress_bar );
const double fraction = b->pieceCount ? ((double)b->pieceIndex / b->pieceCount) : 0;
char * base = g_path_get_basename( b->top );
/* progress label */
if( !b->isDone )
str = g_strdup_printf( _( "Creating \"%s\"" ), base );
else if( b->result == TR_MAKEMETA_OK )
str = g_strdup_printf( _( "Created \"%s\"!" ), base );
else if( b->result == TR_MAKEMETA_URL )
str = g_strdup_printf( _( "Error: invalid announce URL \"%s\"" ), b->errfile );
else if( b->result == TR_MAKEMETA_CANCELLED )
str = g_strdup_printf( _( "Cancelled" ) );
else if( b->result == TR_MAKEMETA_IO_READ )
str = g_strdup_printf( _( "Error reading \"%s\": %s" ), b->errfile, g_strerror( b->my_errno ) );
else if( b->result == TR_MAKEMETA_IO_WRITE )
str = g_strdup_printf( _( "Error writing \"%s\": %s" ), b->errfile, g_strerror( b->my_errno ) );
else
g_assert_not_reached( );
if( str != NULL ) {
gtr_label_set_text( GTK_LABEL( ui->progress_label ), str );
g_free( str );
}
/* progress bar */
if( !b->pieceIndex )
str = g_strdup( "" );
else {
char sizebuf[128];
tr_strlsize( sizebuf, (uint64_t)b->pieceIndex *
(uint64_t)b->pieceSize, sizeof( sizebuf ) );
/* how much data we've scanned through to generate checksums */
str = g_strdup_printf( _( "Scanned %s" ), sizebuf );
}
gtk_progress_bar_set_fraction( p, fraction );
gtk_progress_bar_set_text( p, str );
g_free( str );
/* buttons */
gtk_dialog_set_response_sensitive( d, GTK_RESPONSE_CANCEL, !b->isDone );
gtk_dialog_set_response_sensitive( d, GTK_RESPONSE_CLOSE, b->isDone );
gtk_dialog_set_response_sensitive( d, GTK_RESPONSE_ACCEPT, b->isDone && !b->result );
g_free( base );
return TRUE;
}
static void
onProgressDialogDestroyed( gpointer data, GObject * dead UNUSED )
{
MakeMetaUI * ui = data;
g_source_remove( ui->progress_tag );
}
static void
addTorrent( MakeMetaUI * ui )
{
char * path;
const tr_metainfo_builder * b = ui->builder;
tr_ctor * ctor = tr_ctorNew( tr_core_session( ui->core ) );
tr_ctorSetMetainfoFromFile( ctor, ui->target );
path = g_path_get_dirname( b->top );
tr_ctorSetDownloadDir( ctor, TR_FORCE, path );
g_free( path );
tr_core_add_ctor( ui->core, ctor );
}
static void
onProgressDialogResponse( GtkDialog * d, int response, gpointer data )
{
MakeMetaUI * ui = data;
switch( response )
{
case GTK_RESPONSE_CANCEL:
ui->builder->abortFlag = TRUE;
gtk_widget_destroy( GTK_WIDGET( d ) );
break;
case GTK_RESPONSE_ACCEPT:
addTorrent( ui );
/* fall-through */
case GTK_RESPONSE_CLOSE:
gtk_widget_destroy( ui->builder->result ? GTK_WIDGET( d ) : ui->dialog );
break;
default:
g_assert( 0 && "unhandled response" );
}
}
static void
makeProgressDialog( GtkWidget * parent, MakeMetaUI * ui )
{
GtkWidget *d, *l, *w, *v, *fr;
d = gtk_dialog_new_with_buttons( _( "New Torrent" ),
GTK_WINDOW( parent ),
GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
GTK_STOCK_ADD, GTK_RESPONSE_ACCEPT,
NULL );
ui->progress_dialog = d;
g_signal_connect( d, "response", G_CALLBACK( onProgressDialogResponse ), ui );
fr = gtk_frame_new( NULL );
gtk_container_set_border_width( GTK_CONTAINER( fr ), GUI_PAD_BIG );
gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_NONE );
v = gtk_vbox_new( TRUE, GUI_PAD );
gtk_container_add( GTK_CONTAINER( fr ), v );
l = gtk_label_new( _( "Creating torrent..." ) );
gtk_misc_set_alignment( GTK_MISC( l ), 0.0, 0.5 );
gtk_label_set_justify( GTK_LABEL( l ), GTK_JUSTIFY_LEFT );
ui->progress_label = l;
gtk_box_pack_start( GTK_BOX( v ), l, FALSE, FALSE, 0 );
w = gtk_progress_bar_new( );
ui->progress_bar = w;
gtk_box_pack_start( GTK_BOX( v ), w, FALSE, FALSE, 0 );
ui->progress_tag = gtr_timeout_add_seconds( SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS, onProgressDialogRefresh, ui );
g_object_weak_ref( G_OBJECT( d ), onProgressDialogDestroyed, ui );
onProgressDialogRefresh( ui );
gtr_dialog_set_content( GTK_DIALOG( d ), fr );
gtk_widget_show( d );
}
static void
onResponse( GtkDialog* d, int response, gpointer user_data )
{
MakeMetaUI * ui = user_data;
if( response == GTK_RESPONSE_ACCEPT )
{
if( ui->builder != NULL )
{
int i;
int n;
int tier;
GtkTextIter start, end;
char * dir;
char * base;
char * tracker_text;
char ** tracker_strings;
GtkEntry * c_entry = GTK_ENTRY( ui->comment_entry );
GtkToggleButton * p_check = GTK_TOGGLE_BUTTON( ui->private_check );
GtkToggleButton * c_check = GTK_TOGGLE_BUTTON( ui->comment_check );
const char * comment = gtk_entry_get_text( c_entry );
const gboolean isPrivate = gtk_toggle_button_get_active( p_check );
const gboolean useComment = gtk_toggle_button_get_active( c_check );
tr_tracker_info * trackers;
/* destination file */
dir = gtk_file_chooser_get_filename(
GTK_FILE_CHOOSER( ui->destination_chooser ) );
base = g_path_get_basename( ui->builder->top );
g_free( ui->target );
ui->target = g_strdup_printf( "%s/%s.torrent", dir, base );
/* build the array of trackers */
gtk_text_buffer_get_bounds( ui->announce_text_buffer, &start, &end );
tracker_text = gtk_text_buffer_get_text( ui->announce_text_buffer,
&start, &end, FALSE );
tracker_strings = g_strsplit( tracker_text, "\n", 0 );
for( i=0; tracker_strings[i]; )
++i;
trackers = g_new0( tr_tracker_info, i );
for( i=n=tier=0; tracker_strings[i]; ++i ) {
const char * str = tracker_strings[i];
if( !*str )
++tier;
else {
trackers[n].tier = tier;
trackers[n].announce = tracker_strings[i];
++n;
}
}
/* build the .torrent */
makeProgressDialog( GTK_WIDGET( d ), ui );
tr_makeMetaInfo( ui->builder, ui->target, trackers, n,
useComment ? comment : NULL, isPrivate );
/* cleanup */
g_free( trackers );
g_strfreev( tracker_strings );
g_free( tracker_text );
g_free( base );
g_free( dir );
}
}
else if( response == GTK_RESPONSE_CLOSE )
{
gtk_widget_destroy( GTK_WIDGET( d ) );
}
}
/***
****
***/
static void
onSourceToggled( GtkToggleButton * tb, gpointer user_data )
{
gtk_widget_set_sensitive( GTK_WIDGET( user_data ),
gtk_toggle_button_get_active( tb ) );
}
static void
updatePiecesLabel( MakeMetaUI * ui )
{
const tr_metainfo_builder * builder = ui->builder;
const char * filename = builder ? builder->top : NULL;
GString * gstr = g_string_new( NULL );
g_string_append( gstr, "<i>" );
if( !filename )
{
g_string_append( gstr, _( "No source selected" ) );
}
else
{
char buf[128];
tr_strlsize( buf, builder->totalSize, sizeof( buf ) );
g_string_append_printf( gstr, gtr_ngettext( "%1$s; %2$'d File",
"%1$s; %2$'d Files",
builder->fileCount ),
buf, builder->fileCount );
g_string_append( gstr, "; " );
tr_formatter_mem_B( buf, builder->pieceSize, sizeof( buf ) );
g_string_append_printf( gstr, gtr_ngettext( "%1$'d Piece @ %2$s",
"%1$'d Pieces @ %2$s",
builder->pieceCount ),
builder->pieceCount, buf );
}
g_string_append( gstr, "</i>" );
gtk_label_set_markup ( GTK_LABEL( ui->pieces_lb ), gstr->str );
g_string_free( gstr, TRUE );
}
static void
setFilename( MakeMetaUI * ui, const char * filename )
{
if( ui->builder ) {
tr_metaInfoBuilderFree( ui->builder );
ui->builder = NULL;
}
if( filename )
ui->builder = tr_metaInfoBuilderCreate( filename );
updatePiecesLabel( ui );
}
static void
onChooserChosen( GtkFileChooser * chooser, gpointer user_data )
{
char * filename;
MakeMetaUI * ui = user_data;
g_object_set_data( G_OBJECT( chooser ), FILE_CHOSEN_KEY,
GINT_TO_POINTER( TRUE ) );
filename = gtk_file_chooser_get_filename( chooser );
setFilename( ui, filename );
g_free( filename );
}
static void
onSourceToggled2( GtkToggleButton * tb, GtkWidget * chooser, MakeMetaUI * ui )
{
if( gtk_toggle_button_get_active( tb ) )
{
if( g_object_get_data( G_OBJECT( chooser ), FILE_CHOSEN_KEY ) != NULL )
onChooserChosen( GTK_FILE_CHOOSER( chooser ), ui );
else
setFilename( ui, NULL );
}
}
static void
onFolderToggled( GtkToggleButton * tb, gpointer data )
{
MakeMetaUI * ui = data;
onSourceToggled2( tb, ui->folder_chooser, ui );
}
static void
onFileToggled( GtkToggleButton * tb, gpointer data )
{
MakeMetaUI * ui = data;
onSourceToggled2( tb, ui->file_chooser, ui );
}
static const char *
getDefaultSavePath( void )
{
const char * path;
#if GLIB_CHECK_VERSION( 2,14,0 )
path = g_get_user_special_dir( G_USER_DIRECTORY_DESKTOP );
#else
path = g_get_home_dir( );
#endif
return path;
}
static void
on_drag_data_received( GtkWidget * widget UNUSED,
GdkDragContext * drag_context,
gint x UNUSED,
gint y UNUSED,
GtkSelectionData * selection_data,
guint info UNUSED,
guint time_,
gpointer user_data )
{
gboolean success = FALSE;
MakeMetaUI * ui = user_data;
char ** uris = gtk_selection_data_get_uris( selection_data );
if( uris && uris[0] )
{
const char * uri = uris[ 0 ];
gchar * filename = g_filename_from_uri( uri, NULL, NULL );
if( g_file_test( filename, G_FILE_TEST_IS_DIR ) )
{
/* a directory was dragged onto the dialog... */
gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( ui->folder_radio ), TRUE );
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( ui->folder_chooser ), filename );
success = TRUE;
}
else if( g_file_test( filename, G_FILE_TEST_IS_REGULAR ) )
{
/* a file was dragged on to the dialog... */
gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( ui->file_radio ), TRUE );
gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( ui->file_chooser ), filename );
success = TRUE;
}
g_free( filename );
}
g_strfreev( uris );
gtk_drag_finish( drag_context, success, FALSE, time_ );
}
GtkWidget*
gtr_torrent_creation_dialog_new( GtkWindow * parent, TrCore * core )
{
int row = 0;
const char * str;
GtkWidget * d, *t, *w, *l, *fr, *sw, *v;
GSList * slist;
MakeMetaUI * ui = g_new0 ( MakeMetaUI, 1 );
ui->core = core;
d = gtk_dialog_new_with_buttons( _( "New Torrent" ),
parent,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
GTK_STOCK_NEW, GTK_RESPONSE_ACCEPT,
NULL );
ui->dialog = d;
g_signal_connect( d, "response", G_CALLBACK( onResponse ), ui );
g_object_set_data_full( G_OBJECT( d ), "ui", ui, freeMetaUI );
t = hig_workarea_create ( );
hig_workarea_add_section_title ( t, &row, _( "Files" ) );
str = _( "Sa_ve to:" );
w = gtk_file_chooser_button_new( NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( w ), getDefaultSavePath( ) );
ui->destination_chooser = w;
hig_workarea_add_row( t, &row, str, w, NULL );
l = gtk_radio_button_new_with_mnemonic( NULL, _( "Source F_older:" ) );
gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( l ), FALSE );
w = gtk_file_chooser_button_new( NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER );
g_signal_connect( l, "toggled", G_CALLBACK( onFolderToggled ), ui );
g_signal_connect( l, "toggled", G_CALLBACK( onSourceToggled ), w );
g_signal_connect( w, "selection-changed", G_CALLBACK( onChooserChosen ), ui );
ui->folder_radio = l;
ui->folder_chooser = w;
gtk_widget_set_sensitive( GTK_WIDGET( w ), FALSE );
hig_workarea_add_row_w( t, &row, l, w, NULL );
slist = gtk_radio_button_get_group( GTK_RADIO_BUTTON( l ) ),
l = gtk_radio_button_new_with_mnemonic( slist, _( "Source _File:" ) );
gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( l ), TRUE );
w = gtk_file_chooser_button_new( NULL, GTK_FILE_CHOOSER_ACTION_OPEN );
g_signal_connect( l, "toggled", G_CALLBACK( onFileToggled ), ui );
g_signal_connect( l, "toggled", G_CALLBACK( onSourceToggled ), w );
g_signal_connect( w, "selection-changed", G_CALLBACK( onChooserChosen ), ui );
ui->file_radio = l;
ui->file_chooser = w;
hig_workarea_add_row_w( t, &row, l, w, NULL );
w = gtk_label_new( NULL );
ui->pieces_lb = w;
gtk_label_set_markup( GTK_LABEL( w ), _( "<i>No source selected</i>" ) );
hig_workarea_add_row( t, &row, NULL, w, NULL );
hig_workarea_add_section_divider( t, &row );
hig_workarea_add_section_title ( t, &row, _( "Properties" ) );
str = _( "_Trackers:" );
v = gtk_vbox_new( FALSE, GUI_PAD_SMALL );
ui->announce_text_buffer = gtk_text_buffer_new( NULL );
w = gtk_text_view_new_with_buffer( ui->announce_text_buffer );
gtk_widget_set_size_request( w, -1, 80 );
sw = gtk_scrolled_window_new( NULL, NULL );
gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( sw ),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC );
gtk_container_add( GTK_CONTAINER( sw ), w );
fr = gtk_frame_new( NULL );
gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
gtk_container_add( GTK_CONTAINER( fr ), sw );
gtk_box_pack_start( GTK_BOX( v ), fr, TRUE, TRUE, 0 );
l = gtk_label_new( NULL );
gtk_label_set_markup( GTK_LABEL( l ), _( "To add a backup URL, add it on the line after the primary URL.\n"
"To add another primary URL, add it after a blank line." ) );
gtk_label_set_justify( GTK_LABEL( l ), GTK_JUSTIFY_LEFT );
gtk_misc_set_alignment( GTK_MISC( l ), 0.0, 0.5 );
gtk_box_pack_start( GTK_BOX( v ), l, FALSE, FALSE, 0 );
hig_workarea_add_tall_row( t, &row, str, v, NULL );
l = gtk_check_button_new_with_mnemonic( _( "Co_mment:" ) );
ui->comment_check = l;
gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( l ), FALSE );
w = gtk_entry_new( );
ui->comment_entry = w;
gtk_widget_set_sensitive( GTK_WIDGET( w ), FALSE );
g_signal_connect( l, "toggled", G_CALLBACK( onSourceToggled ), w );
hig_workarea_add_row_w( t, &row, l, w, NULL );
w = hig_workarea_add_wide_checkbutton( t, &row, _( "_Private torrent" ), FALSE );
ui->private_check = w;
hig_workarea_finish( t, &row );
gtr_dialog_set_content( GTK_DIALOG( d ), t );
gtk_drag_dest_set( d, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY );
gtk_drag_dest_add_uri_targets( d );
g_signal_connect( d, "drag-data-received", G_CALLBACK( on_drag_data_received ), ui );
return d;
}