source: trunk/gtk/util.c @ 8758

Last change on this file since 8758 was 8758, checked in by charles, 13 years ago

(trunk gtk) try to make hudson-t happy

  • Property svn:keywords set to Date Rev Author Id
File size: 19.8 KB
Line 
1/******************************************************************************
2 * $Id: util.c 8758 2009-06-29 17:52:24Z 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 <errno.h>
27#include <stdarg.h>
28#include <stdlib.h> /* free() */
29#include <string.h> /* strcmp() */
30
31#include <gtk/gtk.h>
32#include <glib/gi18n.h>
33#include <glib/gstdio.h> /* g_unlink() */
34#ifdef HAVE_GIO
35 #include <gio/gio.h> /* g_file_trash() */
36#endif
37#ifdef HAVE_DBUS_GLIB
38 #include <dbus/dbus-glib.h>
39#endif
40
41#include <evhttp.h>
42
43#include <libtransmission/transmission.h> /* TR_RATIO_NA, TR_RATIO_INF */
44#include <libtransmission/utils.h> /* tr_inf */
45#include <libtransmission/version.h> /* tr_inf */
46
47#include "conf.h"
48#include "hig.h"
49#include "tr-prefs.h"
50#include "util.h"
51
52char*
53tr_strlratio( char * buf, double ratio, size_t buflen )
54{
55    return tr_strratio( buf, buflen, ratio, "\xE2\x88\x9E" );
56}
57
58#define KILOBYTE_FACTOR 1024.0
59#define MEGABYTE_FACTOR ( 1024.0 * 1024.0 )
60#define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 )
61
62char*
63tr_strlsize( char *  buf,
64             guint64 size,
65             size_t  buflen )
66{
67    if( !size )
68        g_strlcpy( buf, _( "None" ), buflen );
69#if GLIB_CHECK_VERSION( 2, 16, 0 )
70    else
71    {
72        char * tmp = g_format_size_for_display( size );
73        g_strlcpy( buf, tmp, buflen );
74        g_free( tmp );
75    }
76#else
77    else if( size < (guint64)KILOBYTE_FACTOR )
78        g_snprintf( buf, buflen,
79                    ngettext( "%'u byte", "%'u bytes",
80                              (guint)size ), (guint)size );
81    else
82    {
83        gdouble displayed_size;
84        if( size < (guint64)MEGABYTE_FACTOR )
85        {
86            displayed_size = (gdouble) size / KILOBYTE_FACTOR;
87            g_snprintf( buf, buflen, _( "%'.1f KB" ), displayed_size );
88        }
89        else if( size < (guint64)GIGABYTE_FACTOR )
90        {
91            displayed_size = (gdouble) size / MEGABYTE_FACTOR;
92            g_snprintf( buf, buflen, _( "%'.1f MB" ), displayed_size );
93        }
94        else
95        {
96            displayed_size = (gdouble) size / GIGABYTE_FACTOR;
97            g_snprintf( buf, buflen, _( "%'.1f GB" ), displayed_size );
98        }
99    }
100#endif
101    return buf;
102}
103
104char*
105tr_strlspeed( char * buf,
106              double kb_sec,
107              size_t buflen )
108{
109    const double speed = kb_sec;
110
111    if( speed < 1000.0 )  /* 0.0 KB to 999.9 KB */
112        g_snprintf( buf, buflen, _( "%'.1f KB/s" ), speed );
113    else if( speed < 102400.0 ) /* 0.98 MB to 99.99 MB */
114        g_snprintf( buf, buflen, _( "%'.2f MB/s" ), ( speed / KILOBYTE_FACTOR ) );
115    else if( speed < 1024000.0 ) /* 100.0 MB to 999.9 MB */
116        g_snprintf( buf, buflen, _( "%'.1f MB/s" ), ( speed / MEGABYTE_FACTOR ) );
117    else /* insane speeds */
118        g_snprintf( buf, buflen, _( "%'.2f GB/s" ), ( speed / GIGABYTE_FACTOR ) );
119
120    return buf;
121}
122
123char*
124tr_strltime( char * buf,
125             int    seconds,
126             size_t buflen )
127{
128    int  days, hours, minutes;
129    char d[128], h[128], m[128], s[128];
130
131    if( seconds < 0 )
132        seconds = 0;
133
134    days = seconds / 86400;
135    hours = ( seconds % 86400 ) / 3600;
136    minutes = ( seconds % 3600 ) / 60;
137    seconds = ( seconds % 3600 ) % 60;
138
139    g_snprintf( d, sizeof( d ), ngettext( "%'d day", "%'d days",
140                                          days ), days );
141    g_snprintf( h, sizeof( h ), ngettext( "%'d hour", "%'d hours",
142                                          hours ), hours );
143    g_snprintf( m, sizeof( m ),
144                ngettext( "%'d minute", "%'d minutes", minutes ), minutes );
145    g_snprintf( s, sizeof( s ),
146                ngettext( "%'d second", "%'d seconds", seconds ), seconds );
147
148    if( days )
149    {
150        if( days >= 4 || !hours )
151        {
152            g_strlcpy( buf, d, buflen );
153        }
154        else
155        {
156            g_snprintf( buf, buflen, "%s, %s", d, h );
157        }
158    }
159    else if( hours )
160    {
161        if( hours >= 4 || !minutes )
162        {
163            g_strlcpy( buf, h, buflen );
164        }
165        else
166        {
167            g_snprintf( buf, buflen, "%s, %s", h, m );
168        }
169    }
170    else if( minutes )
171    {
172        if( minutes >= 4 || !seconds )
173        {
174            g_strlcpy( buf, m, buflen );
175        }
176        else
177        {
178            g_snprintf( buf, buflen, "%s, %s", m, s );
179        }
180    }
181    else
182    {
183        g_strlcpy( buf, s, buflen );
184    }
185
186    return buf;
187}
188
189char *
190gtr_localtime( time_t time )
191{
192    const struct tm tm = *localtime( &time );
193    char            buf[256], *eoln;
194
195    g_strlcpy( buf, asctime( &tm ), sizeof( buf ) );
196    if( ( eoln = strchr( buf, '\n' ) ) )
197        *eoln = '\0';
198
199    return g_locale_to_utf8( buf, -1, NULL, NULL, NULL );
200}
201
202char *
203gtr_localtime2( char * buf, time_t time, size_t buflen )
204{
205    char * tmp = gtr_localtime( time );
206    g_strlcpy( buf, tmp, buflen );
207    g_free( tmp );
208    return buf;
209}
210
211int
212mkdir_p( const char * path,
213         mode_t       mode )
214{
215#if GLIB_CHECK_VERSION( 2, 8, 0 )
216    return !g_mkdir_with_parents( path, mode );
217#else
218    return !tr_mkdirp( path, mode );
219#endif
220}
221
222GSList *
223dupstrlist( GSList * l )
224{
225    GSList * ret = NULL;
226
227    for( ; l != NULL; l = l->next )
228        ret = g_slist_prepend( ret, g_strdup( l->data ) );
229    return g_slist_reverse( ret );
230}
231
232char *
233joinstrlist( GSList *list,
234             char *  sep )
235{
236    GSList * l;
237    GString *gstr = g_string_new ( NULL );
238
239    for( l = list; l != NULL; l = l->next )
240    {
241        g_string_append ( gstr, (char*)l->data );
242        if( l->next != NULL )
243            g_string_append ( gstr, ( sep ) );
244    }
245    return g_string_free ( gstr, FALSE );
246}
247
248void
249freestrlist( GSList *list )
250{
251    g_slist_foreach ( list, (GFunc)g_free, NULL );
252    g_slist_free ( list );
253}
254
255char *
256decode_uri( const char * uri )
257{
258    gboolean in_query = FALSE;
259    char *   ret = g_new( char, strlen( uri ) + 1 );
260    char *   out = ret;
261
262    for( ; uri && *uri; )
263    {
264        char ch = *uri;
265        if( ch == '?' )
266            in_query = TRUE;
267        else if( ch == '+' && in_query )
268            ch = ' ';
269        else if( ch == '%' && isxdigit( (unsigned char)uri[1] )
270               && isxdigit( (unsigned char)uri[2] ) )
271        {
272            char buf[3] = { uri[1], uri[2], '\0' };
273            ch = (char) g_ascii_strtoull( buf, NULL, 16 );
274            uri += 2;
275        }
276
277        ++uri;
278        *out++ = ch;
279    }
280
281    *out = '\0';
282    return ret;
283}
284
285GSList *
286checkfilenames( int    argc,
287                char **argv )
288{
289    int      i;
290    GSList * ret = NULL;
291    char *   pwd = g_get_current_dir( );
292
293    for( i = 0; i < argc; ++i )
294    {
295        char * filename = g_path_is_absolute( argv[i] )
296                          ? g_strdup ( argv[i] )
297                          : g_build_filename( pwd, argv[i], NULL );
298
299        if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
300            ret = g_slist_prepend( ret, filename );
301        else
302            g_free( filename );
303    }
304
305    g_free( pwd );
306    return g_slist_reverse( ret );
307}
308
309static void
310onErrorResponse( GtkWidget * dialog,
311                 int resp    UNUSED,
312                 gpointer    glist )
313{
314    GSList * list = glist;
315
316    if( list )
317    {
318        callbackfunc_t func = list->data;
319        gpointer       user_data = list->next->data;
320        func( user_data );
321        g_slist_free( list );
322    }
323
324    gtk_widget_destroy( dialog );
325}
326
327static GtkWidget *
328verrmsg_full( GtkWindow *    wind,
329              callbackfunc_t func,
330              void *         data,
331              const char *   format,
332              va_list        ap )
333{
334    GtkWidget *dialog;
335    char *     msg;
336    GSList *   funcdata = NULL;
337
338    msg = g_strdup_vprintf( format, ap );
339
340    if( NULL == wind )
341        dialog = gtk_message_dialog_new(
342            NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", msg );
343    else
344        dialog = gtk_message_dialog_new(
345            wind,
346            GTK_DIALOG_MODAL |
347            GTK_DIALOG_DESTROY_WITH_PARENT,
348            GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
349            "%s", msg );
350
351    if( func )
352    {
353        funcdata = g_slist_append( funcdata, (gpointer)func );
354        funcdata = g_slist_append( funcdata, data );
355    }
356    g_signal_connect( dialog, "response", G_CALLBACK(
357                          onErrorResponse ), funcdata );
358    g_free( msg );
359
360    return dialog;
361}
362
363void
364addTorrentErrorDialog( GtkWidget *  child,
365                       int          err,
366                       const char * filename )
367{
368    GtkWidget *  w;
369    GtkWidget *  win;
370    const char * fmt;
371    char *       secondary;
372
373    switch( err )
374    {
375        case TR_EINVALID:
376            fmt = _( "The torrent file \"%s\" contains invalid data." );
377            break;
378
379        case TR_EDUPLICATE:
380            fmt = _( "The torrent file \"%s\" is already in use." ); break;
381
382        default:
383            fmt = _(
384                "The torrent file \"%s\" encountered an unknown error." );
385            break;
386    }
387    secondary = g_strdup_printf( fmt, filename );
388    win = ( !child || GTK_IS_WINDOW( child ) )
389          ? child
390          : gtk_widget_get_ancestor( child ? GTK_WIDGET(
391                                         child ) : NULL, GTK_TYPE_WINDOW );
392    w = gtk_message_dialog_new( GTK_WINDOW( win ),
393                               GTK_DIALOG_DESTROY_WITH_PARENT,
394                               GTK_MESSAGE_ERROR,
395                               GTK_BUTTONS_CLOSE,
396                               "%s", _( "Error opening torrent" ) );
397    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
398                                              "%s", secondary );
399    g_signal_connect_swapped( w, "response",
400                              G_CALLBACK( gtk_widget_destroy ), w );
401    gtk_widget_show_all( w );
402    g_free( secondary );
403}
404
405void
406errmsg( GtkWindow *  wind,
407        const char * format,
408        ... )
409{
410    GtkWidget * dialog;
411    va_list     ap;
412
413    va_start( ap, format );
414    dialog = verrmsg_full( wind, NULL, NULL, format, ap );
415    va_end( ap );
416
417    if( NULL != wind && !GTK_WIDGET_MAPPED( GTK_WIDGET( wind ) ) )
418    {
419        g_signal_connect_swapped( wind, "map",
420                                  G_CALLBACK( gtk_widget_show ), dialog );
421    }
422    else
423    {
424        gtk_widget_show( dialog );
425    }
426}
427
428GtkWidget *
429errmsg_full( GtkWindow *    wind,
430             callbackfunc_t func,
431             void *         data,
432             const char *   format,
433             ... )
434{
435    GtkWidget * dialog;
436    va_list     ap;
437
438    va_start( ap, format );
439    dialog = verrmsg_full( wind, func, data, format, ap );
440    va_end( ap );
441
442    return dialog;
443}
444
445typedef void ( PopupFunc )( GtkWidget*, GdkEventButton* );
446
447/* pop up the context menu if a user right-clicks.
448   if the row they right-click on isn't selected, select it. */
449
450gboolean
451on_tree_view_button_pressed( GtkWidget *      view,
452                             GdkEventButton * event,
453                             gpointer         func )
454{
455    GtkTreeView * tv = GTK_TREE_VIEW( view );
456
457    if( event->type == GDK_BUTTON_PRESS  &&  event->button == 3 )
458    {
459        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
460        GtkTreePath *      path;
461        if( gtk_tree_view_get_path_at_pos ( tv,
462                                            (gint) event->x,
463                                            (gint) event->y,
464                                            &path, NULL, NULL, NULL ) )
465        {
466            if( !gtk_tree_selection_path_is_selected ( selection, path ) )
467            {
468                gtk_tree_selection_unselect_all ( selection );
469                gtk_tree_selection_select_path ( selection, path );
470            }
471            gtk_tree_path_free( path );
472        }
473
474        ( (PopupFunc*)func )( view, event );
475
476        return TRUE;
477    }
478
479    return FALSE;
480}
481
482/* if the user clicked in an empty area of the list,
483 * clear all the selections. */
484gboolean
485on_tree_view_button_released( GtkWidget *      view,
486                              GdkEventButton * event,
487                              gpointer         unused UNUSED )
488{
489    GtkTreeView * tv = GTK_TREE_VIEW( view );
490
491    if( !gtk_tree_view_get_path_at_pos ( tv,
492                                         (gint) event->x,
493                                         (gint) event->y,
494                                         NULL, NULL, NULL, NULL ) )
495    {
496        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
497        gtk_tree_selection_unselect_all ( selection );
498        return TRUE;
499    }
500
501    return FALSE;
502}
503
504gpointer
505tr_object_ref_sink( gpointer object )
506{
507#if GLIB_CHECK_VERSION( 2, 10, 0 )
508    g_object_ref_sink( object );
509#else
510    g_object_ref( object );
511    gtk_object_sink( GTK_OBJECT( object ) );
512#endif
513    return object;
514}
515
516int
517tr_file_trash_or_remove( const char * filename )
518{
519    if( filename && *filename )
520    {
521        gboolean trashed = FALSE;
522#ifdef HAVE_GIO
523        GError * err = NULL;
524        GFile *  file = g_file_new_for_path( filename );
525        trashed = g_file_trash( file, NULL, &err );
526        if( err )
527            g_message( "Unable to trash file \"%s\": %s", filename, err->message );
528        g_clear_error( &err );
529        g_object_unref( G_OBJECT( file ) );
530#endif
531
532        if( !trashed && g_remove( filename ) )
533        {
534            const int err = errno;
535            g_message( "Unable to remove file \"%s\": %s", filename, g_strerror( err ) );
536        }
537    }
538
539    return 0;
540}
541
542char*
543gtr_get_help_url( void )
544{
545    const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
546    int          major, minor;
547
548    sscanf( SHORT_VERSION_STRING, "%d.%d", &major, &minor );
549    return g_strdup_printf( fmt, major, minor / 10 );
550}
551
552void
553gtr_open_file( const char * path )
554{
555    if( path )
556    {
557        gboolean opened = FALSE;
558#ifdef HAVE_GIO
559        if( !opened )
560        {
561            GFile * file = g_file_new_for_path( path );
562            char *  uri = g_file_get_uri( file );
563            opened = g_app_info_launch_default_for_uri( uri, NULL, NULL );
564            g_free( uri );
565            g_object_unref( G_OBJECT( file ) );
566        }
567#endif
568        if( !opened )
569        {
570            char * argv[] = { (char*)"xdg-open", (char*)path, NULL };
571            g_spawn_async( NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
572                           NULL, NULL, NULL, NULL );
573        }
574    }
575}
576
577#define VALUE_SERVICE_NAME        "com.transmissionbt.Transmission"
578#define VALUE_SERVICE_OBJECT_PATH "/com/transmissionbt/Transmission"
579#define VALUE_SERVICE_INTERFACE   "com.transmissionbt.Transmission"
580
581gboolean
582gtr_dbus_add_torrent( const char * filename )
583{
584    /* FIXME: why is this static? */
585    static gboolean success = FALSE;
586#ifdef HAVE_DBUS_GLIB
587    DBusGProxy * proxy = NULL;
588    GError * err = NULL;
589    DBusGConnection * conn;
590    char * file_contents;
591    gsize file_length;
592
593    if( g_file_get_contents( filename, &file_contents, &file_length, NULL ) )
594    {
595        char * b64 = tr_base64_encode( file_contents, file_length, NULL );
596        if(( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err )))
597            proxy = dbus_g_proxy_new_for_name (conn, VALUE_SERVICE_NAME,
598                                                     VALUE_SERVICE_OBJECT_PATH,
599                                                     VALUE_SERVICE_INTERFACE );
600        else if( err )
601           g_message( "err: %s", err->message );
602        if( proxy )
603            dbus_g_proxy_call( proxy, "AddMetainfo", &err,
604                               G_TYPE_STRING, b64,
605                               G_TYPE_INVALID,
606                               G_TYPE_BOOLEAN, &success,
607                               G_TYPE_INVALID );
608        if( err )
609           g_message( "err: %s", err->message );
610
611        if( proxy )
612            g_object_unref( proxy );
613        if( conn )
614            dbus_g_connection_unref( conn );
615
616        tr_free( b64 );
617        g_free( file_contents );
618    }
619    else g_message( "couldn't read %s", filename );
620
621#endif
622    return success;
623}
624
625gboolean
626gtr_dbus_present_window( void )
627{
628    static gboolean   success = FALSE;
629
630#ifdef HAVE_DBUS_GLIB
631    DBusGProxy *      proxy = NULL;
632    GError *          err = NULL;
633    DBusGConnection * conn;
634    if( ( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err ) ) )
635        proxy = dbus_g_proxy_new_for_name ( conn, VALUE_SERVICE_NAME,
636                                            VALUE_SERVICE_OBJECT_PATH,
637                                            VALUE_SERVICE_INTERFACE );
638    else if( err )
639        g_message( "err: %s", err->message );
640    if( proxy )
641        dbus_g_proxy_call( proxy, "PresentWindow", &err,
642                           G_TYPE_INVALID,
643                           G_TYPE_BOOLEAN, &success,
644                           G_TYPE_INVALID );
645    if( err )
646        g_message( "err: %s", err->message );
647
648    g_object_unref( proxy );
649    dbus_g_connection_unref( conn );
650#endif
651    return success;
652}
653
654GtkWidget *
655gtr_button_new_from_stock( const char * stock,
656                           const char * mnemonic )
657{
658    GtkWidget * image = gtk_image_new_from_stock( stock,
659                                                  GTK_ICON_SIZE_BUTTON );
660    GtkWidget * button = gtk_button_new_with_mnemonic( mnemonic );
661
662    gtk_button_set_image( GTK_BUTTON( button ), image );
663    return button;
664}
665
666/***
667****
668***/
669
670void
671gtr_widget_set_tooltip_text( GtkWidget * w, const char * tip )
672{
673#if GTK_CHECK_VERSION( 2,12,0 )
674    gtk_widget_set_tooltip_text( w, tip );
675#else
676    static GtkTooltips * tips = NULL;
677    if( tips == NULL )
678        tips = gtk_tooltips_new( );
679    gtk_tooltips_set_tip( tips, w, tip, NULL );
680#endif
681}
682
683void
684gtr_toolbar_set_orientation( GtkToolbar      * toolbar,
685                             GtkOrientation    orientation )
686{
687#if GTK_CHECK_VERSION( 2,16,0 )
688    gtk_orientable_set_orientation( GTK_ORIENTABLE( toolbar ), orientation );
689#else
690    gtk_toolbar_set_orientation( toolbar, orientation );
691#endif
692}
693
694/***
695****
696***/
697
698#if !GTK_CHECK_VERSION( 2,12,0 )
699struct gtr_func_data
700{
701    GSourceFunc function;
702    gpointer data;
703};
704
705static gboolean
706gtr_thread_func( gpointer data )
707{
708    struct gtr_func_data * idle_data = data;
709    gboolean more;
710
711    gdk_threads_enter( );
712    more = idle_data->function( idle_data->data );
713    gdk_threads_leave( );
714
715    if( !more )
716        g_free( data );
717
718    return more;
719}
720#endif
721
722void
723gtr_idle_add( GSourceFunc function, gpointer data )
724{
725#if GTK_CHECK_VERSION( 2,12,0 )
726    gdk_threads_add_idle( function, data );
727#else
728    struct gtr_func_data * d = g_new( struct gtr_func_data, 1 );
729    d->function = function;
730    d->data = data;
731    g_idle_add( gtr_thread_func, d );
732#endif
733}
734
735guint
736gtr_timeout_add_seconds( guint seconds, GSourceFunc function, gpointer data )
737{
738#if GTK_CHECK_VERSION( 2,14,0 )
739    return gdk_threads_add_timeout_seconds( seconds, function, data );
740#elif GTK_CHECK_VERSION( 2,12,0 )
741    return gdk_threads_add_timeout( seconds*1000, function, data );
742#else
743    struct gtr_func_data * d = g_new( struct gtr_func_data, 1 );
744    d->function = function;
745    d->data = data;
746    return g_timeout_add( seconds*1000, gtr_thread_func, d );
747#endif
748}
Note: See TracBrowser for help on using the repository browser.