source: trunk/gtk/util.c @ 8233

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

(trunk) make it possible to #include "version.h" without having to add -I${TOP}/libtransmission/ to your CFLAGS

  • Property svn:keywords set to Date Rev Author Id
File size: 18.0 KB
Line 
1/******************************************************************************
2 * $Id: util.c 8233 2009-04-13 19:04:21Z 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#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
202int
203mkdir_p( const char * path,
204         mode_t       mode )
205{
206#if GLIB_CHECK_VERSION( 2, 8, 0 )
207    return !g_mkdir_with_parents( path, mode );
208#else
209    return !tr_mkdirp( path, mode );
210#endif
211}
212
213GSList *
214dupstrlist( GSList * l )
215{
216    GSList * ret = NULL;
217
218    for( ; l != NULL; l = l->next )
219        ret = g_slist_prepend( ret, g_strdup( l->data ) );
220    return g_slist_reverse( ret );
221}
222
223char *
224joinstrlist( GSList *list,
225             char *  sep )
226{
227    GSList * l;
228    GString *gstr = g_string_new ( NULL );
229
230    for( l = list; l != NULL; l = l->next )
231    {
232        g_string_append ( gstr, (char*)l->data );
233        if( l->next != NULL )
234            g_string_append ( gstr, ( sep ) );
235    }
236    return g_string_free ( gstr, FALSE );
237}
238
239void
240freestrlist( GSList *list )
241{
242    g_slist_foreach ( list, (GFunc)g_free, NULL );
243    g_slist_free ( list );
244}
245
246char *
247decode_uri( const char * uri )
248{
249    gboolean in_query = FALSE;
250    char *   ret = g_new( char, strlen( uri ) + 1 );
251    char *   out = ret;
252
253    for( ; uri && *uri; )
254    {
255        char ch = *uri;
256        if( ch == '?' )
257            in_query = TRUE;
258        else if( ch == '+' && in_query )
259            ch = ' ';
260        else if( ch == '%' && isxdigit( (unsigned char)uri[1] )
261               && isxdigit( (unsigned char)uri[2] ) )
262        {
263            char buf[3] = { uri[1], uri[2], '\0' };
264            ch = (char) g_ascii_strtoull( buf, NULL, 16 );
265            uri += 2;
266        }
267
268        ++uri;
269        *out++ = ch;
270    }
271
272    *out = '\0';
273    return ret;
274}
275
276GSList *
277checkfilenames( int    argc,
278                char **argv )
279{
280    int      i;
281    GSList * ret = NULL;
282    char *   pwd = g_get_current_dir( );
283
284    for( i = 0; i < argc; ++i )
285    {
286        char * filename = g_path_is_absolute( argv[i] )
287                          ? g_strdup ( argv[i] )
288                          : g_build_filename( pwd, argv[i], NULL );
289
290        if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
291            ret = g_slist_prepend( ret, filename );
292        else
293            g_free( filename );
294    }
295
296    g_free( pwd );
297    return g_slist_reverse( ret );
298}
299
300static void
301onErrorResponse( GtkWidget * dialog,
302                 int resp    UNUSED,
303                 gpointer    glist )
304{
305    GSList * list = glist;
306
307    if( list )
308    {
309        callbackfunc_t func = list->data;
310        gpointer       user_data = list->next->data;
311        func( user_data );
312        g_slist_free( list );
313    }
314
315    gtk_widget_destroy( dialog );
316}
317
318static GtkWidget *
319verrmsg_full( GtkWindow *    wind,
320              callbackfunc_t func,
321              void *         data,
322              const char *   format,
323              va_list        ap )
324{
325    GtkWidget *dialog;
326    char *     msg;
327    GSList *   funcdata = NULL;
328
329    msg = g_strdup_vprintf( format, ap );
330
331    if( NULL == wind )
332        dialog = gtk_message_dialog_new(
333            NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", msg );
334    else
335        dialog = gtk_message_dialog_new(
336            wind,
337            GTK_DIALOG_MODAL |
338            GTK_DIALOG_DESTROY_WITH_PARENT,
339            GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
340            "%s", msg );
341
342    if( func )
343    {
344        funcdata = g_slist_append( funcdata, (gpointer)func );
345        funcdata = g_slist_append( funcdata, data );
346    }
347    g_signal_connect( dialog, "response", G_CALLBACK(
348                          onErrorResponse ), funcdata );
349    g_free( msg );
350
351    return dialog;
352}
353
354void
355addTorrentErrorDialog( GtkWidget *  child,
356                       int          err,
357                       const char * filename )
358{
359    GtkWidget *  w;
360    GtkWidget *  win;
361    const char * fmt;
362    char *       secondary;
363
364    switch( err )
365    {
366        case TR_EINVALID:
367            fmt = _( "The torrent file \"%s\" contains invalid data." );
368            break;
369
370        case TR_EDUPLICATE:
371            fmt = _( "The torrent file \"%s\" is already in use." ); break;
372
373        default:
374            fmt = _(
375                "The torrent file \"%s\" encountered an unknown error." );
376            break;
377    }
378    secondary = g_strdup_printf( fmt, filename );
379    win = ( !child || GTK_IS_WINDOW( child ) )
380          ? child
381          : gtk_widget_get_ancestor( child ? GTK_WIDGET(
382                                         child ) : NULL, GTK_TYPE_WINDOW );
383    w = gtk_message_dialog_new( GTK_WINDOW( win ),
384                               GTK_DIALOG_DESTROY_WITH_PARENT,
385                               GTK_MESSAGE_ERROR,
386                               GTK_BUTTONS_CLOSE,
387                               _( "Error opening torrent" ) );
388    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
389                                              "%s", secondary );
390    g_signal_connect_swapped( w, "response",
391                              G_CALLBACK( gtk_widget_destroy ), w );
392    gtk_widget_show_all( w );
393    g_free( secondary );
394}
395
396void
397errmsg( GtkWindow *  wind,
398        const char * format,
399        ... )
400{
401    GtkWidget * dialog;
402    va_list     ap;
403
404    va_start( ap, format );
405    dialog = verrmsg_full( wind, NULL, NULL, format, ap );
406    va_end( ap );
407
408    if( NULL != wind && !GTK_WIDGET_MAPPED( GTK_WIDGET( wind ) ) )
409    {
410        g_signal_connect_swapped( wind, "map",
411                                  G_CALLBACK( gtk_widget_show ), dialog );
412    }
413    else
414    {
415        gtk_widget_show( dialog );
416    }
417}
418
419GtkWidget *
420errmsg_full( GtkWindow *    wind,
421             callbackfunc_t func,
422             void *         data,
423             const char *   format,
424             ... )
425{
426    GtkWidget * dialog;
427    va_list     ap;
428
429    va_start( ap, format );
430    dialog = verrmsg_full( wind, func, data, format, ap );
431    va_end( ap );
432
433    return dialog;
434}
435
436typedef void ( PopupFunc )( GtkWidget*, GdkEventButton* );
437
438/* pop up the context menu if a user right-clicks.
439   if the row they right-click on isn't selected, select it. */
440
441gboolean
442on_tree_view_button_pressed( GtkWidget *      view,
443                             GdkEventButton * event,
444                             gpointer         func )
445{
446    GtkTreeView * tv = GTK_TREE_VIEW( view );
447
448    if( event->type == GDK_BUTTON_PRESS  &&  event->button == 3 )
449    {
450        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
451        GtkTreePath *      path;
452        if( gtk_tree_view_get_path_at_pos ( tv,
453                                            (gint) event->x,
454                                            (gint) event->y,
455                                            &path, NULL, NULL, NULL ) )
456        {
457            if( !gtk_tree_selection_path_is_selected ( selection, path ) )
458            {
459                gtk_tree_selection_unselect_all ( selection );
460                gtk_tree_selection_select_path ( selection, path );
461            }
462            gtk_tree_path_free( path );
463        }
464
465        ( (PopupFunc*)func )( view, event );
466
467        return TRUE;
468    }
469
470    return FALSE;
471}
472
473/* if the user clicked in an empty area of the list,
474 * clear all the selections. */
475gboolean
476on_tree_view_button_released( GtkWidget *      view,
477                              GdkEventButton * event,
478                              gpointer         unused UNUSED )
479{
480    GtkTreeView * tv = GTK_TREE_VIEW( view );
481
482    if( !gtk_tree_view_get_path_at_pos ( tv,
483                                         (gint) event->x,
484                                         (gint) event->y,
485                                         NULL, NULL, NULL, NULL ) )
486    {
487        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
488        gtk_tree_selection_unselect_all ( selection );
489        return TRUE;
490    }
491
492    return FALSE;
493}
494
495gpointer
496tr_object_ref_sink( gpointer object )
497{
498#if GLIB_CHECK_VERSION( 2, 10, 0 )
499    g_object_ref_sink( object );
500#else
501    g_object_ref( object );
502    gtk_object_sink( GTK_OBJECT( object ) );
503#endif
504    return object;
505}
506
507int
508tr_file_trash_or_remove( const char * filename )
509{
510    if( filename && *filename )
511    {
512        gboolean trashed = FALSE;
513#ifdef HAVE_GIO
514        GError * err = NULL;
515        GFile *  file = g_file_new_for_path( filename );
516        trashed = g_file_trash( file, NULL, &err );
517        if( err )
518            g_message( "Unable to trash file \"%s\": %s", filename, err->message );
519        g_clear_error( &err );
520        g_object_unref( G_OBJECT( file ) );
521#endif
522
523        if( !trashed && g_remove( filename ) )
524        {
525            const int err = errno;
526            g_message( "Unable to remove file \"%s\": %s", filename, g_strerror( err ) );
527        }
528    }
529
530    return 0;
531}
532
533char*
534gtr_get_help_url( void )
535{
536    const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
537    int          major, minor;
538
539    sscanf( SHORT_VERSION_STRING, "%d.%d", &major, &minor );
540    return g_strdup_printf( fmt, major, minor / 10 );
541}
542
543void
544gtr_open_file( const char * path )
545{
546    if( path )
547    {
548        gboolean opened = FALSE;
549#ifdef HAVE_GIO
550        if( !opened )
551        {
552            GFile * file = g_file_new_for_path( path );
553            char *  uri = g_file_get_uri( file );
554            opened = g_app_info_launch_default_for_uri( uri, NULL, NULL );
555            g_free( uri );
556            g_object_unref( G_OBJECT( file ) );
557        }
558#endif
559        if( !opened )
560        {
561            char * argv[] = { (char*)"xdg-open", (char*)path, NULL };
562            g_spawn_async( NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
563                           NULL, NULL, NULL, NULL );
564        }
565    }
566}
567
568#define VALUE_SERVICE_NAME        "com.transmissionbt.Transmission"
569#define VALUE_SERVICE_OBJECT_PATH "/com/transmissionbt/Transmission"
570#define VALUE_SERVICE_INTERFACE   "com.transmissionbt.Transmission"
571
572gboolean
573gtr_dbus_add_torrent( const char * filename )
574{
575    static gboolean   success = FALSE;
576
577#ifdef HAVE_DBUS_GLIB
578    DBusGProxy *      proxy = NULL;
579    GError *          err = NULL;
580    DBusGConnection * conn;
581    if( ( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err ) ) )
582        proxy = dbus_g_proxy_new_for_name ( conn, VALUE_SERVICE_NAME,
583                                            VALUE_SERVICE_OBJECT_PATH,
584                                            VALUE_SERVICE_INTERFACE );
585    else if( err )
586        g_message( "err: %s", err->message );
587    if( proxy )
588        dbus_g_proxy_call( proxy, "AddFile", &err,
589                           G_TYPE_STRING, filename,
590                           G_TYPE_INVALID,
591                           G_TYPE_BOOLEAN, &success,
592                           G_TYPE_INVALID );
593    if( err )
594        g_message( "err: %s", err->message );
595
596    g_object_unref( proxy );
597    dbus_g_connection_unref( conn );
598#endif
599    return success;
600}
601
602gboolean
603gtr_dbus_present_window( void )
604{
605    static gboolean   success = FALSE;
606
607#ifdef HAVE_DBUS_GLIB
608    DBusGProxy *      proxy = NULL;
609    GError *          err = NULL;
610    DBusGConnection * conn;
611    if( ( conn = dbus_g_bus_get( DBUS_BUS_SESSION, &err ) ) )
612        proxy = dbus_g_proxy_new_for_name ( conn, VALUE_SERVICE_NAME,
613                                            VALUE_SERVICE_OBJECT_PATH,
614                                            VALUE_SERVICE_INTERFACE );
615    else if( err )
616        g_message( "err: %s", err->message );
617    if( proxy )
618        dbus_g_proxy_call( proxy, "PresentWindow", &err,
619                           G_TYPE_INVALID,
620                           G_TYPE_BOOLEAN, &success,
621                           G_TYPE_INVALID );
622    if( err )
623        g_message( "err: %s", err->message );
624
625    g_object_unref( proxy );
626    dbus_g_connection_unref( conn );
627#endif
628    return success;
629}
630
631GtkWidget *
632gtr_button_new_from_stock( const char * stock,
633                           const char * mnemonic )
634{
635    GtkWidget * image = gtk_image_new_from_stock( stock,
636                                                  GTK_ICON_SIZE_BUTTON );
637    GtkWidget * button = gtk_button_new_with_mnemonic( mnemonic );
638
639    gtk_button_set_image( GTK_BUTTON( button ), image );
640    return button;
641}
642
643/***
644****
645***/
646
647guint
648gtr_timeout_add_seconds( guint seconds, GSourceFunc function, gpointer data )
649{
650#if GLIB_CHECK_VERSION( 2,14,0 )
651    return g_timeout_add_seconds( seconds, function, data );
652#else
653    return g_timeout_add( seconds*1000, function, data );
654#endif
655}
656
657void
658gtr_widget_set_tooltip_text( GtkWidget * w, const char * tip )
659{
660#if GTK_CHECK_VERSION( 2,12,0 )
661    gtk_widget_set_tooltip_text( w, tip );
662#else
663    static GtkTooltips * tips = NULL;
664    if( tips == NULL )
665        tips = gtk_tooltips_new( );
666    gtk_tooltips_set_tip( tips, w, tip, NULL );
667#endif
668}
Note: See TracBrowser for help on using the repository browser.