source: trunk/gtk/util.c @ 8015

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

(trunk gtk) #1881: Displayed ratio should be truncated, not rounded

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