source: trunk/gtk/util.c @ 7473

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

(trunk) #1029: When removing local data only remove data from the torrent

  • Property svn:keywords set to Date Rev Author Id
File size: 17.7 KB
Line 
1/******************************************************************************
2 * $Id: util.c 7473 2008-12-23 16:04:11Z charles $
3 *
4 * Copyright (c) 2005-2008 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <ctype.h> /* isxdigit() */
26#include <stdarg.h>
27#include <stdlib.h> /* free() */
28#include <string.h> /* strcmp() */
29
30#include <gtk/gtk.h>
31#include <glib/gi18n.h>
32#include <glib/gstdio.h> /* g_unlink() */
33#ifdef HAVE_GIO
34 #include <gio/gio.h> /* g_file_trash() */
35#endif
36#ifdef HAVE_DBUS_GLIB
37 #include <dbus/dbus-glib.h>
38#endif
39
40#include <libevent/evhttp.h>
41
42#include <libtransmission/transmission.h> /* TR_RATIO_NA, TR_RATIO_INF */
43#include <libtransmission/utils.h> /* tr_inf */
44
45#include "conf.h"
46#include "hig.h"
47#include "tr-prefs.h"
48#include "util.h"
49
50char*
51tr_strlratio( char * buf,
52              double ratio,
53              size_t buflen )
54{
55    if( (int)ratio == TR_RATIO_NA )
56        g_strlcpy( buf, _( "None" ), buflen );
57    else if( (int)ratio == TR_RATIO_INF )
58        g_strlcpy( buf, "\xE2\x88\x9E", buflen );
59    else if( ratio < 10.0 )
60        g_snprintf( buf, buflen, "%'.2f", ratio );
61    else if( ratio < 100.0 )
62        g_snprintf( buf, buflen, "%'.1f", ratio );
63    else
64        g_snprintf( buf, buflen, "%'.0f", ratio );
65    return buf;
66}
67
68#define KILOBYTE_FACTOR 1024.0
69#define MEGABYTE_FACTOR ( 1024.0 * 1024.0 )
70#define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 )
71
72char*
73tr_strlsize( char *  buf,
74             guint64 size,
75             size_t  buflen )
76{
77    if( !size )
78        g_strlcpy( buf, _( "None" ), buflen );
79#if GLIB_CHECK_VERSION( 2, 16, 0 )
80    else
81    {
82        char * tmp = g_format_size_for_display( size );
83        g_strlcpy( buf, tmp, buflen );
84        g_free( tmp );
85    }
86#else
87    else if( size < (guint64)KILOBYTE_FACTOR )
88        g_snprintf( buf, buflen,
89                    ngettext( "%'u byte", "%'u bytes",
90                              (guint)size ), (guint)size );
91    else
92    {
93        gdouble displayed_size;
94        if( size < (guint64)MEGABYTE_FACTOR )
95        {
96            displayed_size = (gdouble) size / KILOBYTE_FACTOR;
97            g_snprintf( buf, buflen, _( "%'.1f KB" ), displayed_size );
98        }
99        else if( size < (guint64)GIGABYTE_FACTOR )
100        {
101            displayed_size = (gdouble) size / MEGABYTE_FACTOR;
102            g_snprintf( buf, buflen, _( "%'.1f MB" ), displayed_size );
103        }
104        else
105        {
106            displayed_size = (gdouble) size / GIGABYTE_FACTOR;
107            g_snprintf( buf, buflen, _( "%'.1f GB" ), displayed_size );
108        }
109    }
110#endif
111    return buf;
112}
113
114char*
115tr_strlspeed( char * buf,
116              double kb_sec,
117              size_t buflen )
118{
119    const double speed = kb_sec;
120
121    if( speed < 1000.0 )  /* 0.0 KB to 999.9 KB */
122        g_snprintf( buf, buflen, _( "%'.1f KB/s" ), speed );
123    else if( speed < 102400.0 ) /* 0.98 MB to 99.99 MB */
124        g_snprintf( buf, buflen, _( "%'.2f MB/s" ), ( speed / 1024 ) );
125    else if( speed < 1024000.0 ) /* 100.0 MB to 999.9 MB */
126        g_snprintf( buf, buflen, _( "%'.1f MB/s" ), ( speed / 1024 ) );
127    else /* insane speeds */
128        g_snprintf( buf, buflen, _( "%'.2f GB/s" ), ( speed / 1048576 ) );
129
130    return buf;
131}
132
133char*
134tr_strltime( char * buf,
135             int    seconds,
136             size_t buflen )
137{
138    int  days, hours, minutes;
139    char d[128], h[128], m[128], s[128];
140
141    if( seconds < 0 )
142        seconds = 0;
143
144    days = seconds / 86400;
145    hours = ( seconds % 86400 ) / 3600;
146    minutes = ( seconds % 3600 ) / 60;
147    seconds = ( seconds % 3600 ) % 60;
148
149    g_snprintf( d, sizeof( d ), ngettext( "%'d day", "%'d days",
150                                          days ), days );
151    g_snprintf( h, sizeof( h ), ngettext( "%'d hour", "%'d hours",
152                                          hours ), hours );
153    g_snprintf( m, sizeof( m ),
154                ngettext( "%'d minute", "%'d minutes", minutes ), minutes );
155    g_snprintf( s, sizeof( s ),
156                ngettext( "%'d second", "%'d seconds", seconds ), seconds );
157
158    if( days )
159    {
160        if( days >= 4 || !hours )
161        {
162            g_strlcpy( buf, d, buflen );
163        }
164        else
165        {
166            g_snprintf( buf, buflen, "%s, %s", d, h );
167        }
168    }
169    else if( hours )
170    {
171        if( hours >= 4 || !minutes )
172        {
173            g_strlcpy( buf, h, buflen );
174        }
175        else
176        {
177            g_snprintf( buf, buflen, "%s, %s", h, m );
178        }
179    }
180    else if( minutes )
181    {
182        if( minutes >= 4 || !seconds )
183        {
184            g_strlcpy( buf, m, buflen );
185        }
186        else
187        {
188            g_snprintf( buf, buflen, "%s, %s", m, s );
189        }
190    }
191    else
192    {
193        g_strlcpy( buf, s, buflen );
194    }
195
196    return buf;
197}
198
199char *
200gtr_localtime( time_t time )
201{
202    const struct tm tm = *localtime( &time );
203    char            buf[256], *eoln;
204
205    g_strlcpy( buf, asctime( &tm ), sizeof( buf ) );
206    if( ( eoln = strchr( buf, '\n' ) ) )
207        *eoln = '\0';
208
209    return g_locale_to_utf8( buf, -1, NULL, NULL, NULL );
210}
211
212int
213mkdir_p( const char * path,
214         mode_t       mode )
215{
216#if GLIB_CHECK_VERSION( 2, 8, 0 )
217    return !g_mkdir_with_parents( path, mode );
218#else
219    return !tr_mkdirp( path, mode );
220#endif
221}
222
223GSList *
224dupstrlist( GSList * l )
225{
226    GSList * ret = NULL;
227
228    for( ; l != NULL; l = l->next )
229        ret = g_slist_prepend( ret, g_strdup( l->data ) );
230    return g_slist_reverse( ret );
231}
232
233char *
234joinstrlist( GSList *list,
235             char *  sep )
236{
237    GSList * l;
238    GString *gstr = g_string_new ( NULL );
239
240    for( l = list; l != NULL; l = l->next )
241    {
242        g_string_append ( gstr, (char*)l->data );
243        if( l->next != NULL )
244            g_string_append ( gstr, ( sep ) );
245    }
246    return g_string_free ( gstr, FALSE );
247}
248
249void
250freestrlist( GSList *list )
251{
252    g_slist_foreach ( list, (GFunc)g_free, NULL );
253    g_slist_free ( list );
254}
255
256char *
257decode_uri( const char * uri )
258{
259    gboolean in_query = FALSE;
260    char *   ret = g_new( char, strlen( uri ) + 1 );
261    char *   out = ret;
262
263    for( ; uri && *uri; )
264    {
265        char ch = *uri;
266        if( ch == '?' )
267            in_query = TRUE;
268        else if( ch == '+' && in_query )
269            ch = ' ';
270        else if( ch == '%' && isxdigit( (unsigned char)uri[1] )
271               && isxdigit( (unsigned char)uri[2] ) )
272        {
273            char buf[3] = { uri[1], uri[2], '\0' };
274            ch = (char) g_ascii_strtoull( buf, NULL, 16 );
275            uri += 2;
276        }
277
278        ++uri;
279        *out++ = ch;
280    }
281
282    *out = '\0';
283    return ret;
284}
285
286GSList *
287checkfilenames( int    argc,
288                char **argv )
289{
290    int      i;
291    GSList * ret = NULL;
292    char *   pwd = g_get_current_dir( );
293
294    for( i = 0; i < argc; ++i )
295    {
296        char * filename = g_path_is_absolute( argv[i] )
297                          ? g_strdup ( argv[i] )
298                          : g_build_filename( pwd, argv[i], NULL );
299
300        if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
301            ret = g_slist_prepend( ret, filename );
302        else
303            g_free( filename );
304    }
305
306    g_free( pwd );
307    return g_slist_reverse( ret );
308}
309
310static void
311onErrorResponse( GtkWidget * dialog,
312                 int resp    UNUSED,
313                 gpointer    glist )
314{
315    GSList * list = glist;
316
317    if( list )
318    {
319        callbackfunc_t func = list->data;
320        gpointer       user_data = list->next->data;
321        func( user_data );
322        g_slist_free( list );
323    }
324
325    gtk_widget_destroy( dialog );
326}
327
328static GtkWidget *
329verrmsg_full( GtkWindow *    wind,
330              callbackfunc_t func,
331              void *         data,
332              const char *   format,
333              va_list        ap )
334{
335    GtkWidget *dialog;
336    char *     msg;
337    GSList *   funcdata = NULL;
338
339    msg = g_strdup_vprintf( format, ap );
340
341    if( NULL == wind )
342        dialog = gtk_message_dialog_new(
343            NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", msg );
344    else
345        dialog = gtk_message_dialog_new(
346            wind,
347            GTK_DIALOG_MODAL |
348            GTK_DIALOG_DESTROY_WITH_PARENT,
349            GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
350            "%s", msg );
351
352    if( func )
353    {
354        funcdata = g_slist_append( funcdata, (gpointer)func );
355        funcdata = g_slist_append( funcdata, data );
356    }
357    g_signal_connect( dialog, "response", G_CALLBACK(
358                          onErrorResponse ), funcdata );
359    g_free( msg );
360
361    return dialog;
362}
363
364void
365addTorrentErrorDialog( GtkWidget *  child,
366                       int          err,
367                       const char * filename )
368{
369    GtkWidget *  w;
370    GtkWidget *  win;
371    const char * fmt;
372    char *       secondary;
373
374    switch( err )
375    {
376        case TR_EINVALID:
377            fmt = _( "The torrent file \"%s\" contains invalid data." );
378            break;
379
380        case TR_EDUPLICATE:
381            fmt = _( "The torrent file \"%s\" is already in use." ); break;
382
383        default:
384            fmt = _(
385                "The torrent file \"%s\" encountered an unknown error." );
386            break;
387    }
388    secondary = g_strdup_printf( fmt, filename );
389    win = ( !child || GTK_IS_WINDOW( child ) )
390          ? child
391          : gtk_widget_get_ancestor( child ? GTK_WIDGET(
392                                         child ) : NULL, GTK_TYPE_WINDOW );
393    w = gtk_message_dialog_new( GTK_WINDOW( win ),
394                               GTK_DIALOG_DESTROY_WITH_PARENT,
395                               GTK_MESSAGE_ERROR,
396                               GTK_BUTTONS_CLOSE,
397                               _( "Error opening torrent" ) );
398    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
399                                              "%s", secondary );
400    g_signal_connect_swapped( w, "response",
401                              G_CALLBACK( gtk_widget_destroy ), w );
402    gtk_widget_show_all( w );
403    g_free( secondary );
404}
405
406void
407errmsg( GtkWindow *  wind,
408        const char * format,
409        ... )
410{
411    GtkWidget * dialog;
412    va_list     ap;
413
414    va_start( ap, format );
415    dialog = verrmsg_full( wind, NULL, NULL, format, ap );
416    va_end( ap );
417
418    if( NULL != wind && !GTK_WIDGET_MAPPED( GTK_WIDGET( wind ) ) )
419    {
420        g_signal_connect_swapped( wind, "map",
421                                  G_CALLBACK( gtk_widget_show ), dialog );
422    }
423    else
424    {
425        gtk_widget_show( dialog );
426    }
427}
428
429GtkWidget *
430errmsg_full( GtkWindow *    wind,
431             callbackfunc_t func,
432             void *         data,
433             const char *   format,
434             ... )
435{
436    GtkWidget * dialog;
437    va_list     ap;
438
439    va_start( ap, format );
440    dialog = verrmsg_full( wind, func, data, format, ap );
441    va_end( ap );
442
443    return dialog;
444}
445
446typedef void ( PopupFunc )( GtkWidget*, GdkEventButton* );
447
448/* pop up the context menu if a user right-clicks.
449   if the row they right-click on isn't selected, select it. */
450
451gboolean
452on_tree_view_button_pressed( GtkWidget *      view,
453                             GdkEventButton * event,
454                             gpointer         func )
455{
456    GtkTreeView * tv = GTK_TREE_VIEW( view );
457
458    if( event->type == GDK_BUTTON_PRESS  &&  event->button == 3 )
459    {
460        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
461        GtkTreePath *      path;
462        if( gtk_tree_view_get_path_at_pos ( tv,
463                                            (gint) event->x,
464                                            (gint) event->y,
465                                            &path, NULL, NULL, NULL ) )
466        {
467            if( !gtk_tree_selection_path_is_selected ( selection, path ) )
468            {
469                gtk_tree_selection_unselect_all ( selection );
470                gtk_tree_selection_select_path ( selection, path );
471            }
472            gtk_tree_path_free( path );
473        }
474
475        ( (PopupFunc*)func )( view, event );
476
477        return TRUE;
478    }
479
480    return FALSE;
481}
482
483/* if the user clicked in an empty area of the list,
484 * clear all the selections. */
485gboolean
486on_tree_view_button_released( GtkWidget *      view,
487                              GdkEventButton * event,
488                              gpointer         unused UNUSED )
489{
490    GtkTreeView * tv = GTK_TREE_VIEW( view );
491
492    if( !gtk_tree_view_get_path_at_pos ( tv,
493                                         (gint) event->x,
494                                         (gint) event->y,
495                                         NULL, NULL, NULL, NULL ) )
496    {
497        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
498        gtk_tree_selection_unselect_all ( selection );
499        return TRUE;
500    }
501
502    return FALSE;
503}
504
505gpointer
506tr_object_ref_sink( gpointer object )
507{
508#if GLIB_CHECK_VERSION( 2, 10, 0 )
509    g_object_ref_sink( object );
510#else
511    g_object_ref( object );
512    gtk_object_sink( GTK_OBJECT( object ) );
513#endif
514    return object;
515}
516
517int
518tr_file_trash_or_unlink( const char * filename )
519{
520    if( filename && *filename )
521    {
522        gboolean trashed = FALSE;
523#ifdef HAVE_GIO
524        GFile *  file = g_file_new_for_path( filename );
525        trashed = g_file_trash( file, NULL, NULL );
526        g_object_unref( G_OBJECT( file ) );
527#endif
528        if( !trashed )
529            g_unlink( filename );
530    }
531
532    return 0;
533}
534
535char*
536gtr_get_help_url( void )
537{
538    const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
539    int          major, minor;
540
541    sscanf( SHORT_VERSION_STRING, "%d.%d", &major, &minor );
542    return g_strdup_printf( fmt, major, minor / 10 );
543}
544
545void
546gtr_open_file( const char * path )
547{
548    if( path )
549    {
550        gboolean opened = FALSE;
551#ifdef HAVE_GIO
552        if( !opened )
553        {
554            GFile * file = g_file_new_for_path( path );
555            char *  uri = g_file_get_uri( file );
556            opened = g_app_info_launch_default_for_uri( uri, NULL, NULL );
557            g_free( uri );
558            g_object_unref( G_OBJECT( file ) );
559        }
560#endif
561        if( !opened )
562        {
563            char * argv[] = { "xdg-open", (char*)path, NULL };
564            g_spawn_async( NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
565                           NULL, NULL, NULL, NULL );
566        }
567    }
568}
569
570#define VALUE_SERVICE_NAME        "com.transmissionbt.Transmission"
571#define VALUE_SERVICE_OBJECT_PATH "/com/transmissionbt/Transmission"
572#define VALUE_SERVICE_INTERFACE   "com.transmissionbt.Transmission"
573
574gboolean
575gtr_dbus_add_torrent( const char * filename )
576{
577    static gboolean   success = FALSE;
578
579#ifdef HAVE_DBUS_GLIB
580    DBusGProxy *      proxy = NULL;
581    GError *          err = NULL;
582    DBusGConnection * conn;
583    if( ( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err ) ) )
584        proxy = dbus_g_proxy_new_for_name ( conn, VALUE_SERVICE_NAME,
585                                            VALUE_SERVICE_OBJECT_PATH,
586                                            VALUE_SERVICE_INTERFACE );
587    else if( err )
588        g_message( "err: %s", err->message );
589    if( proxy )
590        dbus_g_proxy_call( proxy, "AddFile", &err,
591                           G_TYPE_STRING, filename,
592                           G_TYPE_INVALID,
593                           G_TYPE_BOOLEAN, &success,
594                           G_TYPE_INVALID );
595    if( err )
596        g_message( "err: %s", err->message );
597
598    g_object_unref( proxy );
599    dbus_g_connection_unref( conn );
600#endif
601    return success;
602}
603
604gboolean
605gtr_dbus_present_window( void )
606{
607    static gboolean   success = FALSE;
608
609#ifdef HAVE_DBUS_GLIB
610    DBusGProxy *      proxy = NULL;
611    GError *          err = NULL;
612    DBusGConnection * conn;
613    if( ( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err ) ) )
614        proxy = dbus_g_proxy_new_for_name ( conn, VALUE_SERVICE_NAME,
615                                            VALUE_SERVICE_OBJECT_PATH,
616                                            VALUE_SERVICE_INTERFACE );
617    else if( err )
618        g_message( "err: %s", err->message );
619    if( proxy )
620        dbus_g_proxy_call( proxy, "PresentWindow", &err,
621                           G_TYPE_INVALID,
622                           G_TYPE_BOOLEAN, &success,
623                           G_TYPE_INVALID );
624    if( err )
625        g_message( "err: %s", err->message );
626
627    g_object_unref( proxy );
628    dbus_g_connection_unref( conn );
629#endif
630    return success;
631}
632
633GtkWidget *
634gtr_button_new_from_stock( const char * stock,
635                           const char * mnemonic )
636{
637    GtkWidget * image = gtk_image_new_from_stock( stock,
638                                                  GTK_ICON_SIZE_BUTTON );
639    GtkWidget * button = gtk_button_new_with_mnemonic( mnemonic );
640
641    gtk_button_set_image( GTK_BUTTON( button ), image );
642    return button;
643}
644
645/***
646****
647***/
648
649guint
650gtr_timeout_add_seconds( guint        interval,
651                         GSourceFunc  function,
652                         gpointer     data )
653{
654#if GLIB_CHECK_VERSION( 2,14,0 )
655    return g_timeout_add_seconds( interval, function, data );
656#else
657    return g_timeout_add( interval*1000, function, data );
658#endif
659}
Note: See TracBrowser for help on using the repository browser.