source: trunk/gtk/util.c @ 10828

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

(trunk gtk) #3336 "gtk client confuses magnet links as files and tries to trash them" -- fixed

  • Property svn:keywords set to Date Rev Author Id
File size: 21.5 KB
Line 
1/*
2 * This file Copyright (C) 2008-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: util.c 10828 2010-06-23 16:32:02Z charles $
11 */
12
13#include <ctype.h> /* isxdigit() */
14#include <errno.h>
15#include <stdlib.h> /* free() */
16#include <string.h> /* strcmp() */
17
18#include <sys/types.h> /* for gtr_lockfile()'s open() */
19#include <sys/stat.h> /* for gtr_lockfile()'s open() */
20#include <fcntl.h> /* for gtr_lockfile()'s open() */
21
22#include <gtk/gtk.h>
23#include <glib/gi18n.h>
24#include <glib/gstdio.h> /* g_unlink() */
25#ifdef HAVE_GIO
26 #include <gio/gio.h> /* g_file_trash() */
27#endif
28#ifdef HAVE_DBUS_GLIB
29 #include <dbus/dbus-glib.h>
30#endif
31
32#include <evhttp.h>
33
34#include <libtransmission/transmission.h> /* TR_RATIO_NA, TR_RATIO_INF */
35#include <libtransmission/utils.h> /* tr_inf */
36#include <libtransmission/version.h> /* tr_inf */
37
38#include "conf.h"
39#include "hig.h"
40#include "tr-prefs.h"
41#include "util.h"
42
43gtr_lockfile_state_t
44gtr_lockfile( const char * filename )
45{
46    gtr_lockfile_state_t ret;
47
48#ifdef WIN32
49
50    HANDLE file = CreateFile( filename,
51                              GENERIC_READ | GENERIC_WRITE,
52                              FILE_SHARE_READ | FILE_SHARE_WRITE,
53                              NULL,
54                              OPEN_ALWAYS,
55                              FILE_ATTRIBUTE_NORMAL,
56                              NULL );
57    if( file == INVALID_HANDLE_VALUE )
58        ret = GTR_LOCKFILE_EOPEN;
59    else if( !LockFile( file, 0, 0, 1, 1 ) )
60        ret = GTR_LOCKFILE_ELOCK;
61    else
62        ret = GTR_LOCKFILE_SUCCESS;
63
64#else
65
66    int fd = open( filename, O_RDWR | O_CREAT, 0666 );
67    if( fd < 0 )
68        ret = GTR_LOCKFILE_EOPEN;
69    else {
70        struct flock lk;
71        memset( &lk, 0,  sizeof( lk ) );
72        lk.l_start = 0;
73        lk.l_len = 0;
74        lk.l_type = F_WRLCK;
75        lk.l_whence = SEEK_SET;
76        if( -1 == fcntl( fd, F_SETLK, &lk ) )
77            ret = GTR_LOCKFILE_ELOCK;
78        else
79            ret = GTR_LOCKFILE_SUCCESS;
80    }
81
82#endif
83
84    return ret;
85}
86
87/***
88****
89***/
90
91const char*
92gtr_get_unicode_string( int i )
93{
94    switch( i ) {
95        case GTR_UNICODE_UP:   return "\xE2\x86\x91";
96        case GTR_UNICODE_DOWN: return "\xE2\x86\x93";
97        case GTR_UNICODE_INF:  return "\xE2\x88\x9E";
98        default:               return "err";
99    }
100}
101
102char*
103tr_strlratio( char * buf, double ratio, size_t buflen )
104{
105    return tr_strratio( buf, buflen, ratio, gtr_get_unicode_string( GTR_UNICODE_INF ) );
106}
107
108char*
109tr_strlpercent( char * buf, double x, size_t buflen )
110{
111    return tr_strpercent( buf, x, buflen );
112}
113
114char*
115tr_strlsize( char * buf, guint64 bytes, size_t buflen )
116{
117    if( !bytes )
118        g_strlcpy( buf, _( "None" ), buflen );
119    else
120        tr_formatter_size( buf, bytes, buflen );
121
122    return buf;
123}
124
125char*
126tr_strlspeed( char * buf, double kb_sec, size_t buflen )
127{
128    const int64_t bytes_per_second = kb_sec * 1024.0;
129
130    if( bytes_per_second < 1 )
131        g_strlcpy( buf, _( "None" ), buflen );
132    else
133        tr_formatter_speed( buf, bytes_per_second, buflen );
134
135    return buf;
136}
137
138char*
139tr_strltime( char * buf, int seconds, size_t buflen )
140{
141    int  days, hours, minutes;
142    char d[128], h[128], m[128], s[128];
143
144    if( seconds < 0 )
145        seconds = 0;
146
147    days = seconds / 86400;
148    hours = ( seconds % 86400 ) / 3600;
149    minutes = ( seconds % 3600 ) / 60;
150    seconds = ( seconds % 3600 ) % 60;
151
152    g_snprintf( d, sizeof( d ), ngettext( "%'d day", "%'d days",
153                                          days ), days );
154    g_snprintf( h, sizeof( h ), ngettext( "%'d hour", "%'d hours",
155                                          hours ), hours );
156    g_snprintf( m, sizeof( m ),
157                ngettext( "%'d minute", "%'d minutes", minutes ), minutes );
158    g_snprintf( s, sizeof( s ),
159                ngettext( "%'d second", "%'d seconds", seconds ), seconds );
160
161    if( days )
162    {
163        if( days >= 4 || !hours )
164        {
165            g_strlcpy( buf, d, buflen );
166        }
167        else
168        {
169            g_snprintf( buf, buflen, "%s, %s", d, h );
170        }
171    }
172    else if( hours )
173    {
174        if( hours >= 4 || !minutes )
175        {
176            g_strlcpy( buf, h, buflen );
177        }
178        else
179        {
180            g_snprintf( buf, buflen, "%s, %s", h, m );
181        }
182    }
183    else if( minutes )
184    {
185        if( minutes >= 4 || !seconds )
186        {
187            g_strlcpy( buf, m, buflen );
188        }
189        else
190        {
191            g_snprintf( buf, buflen, "%s, %s", m, s );
192        }
193    }
194    else
195    {
196        g_strlcpy( buf, s, buflen );
197    }
198
199    return buf;
200}
201
202char *
203gtr_localtime( time_t time )
204{
205    const struct tm tm = *localtime( &time );
206    char            buf[256], *eoln;
207
208    g_strlcpy( buf, asctime( &tm ), sizeof( buf ) );
209    if( ( eoln = strchr( buf, '\n' ) ) )
210        *eoln = '\0';
211
212    return g_locale_to_utf8( buf, -1, NULL, NULL, NULL );
213}
214
215int
216gtr_mkdir_with_parents( const char * path, int mode )
217{
218#if GLIB_CHECK_VERSION( 2, 8, 0 )
219    return !g_mkdir_with_parents( path, mode );
220#else
221    return !tr_mkdirp( path, mode );
222#endif
223}
224
225GSList *
226dupstrlist( GSList * l )
227{
228    GSList * ret = NULL;
229
230    for( ; l != NULL; l = l->next )
231        ret = g_slist_prepend( ret, g_strdup( l->data ) );
232    return g_slist_reverse( ret );
233}
234
235char *
236joinstrlist( GSList *list,
237             char *  sep )
238{
239    GSList * l;
240    GString *gstr = g_string_new ( NULL );
241
242    for( l = list; l != NULL; l = l->next )
243    {
244        g_string_append ( gstr, (char*)l->data );
245        if( l->next != NULL )
246            g_string_append ( gstr, ( sep ) );
247    }
248    return g_string_free ( gstr, FALSE );
249}
250
251void
252freestrlist( GSList *list )
253{
254    g_slist_foreach ( list, (GFunc)g_free, NULL );
255    g_slist_free ( list );
256}
257
258char *
259decode_uri( const char * uri )
260{
261    gboolean in_query = FALSE;
262    char *   ret = g_new( char, strlen( uri ) + 1 );
263    char *   out = ret;
264
265    for( ; uri && *uri; )
266    {
267        char ch = *uri;
268        if( ch == '?' )
269            in_query = TRUE;
270        else if( ch == '+' && in_query )
271            ch = ' ';
272        else if( ch == '%' && isxdigit( (unsigned char)uri[1] )
273               && isxdigit( (unsigned char)uri[2] ) )
274        {
275            char buf[3] = { uri[1], uri[2], '\0' };
276            ch = (char) g_ascii_strtoull( buf, NULL, 16 );
277            uri += 2;
278        }
279
280        ++uri;
281        *out++ = ch;
282    }
283
284    *out = '\0';
285    return ret;
286}
287
288/* pattern-matching text; ie, legaltorrents.com */
289char*
290gtr_get_host_from_url( const char * url )
291{
292    char * h = NULL;
293    char * name;
294    const char * first_dot;
295    const char * last_dot;
296
297    tr_urlParse( url, -1, NULL, &h, NULL, NULL );
298    first_dot = strchr( h, '.' );
299    last_dot = strrchr( h, '.' );
300
301    if( ( first_dot ) && ( last_dot ) && ( first_dot != last_dot ) )
302        name = g_strdup( first_dot + 1 );
303    else
304        name = g_strdup( h );
305
306    tr_free( h );
307    return name;
308}
309
310gboolean
311gtr_is_supported_url( const char * str )
312{
313    return !strncmp( str, "ftp://", 6 )
314        || !strncmp( str, "http://", 7 )
315        || !strncmp( str, "https://", 8 );
316}
317
318gboolean
319gtr_is_magnet_link( const char * str )
320{
321    return !strncmp( str, "magnet:?", 8 );
322}
323
324gboolean
325gtr_is_hex_hashcode( const char * str )
326{
327    int i;
328
329    if( !str || ( strlen( str ) != 40 ) )
330        return FALSE;
331
332    for( i=0; i<40; ++i )
333        if( !isxdigit( str[i] ) )
334            return FALSE;
335
336    return TRUE;
337}
338
339static GtkWindow *
340getWindow( GtkWidget * w )
341{
342    if( w == NULL )
343        return NULL;
344
345    if( GTK_IS_WINDOW( w ) )
346        return GTK_WINDOW( w );
347
348    return GTK_WINDOW( gtk_widget_get_ancestor( w, GTK_TYPE_WINDOW ) );
349}
350
351void
352addTorrentErrorDialog( GtkWidget *  child,
353                       int          err,
354                       const char * filename )
355{
356    char * secondary;
357    const char * fmt;
358    GtkWidget * w;
359    GtkWindow * win = getWindow( child );
360
361    switch( err )
362    {
363        case TR_PARSE_ERR: fmt = _( "The torrent file \"%s\" contains invalid data." ); break;
364        case TR_PARSE_DUPLICATE: fmt = _( "The torrent file \"%s\" is already in use." ); break;
365        default: fmt = _( "The torrent file \"%s\" encountered an unknown error." ); break;
366    }
367    secondary = g_strdup_printf( fmt, filename );
368
369    w = gtk_message_dialog_new( win,
370                                GTK_DIALOG_DESTROY_WITH_PARENT,
371                                GTK_MESSAGE_ERROR,
372                                GTK_BUTTONS_CLOSE,
373                                "%s", _( "Error opening torrent" ) );
374    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
375                                              "%s", secondary );
376    g_signal_connect_swapped( w, "response",
377                              G_CALLBACK( gtk_widget_destroy ), w );
378    gtk_widget_show_all( w );
379    g_free( secondary );
380}
381
382typedef void ( PopupFunc )( GtkWidget*, GdkEventButton* );
383
384/* pop up the context menu if a user right-clicks.
385   if the row they right-click on isn't selected, select it. */
386
387gboolean
388on_tree_view_button_pressed( GtkWidget *      view,
389                             GdkEventButton * event,
390                             gpointer         func )
391{
392    GtkTreeView * tv = GTK_TREE_VIEW( view );
393
394    if( event->type == GDK_BUTTON_PRESS  &&  event->button == 3 )
395    {
396        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
397        GtkTreePath *      path;
398        if( gtk_tree_view_get_path_at_pos ( tv,
399                                            (gint) event->x,
400                                            (gint) event->y,
401                                            &path, NULL, NULL, NULL ) )
402        {
403            if( !gtk_tree_selection_path_is_selected ( selection, path ) )
404            {
405                gtk_tree_selection_unselect_all ( selection );
406                gtk_tree_selection_select_path ( selection, path );
407            }
408            gtk_tree_path_free( path );
409        }
410
411        if( func != NULL )
412            ( (PopupFunc*)func )( view, event );
413
414        return TRUE;
415    }
416
417    return FALSE;
418}
419
420/* if the user clicked in an empty area of the list,
421 * clear all the selections. */
422gboolean
423on_tree_view_button_released( GtkWidget *      view,
424                              GdkEventButton * event,
425                              gpointer         unused UNUSED )
426{
427    GtkTreeView * tv = GTK_TREE_VIEW( view );
428
429    if( !gtk_tree_view_get_path_at_pos ( tv,
430                                         (gint) event->x,
431                                         (gint) event->y,
432                                         NULL, NULL, NULL, NULL ) )
433    {
434        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
435        gtk_tree_selection_unselect_all ( selection );
436    }
437
438    return FALSE;
439}
440
441gpointer
442gtr_object_ref_sink( gpointer object )
443{
444#if GLIB_CHECK_VERSION( 2, 10, 0 )
445    g_object_ref_sink( object );
446#else
447    g_object_ref( object );
448    gtk_object_sink( GTK_OBJECT( object ) );
449#endif
450    return object;
451}
452
453int
454gtr_file_trash_or_remove( const char * filename )
455{
456    if( filename && g_file_test( filename, G_FILE_TEST_EXISTS ) )
457    {
458        gboolean trashed = FALSE;
459#ifdef HAVE_GIO
460        GError * err = NULL;
461        GFile *  file = g_file_new_for_path( filename );
462        trashed = g_file_trash( file, NULL, &err );
463        if( err )
464            g_message( "Unable to trash file \"%s\": %s", filename, err->message );
465        g_clear_error( &err );
466        g_object_unref( G_OBJECT( file ) );
467#endif
468
469        if( !trashed && g_remove( filename ) )
470        {
471            const int err = errno;
472            g_message( "Unable to remove file \"%s\": %s", filename, g_strerror( err ) );
473        }
474    }
475
476    return 0;
477}
478
479char*
480gtr_get_help_url( void )
481{
482    const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
483    int          major, minor;
484
485    sscanf( SHORT_VERSION_STRING, "%d.%d", &major, &minor );
486    return g_strdup_printf( fmt, major, minor / 10 );
487}
488
489void
490gtr_open_file( const char * path )
491{
492    if( path )
493    {
494        gboolean opened = FALSE;
495#ifdef HAVE_GIO
496        if( !opened )
497        {
498            GFile * file = g_file_new_for_path( path );
499            char *  uri = g_file_get_uri( file );
500            opened = g_app_info_launch_default_for_uri( uri, NULL, NULL );
501            g_free( uri );
502            g_object_unref( G_OBJECT( file ) );
503        }
504#endif
505        if( !opened )
506        {
507            char * argv[] = { (char*)"xdg-open", (char*)path, NULL };
508            opened = g_spawn_async( NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
509                                    NULL, NULL, NULL, NULL );
510        }
511
512        if( !opened )
513        {
514            g_message( "Unable to open \"%s\"", path );
515        }
516    }
517}
518
519#define VALUE_SERVICE_NAME        "com.transmissionbt.Transmission"
520#define VALUE_SERVICE_OBJECT_PATH "/com/transmissionbt/Transmission"
521#define VALUE_SERVICE_INTERFACE   "com.transmissionbt.Transmission"
522
523gboolean
524gtr_dbus_add_torrent( const char * filename )
525{
526    /* FIXME: why is this static? */
527    static gboolean handled = FALSE;
528
529#ifdef HAVE_DBUS_GLIB
530    char * payload;
531    gsize file_length;
532    char * file_contents = NULL;
533
534    /* If it's a file, load its contents and send them over the wire...
535     * it might be a temporary file that's going to disappear. */
536    if( g_file_get_contents( filename, &file_contents, &file_length, NULL ) )
537        payload = tr_base64_encode( file_contents, file_length, NULL );
538    else if( gtr_is_supported_url( filename ) || gtr_is_magnet_link( filename ) )
539        payload = tr_strdup( filename );
540    else
541        payload = NULL;
542
543    if( payload != NULL )
544    {
545        GError * err = NULL;
546        DBusGConnection * conn;
547        DBusGProxy * proxy = NULL;
548
549        if(( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err )))
550            proxy = dbus_g_proxy_new_for_name (conn, VALUE_SERVICE_NAME,
551                                                     VALUE_SERVICE_OBJECT_PATH,
552                                                     VALUE_SERVICE_INTERFACE );
553        else if( err )
554           g_message( "err: %s", err->message );
555
556        if( proxy )
557            dbus_g_proxy_call( proxy, "AddMetainfo", &err,
558                               G_TYPE_STRING, payload,
559                               G_TYPE_STRING, filename,
560                               G_TYPE_INVALID,
561                               G_TYPE_BOOLEAN, &handled,
562                               G_TYPE_INVALID );
563        if( err )
564           g_message( "err: %s", err->message );
565
566        if( proxy )
567            g_object_unref( proxy );
568        if( conn )
569            dbus_g_connection_unref( conn );
570
571        tr_free( payload );
572    }
573
574    g_free( file_contents );
575
576#endif
577    return handled;
578}
579
580gboolean
581gtr_dbus_present_window( void )
582{
583    static gboolean   success = FALSE;
584
585#ifdef HAVE_DBUS_GLIB
586    DBusGProxy *      proxy = NULL;
587    GError *          err = NULL;
588    DBusGConnection * conn;
589    if( ( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err ) ) )
590        proxy = dbus_g_proxy_new_for_name ( conn, VALUE_SERVICE_NAME,
591                                            VALUE_SERVICE_OBJECT_PATH,
592                                            VALUE_SERVICE_INTERFACE );
593    else if( err )
594        g_message( "err: %s", err->message );
595    if( proxy )
596        dbus_g_proxy_call( proxy, "PresentWindow", &err,
597                           G_TYPE_INVALID,
598                           G_TYPE_BOOLEAN, &success,
599                           G_TYPE_INVALID );
600    if( err )
601        g_message( "err: %s", err->message );
602
603    g_object_unref( proxy );
604    dbus_g_connection_unref( conn );
605#endif
606    return success;
607}
608
609GtkWidget *
610gtr_button_new_from_stock( const char * stock,
611                           const char * mnemonic )
612{
613    GtkWidget * image = gtk_image_new_from_stock( stock,
614                                                  GTK_ICON_SIZE_BUTTON );
615    GtkWidget * button = gtk_button_new_with_mnemonic( mnemonic );
616
617    gtk_button_set_image( GTK_BUTTON( button ), image );
618    return button;
619}
620
621/***
622****
623***/
624
625void
626gtr_priority_combo_set_value( GtkWidget * w, tr_priority_t value )
627{
628    int i;
629    int currentValue;
630    const int column = 0;
631    GtkTreeIter iter;
632    GtkComboBox * combobox = GTK_COMBO_BOX( w );
633    GtkTreeModel * model = gtk_combo_box_get_model( combobox );
634
635    /* do the value and current value match? */
636    if( gtk_combo_box_get_active_iter( combobox, &iter ) ) {
637        gtk_tree_model_get( model, &iter, column, &currentValue, -1 );
638        if( currentValue == value )
639            return;
640    }
641
642    /* find the one to select */
643    i = 0;
644    while(( gtk_tree_model_iter_nth_child( model, &iter, NULL, i++ ))) {
645        gtk_tree_model_get( model, &iter, column, &currentValue, -1 );
646        if( currentValue == value ) {
647            gtk_combo_box_set_active_iter( combobox, &iter );
648            return;
649        }
650    }
651}
652
653tr_priority_t
654gtr_priority_combo_get_value( GtkWidget * w )
655{
656    int value = 0;
657    GtkTreeIter iter;
658    GtkComboBox * combo_box = GTK_COMBO_BOX( w );
659
660    if( gtk_combo_box_get_active_iter( combo_box, &iter ) )
661        gtk_tree_model_get( gtk_combo_box_get_model( combo_box ), &iter, 0, &value, -1 );
662
663    return value;
664}
665
666GtkWidget *
667gtr_priority_combo_new( void )
668{
669    int i;
670    GtkWidget * w;
671    GtkCellRenderer * r;
672    GtkListStore * store;
673    const struct {
674        int value;
675        const char * text;
676    } items[] = {
677        { TR_PRI_HIGH,   N_( "High" )  },
678        { TR_PRI_NORMAL, N_( "Normal" ) },
679        { TR_PRI_LOW,    N_( "Low" )  }
680    };
681
682    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
683    for( i=0; i<(int)G_N_ELEMENTS(items); ++i ) {
684        GtkTreeIter iter;
685        gtk_list_store_append( store, &iter );
686        gtk_list_store_set( store, &iter, 0, items[i].value,
687                                          1, _( items[i].text ),
688                                         -1 );
689    }
690
691    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
692    r = gtk_cell_renderer_text_new( );
693    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
694    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 1, NULL );
695
696    /* cleanup */
697    g_object_unref( store );
698    return w;
699}
700
701/***
702****
703***/
704
705void
706gtr_widget_set_tooltip_text( GtkWidget * w, const char * tip )
707{
708#if GTK_CHECK_VERSION( 2,12,0 )
709    gtk_widget_set_tooltip_text( w, tip );
710#else
711    static GtkTooltips * tips = NULL;
712    if( tips == NULL )
713        tips = gtk_tooltips_new( );
714    gtk_tooltips_set_tip( tips, w, tip, NULL );
715#endif
716}
717
718void
719gtr_toolbar_set_orientation( GtkToolbar      * toolbar,
720                             GtkOrientation    orientation )
721{
722#if GTK_CHECK_VERSION( 2,16,0 )
723    gtk_orientable_set_orientation( GTK_ORIENTABLE( toolbar ), orientation );
724#else
725    gtk_toolbar_set_orientation( toolbar, orientation );
726#endif
727}
728
729/***
730****
731***/
732
733#if !GTK_CHECK_VERSION( 2,12,0 )
734struct gtr_func_data
735{
736    GSourceFunc function;
737    gpointer data;
738};
739
740static void
741gtr_func_data_free( gpointer data )
742{
743#if GTK_CHECK_VERSION( 2,10,0 )
744    g_slice_free( struct gtr_func_data, data );
745#else
746    g_free( data );
747#endif
748}
749
750static struct gtr_func_data *
751gtr_func_data_new( GSourceFunc function, gpointer data )
752{
753#if GTK_CHECK_VERSION( 2,10,0 )
754    struct gtr_func_data * d = g_slice_new( struct gtr_func_data );
755#else
756    struct gtr_func_data * d = g_new( struct gtr_func_data, 1 );
757#endif
758    d->function = function;
759    d->data = data;
760    return d;
761}
762
763static gboolean
764gtr_thread_func( gpointer data )
765{
766    gboolean more;
767    struct gtr_func_data * idle_data = data;
768
769    gdk_threads_enter( );
770    more = idle_data->function( idle_data->data );
771    gdk_threads_leave( );
772
773    return more;
774}
775#endif
776
777void
778gtr_idle_add( GSourceFunc function, gpointer data )
779{
780#if GTK_CHECK_VERSION( 2,12,0 )
781    gdk_threads_add_idle( function, data );
782#else
783    g_idle_add_full( G_PRIORITY_DEFAULT,
784                     gtr_thread_func,
785                     gtr_func_data_new( function, data ),
786                     gtr_func_data_free );
787#endif
788}
789
790guint
791gtr_timeout_add_seconds( guint seconds, GSourceFunc function, gpointer data )
792{
793#if GTK_CHECK_VERSION( 2,14,0 )
794    return gdk_threads_add_timeout_seconds( seconds, function, data );
795#elif GTK_CHECK_VERSION( 2,12,0 )
796    return gdk_threads_add_timeout( seconds*1000, function, data );
797#else
798    return g_timeout_add_full( G_PRIORITY_DEFAULT,
799                               seconds * 1000,
800                               gtr_thread_func,
801                               gtr_func_data_new( function, data ),
802                               gtr_func_data_free );
803#endif
804}
805
806void
807gtr_unrecognized_url_dialog( GtkWidget * parent, const char * url )
808{
809    const char * xt = "xt=urn:btih";
810
811    GtkWindow * window = getWindow( parent );
812
813    GString * gstr = g_string_new( NULL );
814
815    GtkWidget * w = gtk_message_dialog_new( window, 0,
816                                            GTK_MESSAGE_ERROR,
817                                            GTK_BUTTONS_CLOSE,
818                                            "%s", _( "Unrecognized URL" ) );
819
820    g_string_append_printf( gstr, _( "Transmission doesn't know how to use \"%s\"" ), url );
821
822    if( gtr_is_magnet_link( url ) && ( strstr( url, xt ) == NULL ) )
823    {
824        g_string_append_printf( gstr, "\n \n" );
825        g_string_append_printf( gstr, _( "This magnet link appears to be intended for something other than BitTorrent.  BitTorrent magnet links have a section containing \"%s\"." ), xt );
826    }
827
828    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), "%s", gstr->str );
829    g_signal_connect_swapped( w, "response", G_CALLBACK( gtk_widget_destroy ), w );
830    gtk_widget_show( w );
831    g_string_free( gstr, TRUE );
832}
Note: See TracBrowser for help on using the repository browser.