source: trunk/gtk/util.c @ 8021

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

(trunk) libT and gtk+ parts for #1889: per-torrent vs. global speed limit confusion

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