source: trunk/gtk/util.c @ 12656

Last change on this file since 12656 was 12656, checked in by jordan, 10 years ago

(trunk gtk) first cut at using GApplication. This lets glib replace hundreds of lines of homegrown code. Whee!

  • Property svn:keywords set to Date Rev Author Id
File size: 15.9 KB
Line 
1/*
2 * This file Copyright (C) Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: util.c 12656 2011-08-09 02:30:31Z jordan $
11 */
12
13#include <ctype.h> /* isxdigit() */
14#include <errno.h>
15#include <stdarg.h>
16#include <string.h> /* strchr(), strrchr(), strlen(), strncmp(), strstr() */
17
18#include <gtk/gtk.h>
19#include <glib/gi18n.h>
20#include <glib/gstdio.h> /* g_unlink() */
21#include <gio/gio.h> /* g_file_trash() */
22
23#include <libtransmission/transmission.h> /* TR_RATIO_NA, TR_RATIO_INF */
24#include <libtransmission/utils.h> /* tr_strratio() */
25#include <libtransmission/web.h> /* tr_webResponseStr() */
26#include <libtransmission/version.h> /* SHORT_VERSION_STRING */
27
28#include "hig.h"
29#include "tr-core.h" /* dbus names */
30#include "util.h"
31
32/***
33****  UNITS
34***/
35
36const int mem_K = 1024;
37const char * mem_K_str = N_("KiB");
38const char * mem_M_str = N_("MiB");
39const char * mem_G_str = N_("GiB");
40const char * mem_T_str = N_("TiB");
41
42const int disk_K = 1024;
43const char * disk_K_str = N_("KiB");
44const char * disk_M_str = N_("MiB");
45const char * disk_G_str = N_("GiB");
46const char * disk_T_str = N_("TiB");
47
48const int speed_K = 1024;
49const char * speed_K_str = N_("KiB/s");
50const char * speed_M_str = N_("MiB/s");
51const char * speed_G_str = N_("GiB/s");
52const char * speed_T_str = N_("TiB/s");
53
54/***
55****
56***/
57
58const char*
59gtr_get_unicode_string( int i )
60{
61    switch( i ) {
62        case GTR_UNICODE_UP:      return "\xE2\x86\x91";
63        case GTR_UNICODE_DOWN:    return "\xE2\x86\x93";
64        case GTR_UNICODE_INF:     return "\xE2\x88\x9E";
65        case GTR_UNICODE_BULLET:  return "\xE2\x88\x99";
66        default:                  return "err";
67    }
68}
69
70char*
71tr_strlratio( char * buf, double ratio, size_t buflen )
72{
73    return tr_strratio( buf, buflen, ratio, gtr_get_unicode_string( GTR_UNICODE_INF ) );
74}
75
76char*
77tr_strlpercent( char * buf, double x, size_t buflen )
78{
79    return tr_strpercent( buf, x, buflen );
80}
81
82char*
83tr_strlsize( char * buf, guint64 bytes, size_t buflen )
84{
85    if( !bytes )
86        g_strlcpy( buf, Q_( "None" ), buflen );
87    else
88        tr_formatter_size_B( buf, bytes, buflen );
89
90    return buf;
91}
92
93char*
94tr_strltime( char * buf, int seconds, size_t buflen )
95{
96    int  days, hours, minutes;
97    char d[128], h[128], m[128], s[128];
98
99    if( seconds < 0 )
100        seconds = 0;
101
102    days = seconds / 86400;
103    hours = ( seconds % 86400 ) / 3600;
104    minutes = ( seconds % 3600 ) / 60;
105    seconds = ( seconds % 3600 ) % 60;
106
107    g_snprintf( d, sizeof( d ), ngettext( "%'d day", "%'d days", days ), days );
108    g_snprintf( h, sizeof( h ), ngettext( "%'d hour", "%'d hours", hours ), hours );
109    g_snprintf( m, sizeof( m ), ngettext( "%'d minute", "%'d minutes", minutes ), minutes );
110    g_snprintf( s, sizeof( s ), ngettext( "%'d second", "%'d seconds", seconds ), seconds );
111
112    if( days )
113    {
114        if( days >= 4 || !hours )
115            g_strlcpy( buf, d, buflen );
116        else
117            g_snprintf( buf, buflen, "%s, %s", d, h );
118    }
119    else if( hours )
120    {
121        if( hours >= 4 || !minutes )
122            g_strlcpy( buf, h, buflen );
123        else
124            g_snprintf( buf, buflen, "%s, %s", h, m );
125    }
126    else if( minutes )
127    {
128        if( minutes >= 4 || !seconds )
129            g_strlcpy( buf, m, buflen );
130        else
131            g_snprintf( buf, buflen, "%s, %s", m, s );
132    }
133    else
134    {
135        g_strlcpy( buf, s, buflen );
136    }
137
138    return buf;
139}
140
141/* pattern-matching text; ie, legaltorrents.com */
142void
143gtr_get_host_from_url( char * buf, size_t buflen, const char * url )
144{
145    char host[1024];
146    const char * pch;
147
148    if(( pch = strstr( url, "://" ))) {
149        const size_t hostlen = strcspn( pch+3, ":/" );
150        const size_t copylen = MIN( hostlen, sizeof(host)-1 );
151        memcpy( host, pch+3, copylen );
152        host[copylen] = '\0';
153    } else {
154        *host = '\0';
155    }
156
157    if( tr_addressIsIP( host ) )
158        g_strlcpy( buf, url, buflen );
159    else {
160        const char * first_dot = strchr( host, '.' );
161        const char * last_dot = strrchr( host, '.' );
162        if( ( first_dot ) && ( last_dot ) && ( first_dot != last_dot ) )
163            g_strlcpy( buf, first_dot + 1, buflen );
164        else
165            g_strlcpy( buf, host, buflen );
166    }
167}
168
169static gboolean
170gtr_is_supported_url( const char * str )
171{
172    return !strncmp( str, "ftp://", 6 )
173        || !strncmp( str, "http://", 7 )
174        || !strncmp( str, "https://", 8 );
175}
176
177gboolean
178gtr_is_magnet_link( const char * str )
179{
180    return !strncmp( str, "magnet:?", 8 );
181}
182
183gboolean
184gtr_is_hex_hashcode( const char * str )
185{
186    int i;
187
188    if( !str || ( strlen( str ) != 40 ) )
189        return FALSE;
190
191    for( i=0; i<40; ++i )
192        if( !isxdigit( str[i] ) )
193            return FALSE;
194
195    return TRUE;
196}
197
198static GtkWindow *
199getWindow( GtkWidget * w )
200{
201    if( w == NULL )
202        return NULL;
203
204    if( GTK_IS_WINDOW( w ) )
205        return GTK_WINDOW( w );
206
207    return GTK_WINDOW( gtk_widget_get_ancestor( w, GTK_TYPE_WINDOW ) );
208}
209
210void
211gtr_add_torrent_error_dialog( GtkWidget * child, int err, const char * file )
212{
213    char * secondary;
214    const char * fmt;
215    GtkWidget * w;
216    GtkWindow * win = getWindow( child );
217
218    switch( err )
219    {
220        case TR_PARSE_ERR: fmt = _( "The torrent file \"%s\" contains invalid data." ); break;
221        case TR_PARSE_DUPLICATE: fmt = _( "The torrent file \"%s\" is already in use." ); break;
222        default: fmt = _( "The torrent file \"%s\" encountered an unknown error." ); break;
223    }
224    secondary = g_strdup_printf( fmt, file );
225
226    w = gtk_message_dialog_new( win,
227                                GTK_DIALOG_DESTROY_WITH_PARENT,
228                                GTK_MESSAGE_ERROR,
229                                GTK_BUTTONS_CLOSE,
230                                "%s", _( "Error opening torrent" ) );
231    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
232                                              "%s", secondary );
233    g_signal_connect_swapped( w, "response",
234                              G_CALLBACK( gtk_widget_destroy ), w );
235    gtk_widget_show_all( w );
236    g_free( secondary );
237}
238
239typedef void ( PopupFunc )( GtkWidget*, GdkEventButton* );
240
241/* pop up the context menu if a user right-clicks.
242   if the row they right-click on isn't selected, select it. */
243
244gboolean
245on_tree_view_button_pressed( GtkWidget *      view,
246                             GdkEventButton * event,
247                             gpointer         func )
248{
249    GtkTreeView * tv = GTK_TREE_VIEW( view );
250
251    if( event->type == GDK_BUTTON_PRESS  &&  event->button == 3 )
252    {
253        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
254        GtkTreePath *      path;
255        if( gtk_tree_view_get_path_at_pos ( tv,
256                                            (gint) event->x,
257                                            (gint) event->y,
258                                            &path, NULL, NULL, NULL ) )
259        {
260            if( !gtk_tree_selection_path_is_selected ( selection, path ) )
261            {
262                gtk_tree_selection_unselect_all ( selection );
263                gtk_tree_selection_select_path ( selection, path );
264            }
265            gtk_tree_path_free( path );
266        }
267
268        if( func != NULL )
269            ( (PopupFunc*)func )( view, event );
270
271        return TRUE;
272    }
273
274    return FALSE;
275}
276
277/* if the user clicked in an empty area of the list,
278 * clear all the selections. */
279gboolean
280on_tree_view_button_released( GtkWidget *      view,
281                              GdkEventButton * event,
282                              gpointer         unused UNUSED )
283{
284    GtkTreeView * tv = GTK_TREE_VIEW( view );
285
286    if( !gtk_tree_view_get_path_at_pos ( tv,
287                                         (gint) event->x,
288                                         (gint) event->y,
289                                         NULL, NULL, NULL, NULL ) )
290    {
291        GtkTreeSelection * selection = gtk_tree_view_get_selection( tv );
292        gtk_tree_selection_unselect_all ( selection );
293    }
294
295    return FALSE;
296}
297
298int
299gtr_file_trash_or_remove( const char * filename )
300{
301    if( filename && g_file_test( filename, G_FILE_TEST_EXISTS ) )
302    {
303        gboolean trashed = FALSE;
304        GError * err = NULL;
305        GFile *  file = g_file_new_for_path( filename );
306        trashed = g_file_trash( file, NULL, &err );
307        if( err )
308            g_message( "Unable to trash file \"%s\": %s", filename, err->message );
309        g_clear_error( &err );
310        g_object_unref( G_OBJECT( file ) );
311
312        if( !trashed && g_remove( filename ) )
313        {
314            const int err = errno;
315            g_message( "Unable to remove file \"%s\": %s", filename, g_strerror( err ) );
316        }
317    }
318
319    return 0;
320}
321
322const char*
323gtr_get_help_uri( void )
324{
325    static char * uri = NULL;
326
327    if( !uri )
328    {
329        int major, minor;
330        const char * fmt = "http://www.transmissionbt.com/help/gtk/%d.%dx";
331        sscanf( SHORT_VERSION_STRING, "%d.%d", &major, &minor );
332        uri = g_strdup_printf( fmt, major, minor / 10 );
333    }
334
335    return uri;
336}
337
338void
339gtr_open_file( const char * path )
340{
341    char * uri = NULL;
342
343    GFile * file = g_file_new_for_path( path );
344    uri = g_file_get_uri( file );
345    g_object_unref( G_OBJECT( file ) );
346
347    if( g_path_is_absolute( path ) )
348        uri = g_strdup_printf( "file://%s", path );
349    else {
350        char * cwd = g_get_current_dir();
351        uri = g_strdup_printf( "file://%s/%s", cwd, path );
352        g_free( cwd );
353    }
354
355    gtr_open_uri( uri );
356    g_free( uri );
357}
358
359void
360gtr_open_uri( const char * uri )
361{
362    if( uri )
363    {
364        gboolean opened = FALSE;
365
366        if( !opened )
367            opened = gtk_show_uri( NULL, uri, GDK_CURRENT_TIME, NULL );
368
369        if( !opened )
370            opened = g_app_info_launch_default_for_uri( uri, NULL, NULL );
371
372        if( !opened ) {
373            char * argv[] = { (char*)"xdg-open", (char*)uri, NULL };
374            opened = g_spawn_async( NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
375                                    NULL, NULL, NULL, NULL );
376        }
377
378        if( !opened )
379            g_message( "Unable to open \"%s\"", uri );
380    }
381}
382
383/***
384****
385***/
386
387void
388gtr_combo_box_set_active_enum( GtkComboBox * combo_box, int value )
389{
390    int i;
391    int currentValue;
392    const int column = 0;
393    GtkTreeIter iter;
394    GtkTreeModel * model = gtk_combo_box_get_model( combo_box );
395
396    /* do the value and current value match? */
397    if( gtk_combo_box_get_active_iter( combo_box, &iter ) ) {
398        gtk_tree_model_get( model, &iter, column, &currentValue, -1 );
399        if( currentValue == value )
400            return;
401    }
402
403    /* find the one to select */
404    i = 0;
405    while(( gtk_tree_model_iter_nth_child( model, &iter, NULL, i++ ))) {
406        gtk_tree_model_get( model, &iter, column, &currentValue, -1 );
407        if( currentValue == value ) {
408            gtk_combo_box_set_active_iter( combo_box, &iter );
409            return;
410        }
411    }
412}
413
414
415GtkWidget *
416gtr_combo_box_new_enum( const char * text_1, ... )
417{
418    GtkWidget * w;
419    GtkCellRenderer * r;
420    GtkListStore * store;
421    va_list vl;
422    const char * text;
423    va_start( vl, text_1 );
424
425    store = gtk_list_store_new( 2, G_TYPE_INT, G_TYPE_STRING );
426
427    text = text_1;
428    if( text != NULL ) do
429    {
430        const int val = va_arg( vl, int );
431        gtk_list_store_insert_with_values( store, NULL, INT_MAX, 0, val, 1, text, -1 );
432        text = va_arg( vl, const char * );
433    }
434    while( text != NULL );
435
436    w = gtk_combo_box_new_with_model( GTK_TREE_MODEL( store ) );
437    r = gtk_cell_renderer_text_new( );
438    gtk_cell_layout_pack_start( GTK_CELL_LAYOUT( w ), r, TRUE );
439    gtk_cell_layout_set_attributes( GTK_CELL_LAYOUT( w ), r, "text", 1, NULL );
440
441    /* cleanup */
442    g_object_unref( store );
443    return w;
444}
445
446int
447gtr_combo_box_get_active_enum( GtkComboBox * combo_box )
448{
449    int value = 0;
450    GtkTreeIter iter;
451
452    if( gtk_combo_box_get_active_iter( combo_box, &iter ) )
453        gtk_tree_model_get( gtk_combo_box_get_model( combo_box ), &iter, 0, &value, -1 );
454
455    return value;
456}
457
458GtkWidget *
459gtr_priority_combo_new( void )
460{
461    return gtr_combo_box_new_enum( _( "High" ),   TR_PRI_HIGH,
462                                   _( "Normal" ), TR_PRI_NORMAL,
463                                   _( "Low" ),    TR_PRI_LOW,
464                                   NULL );
465}
466
467/***
468****
469***/
470
471void
472gtr_widget_set_visible( GtkWidget * w, gboolean b )
473{
474    /* toggle the transient children, too */
475    if( GTK_IS_WINDOW( w ) )
476    {
477        GList * l;
478        GList * windows = gtk_window_list_toplevels( );
479        GtkWindow * window = GTK_WINDOW( w );
480
481        for( l=windows; l!=NULL; l=l->next )
482            if( GTK_IS_WINDOW( l->data ) )
483                if( gtk_window_get_transient_for( GTK_WINDOW( l->data ) ) == window )
484                    gtr_widget_set_visible( GTK_WIDGET( l->data ), b );
485
486        g_list_free( windows );
487    }
488
489    gtk_widget_set_visible( w, b );
490}
491
492void
493gtr_dialog_set_content( GtkDialog * dialog, GtkWidget * content )
494{
495    GtkWidget * vbox = gtk_dialog_get_content_area( dialog );
496    gtk_box_pack_start( GTK_BOX( vbox ), content, TRUE, TRUE, 0 );
497    gtk_widget_show_all( content );
498}
499
500/***
501****
502***/
503
504void
505gtr_http_failure_dialog( GtkWidget * parent, const char * url, long response_code )
506{
507    GtkWindow * window = getWindow( parent );
508
509    GtkWidget * w = gtk_message_dialog_new( window, 0,
510                                            GTK_MESSAGE_ERROR,
511                                            GTK_BUTTONS_CLOSE,
512                                            _( "Error opening \"%s\"" ), url );
513
514    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ),
515                                              _( "Server returned \"%1$ld %2$s\"" ),
516                                              response_code,
517                                              tr_webGetResponseStr( response_code ) );
518
519    g_signal_connect_swapped( w, "response", G_CALLBACK( gtk_widget_destroy ), w );
520    gtk_widget_show( w );
521}
522
523void
524gtr_unrecognized_url_dialog( GtkWidget * parent, const char * url )
525{
526    const char * xt = "xt=urn:btih";
527
528    GtkWindow * window = getWindow( parent );
529
530    GString * gstr = g_string_new( NULL );
531
532    GtkWidget * w = gtk_message_dialog_new( window, 0,
533                                            GTK_MESSAGE_ERROR,
534                                            GTK_BUTTONS_CLOSE,
535                                            "%s", _( "Unrecognized URL" ) );
536
537    g_string_append_printf( gstr, _( "Transmission doesn't know how to use \"%s\"" ), url );
538
539    if( gtr_is_magnet_link( url ) && ( strstr( url, xt ) == NULL ) )
540    {
541        g_string_append_printf( gstr, "\n \n" );
542        g_string_append_printf( gstr, _( "This magnet link appears to be intended for something other than BitTorrent. BitTorrent magnet links have a section containing \"%s\"." ), xt );
543    }
544
545    gtk_message_dialog_format_secondary_text( GTK_MESSAGE_DIALOG( w ), "%s", gstr->str );
546    g_signal_connect_swapped( w, "response", G_CALLBACK( gtk_widget_destroy ), w );
547    gtk_widget_show( w );
548    g_string_free( gstr, TRUE );
549}
550
551/***
552****
553***/
554
555void
556gtr_paste_clipboard_url_into_entry( GtkWidget * e )
557{
558  size_t i;
559
560  char * text[] = {
561    gtk_clipboard_wait_for_text( gtk_clipboard_get( GDK_SELECTION_PRIMARY ) ),
562    gtk_clipboard_wait_for_text( gtk_clipboard_get( GDK_SELECTION_CLIPBOARD ) )
563  };
564
565  for( i=0; i<G_N_ELEMENTS(text); ++i ) {
566      char * s = text[i];
567      if( s && ( gtr_is_supported_url( s ) || gtr_is_magnet_link( s )
568                                           || gtr_is_hex_hashcode( s ) ) ) {
569          gtk_entry_set_text( GTK_ENTRY( e ), s );
570          break;
571      }
572  }
573
574  for( i=0; i<G_N_ELEMENTS(text); ++i )
575    g_free( text[i] );
576}
577
578/***
579****
580***/
581
582void
583gtr_label_set_text( GtkLabel * lb, const char * newstr )
584{
585    const char * oldstr = gtk_label_get_text( lb );
586
587    if( tr_strcmp0( oldstr, newstr ) )
588        gtk_label_set_text( lb, newstr );
589}
Note: See TracBrowser for help on using the repository browser.