source: trunk/gtk/util.c @ 7774

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

(trunk gtk) use g_remove() instead of g_unlink() so that folders can be deleted too

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