source: trunk/gtk/main.c @ 5209

Last change on this file since 5209 was 5209, checked in by charles, 14 years ago

more i18n work.

  • Property svn:keywords set to Date Rev Author Id
File size: 35.6 KB
Line 
1/******************************************************************************
2 * $Id: main.c 5209 2008-03-07 03:26:59Z 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 <sys/param.h>
26#include <signal.h>
27#include <string.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <time.h>
31#include <unistd.h>
32
33#include <gtk/gtk.h>
34#include <glib/gi18n.h>
35#include <glib/gstdio.h>
36
37#include <gdk/gdk.h>
38#ifdef GDK_WINDOWING_X11
39#include <X11/Xatom.h>
40#include <gdk/gdkx.h>
41#endif
42
43#include "actions.h"
44#include "conf.h"
45#include "details.h"
46#include "dialogs.h"
47#include "ipc.h"
48#include "makemeta-ui.h"
49#include "msgwin.h"
50#include "notify.h"
51#include "open-dialog.h"
52#include "stats.h"
53#include "tr-core.h"
54#include "tr-icon.h"
55#include "tr-prefs.h"
56#include "tr-torrent.h"
57#include "tr-window.h"
58#include "util.h"
59#include "ui.h"
60
61#include <libtransmission/transmission.h>
62#include <libtransmission/version.h>
63
64/* interval in milliseconds to update the torrent list display */
65#define UPDATE_INTERVAL         1000
66
67/* interval in milliseconds to check for stopped torrents and update display */
68#define EXIT_CHECK_INTERVAL     500
69
70#if GTK_CHECK_VERSION(2,8,0)
71#define SHOW_LICENSE
72static const char * LICENSE = 
73"The Transmission binaries and most of its source code is distributed "
74"license. "
75"\n\n"
76"Some files are copyrighted by Charles Kerr and are covered by "
77"the GPL version 2.  Works owned by the Transmission project "
78"are granted a special exemption to clause 2(b) so that the bulk "
79"of its code can remain under the MIT license.  This exemption does "
80"not extend to original or derived works not owned by the "
81"Transmission project. "
82"\n\n"
83"Permission is hereby granted, free of charge, to any person obtaining "
84"a copy of this software and associated documentation files (the "
85"'Software'), to deal in the Software without restriction, including "
86"without limitation the rights to use, copy, modify, merge, publish, "
87"distribute, sublicense, and/or sell copies of the Software, and to "
88"permit persons to whom the Software is furnished to do so, subject to "
89"the following conditions: "
90"\n\n"
91"The above copyright notice and this permission notice shall be included "
92"in all copies or substantial portions of the Software. "
93"\n\n"
94"THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, "
95"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
96"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. "
97"IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY "
98"CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, "
99"TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE "
100"SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.";
101#endif
102
103struct cbdata
104{
105    guint          idle_hide_mainwindow_tag;
106    GtkWindow    * wind;
107    TrCore       * core;
108    gpointer       icon;
109    GtkWidget    * msgwin;
110    GtkWidget    * prefs;
111    guint          timer;
112    gboolean       closing;
113    GList        * errqueue;
114    GHashTable   * tor2details;
115    GHashTable   * details2tor;
116    gboolean       minimized;
117};
118
119#define CBDATA_PTR "callback-data-pointer"
120
121static GtkUIManager * myUIManager = NULL;
122
123static gboolean
124sendremote( GList * files, gboolean sendquit );
125static void
126appsetup( TrWindow * wind, GList * args,
127          struct cbdata *,
128          gboolean paused, gboolean minimized );
129static void
130winsetup( struct cbdata * cbdata, TrWindow * wind );
131static void
132makeicon( struct cbdata * cbdata );
133static void
134wannaquit( void * vdata );
135static void
136setupdrag(GtkWidget *widget, struct cbdata *data);
137static void
138gotdrag(GtkWidget *widget, GdkDragContext *dc, gint x, gint y,
139        GtkSelectionData *sel, guint info, guint time, gpointer gdata);
140
141static void
142coreerr( TrCore * core, enum tr_core_err code, const char * msg,
143         gpointer gdata );
144static void
145coreprompt( TrCore *, GList *, gpointer, gpointer );
146static void
147initializeFromPrefs( struct cbdata * cbdata );
148static void
149prefschanged( TrCore * core, const char * key, gpointer data );
150static gboolean
151updatemodel(gpointer gdata);
152
153struct counts_data
154{
155    int totalCount;
156    int activeCount;
157    int inactiveCount;
158};
159
160static void
161accumulateStatusForeach( GtkTreeModel * model,
162                         GtkTreePath  * path UNUSED,
163                         GtkTreeIter  * iter,
164                         gpointer       user_data )
165{
166    int status = 0;
167    struct counts_data * counts = user_data;
168
169    ++counts->totalCount;
170
171    gtk_tree_model_get( model, iter, MC_STATUS, &status, -1 );
172
173    if( TR_STATUS_IS_ACTIVE( status ) )
174        ++counts->activeCount;
175    else
176        ++counts->inactiveCount;
177}
178
179static void
180accumulateCanUpdateForeach (GtkTreeModel * model,
181                            GtkTreePath  * path UNUSED,
182                            GtkTreeIter  * iter,
183                            gpointer       accumulated_status)
184{
185    tr_torrent * tor;
186    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
187    *(int*)accumulated_status |= tr_torrentCanManualUpdate( tor );
188}
189
190static void
191refreshTorrentActions( GtkTreeSelection * s )
192{
193    int canUpdate;
194    struct counts_data counts;
195
196    counts.activeCount = 0;
197    counts.inactiveCount = 0;
198    counts.totalCount = 0;
199    gtk_tree_selection_selected_foreach( s, accumulateStatusForeach, &counts );
200    action_sensitize( "pause-torrent", counts.activeCount!=0 );
201    action_sensitize( "start-torrent", counts.inactiveCount!=0 );
202    action_sensitize( "close-torrent", counts.totalCount!=0 );
203    action_sensitize( "delete-torrent", counts.totalCount!=0 );
204    action_sensitize( "verify-torrent", counts.totalCount!=0 );
205    action_sensitize( "show-torrent-details", counts.totalCount==1 );
206
207    canUpdate = 0;
208    gtk_tree_selection_selected_foreach( s, accumulateCanUpdateForeach, &canUpdate );
209    action_sensitize( "update-tracker", canUpdate!=0 );
210}
211
212static void
213selectionChangedCB( GtkTreeSelection * s, gpointer unused UNUSED )
214{
215    refreshTorrentActions( s );
216}
217
218static void
219windowStateChanged( GtkWidget * widget UNUSED, GdkEventWindowState * event, gpointer gdata )
220{
221    if( event->changed_mask & GDK_WINDOW_STATE_ICONIFIED )
222    {
223        struct cbdata * cbdata = gdata;
224        cbdata->minimized = ( event->new_window_state & GDK_WINDOW_STATE_ICONIFIED ) ? 1 : 0;
225    }
226}
227
228static sig_atomic_t global_sigcount = 0;
229
230static void
231fatalsig( int sig )
232{
233    static const int SIGCOUNT_MAX = 3; /* revert to default handler after this many */
234
235    if( ++global_sigcount >= SIGCOUNT_MAX )
236    {
237        signal( sig, SIG_DFL );
238        raise( sig );
239    }
240}
241
242
243static void
244setupsighandlers( void )
245{
246#ifdef G_OS_WIN32
247  const int sigs[] = { SIGINT, SIGTERM };
248#else
249  const int sigs[] = { SIGHUP, SIGINT, SIGQUIT, SIGTERM };
250#endif
251  guint i;
252
253  for( i=0; i<G_N_ELEMENTS(sigs); ++i )
254      signal( sigs[i], fatalsig );
255}
256
257int
258main( int argc, char ** argv )
259{
260    char * err;
261    struct cbdata * cbdata;
262    GList * argfiles;
263    GError * gerr;
264    gboolean didinit = FALSE;
265    gboolean didlock = FALSE;
266    gboolean sendquit = FALSE;
267    gboolean startpaused = FALSE;
268    gboolean startminimized = FALSE;
269    char * domain = "transmission";
270    GOptionEntry entries[] = {
271        { "paused", 'p', 0, G_OPTION_ARG_NONE, &startpaused,
272          _("Start with all torrents paused"), NULL },
273        { "quit", 'q', 0, G_OPTION_ARG_NONE, &sendquit,
274          _( "Request that the running instance quit"), NULL },
275#ifdef STATUS_ICON_SUPPORTED
276        { "minimized", 'm', 0, G_OPTION_ARG_NONE, &startminimized,
277          _( "Start minimized in system tray"), NULL },
278#endif
279        { NULL, 0, 0, 0, NULL, NULL, NULL }
280    };
281
282    cbdata = g_new0( struct cbdata, 1 );
283    cbdata->tor2details = g_hash_table_new( g_str_hash, g_str_equal );
284    cbdata->details2tor = g_hash_table_new( g_direct_hash, g_direct_equal );
285
286    /* bind the gettext domain */
287    bindtextdomain( domain, TRANSMISSIONLOCALEDIR );
288    bind_textdomain_codeset( domain, "UTF-8" );
289    textdomain( domain );
290    g_set_application_name( _( "Transmission" ) );
291
292    /* initialize gtk */
293    g_thread_init( NULL );
294    gerr = NULL;
295    if( !gtk_init_with_args( &argc, &argv, _("[torrent files]"), entries, domain, &gerr ) ) {
296        g_message( "%s", gerr->message );
297        g_clear_error( &gerr );
298        return 0;
299    }
300
301    tr_notify_init( );
302
303    didinit = cf_init( tr_getPrefsDirectory(), NULL ); /* must come before actions_init */
304    tr_prefs_init_global( );
305    myUIManager = gtk_ui_manager_new ();
306    actions_init ( myUIManager, cbdata );
307    gtk_ui_manager_add_ui_from_string (myUIManager, fallback_ui_file, -1, NULL);
308    gtk_ui_manager_ensure_update (myUIManager);
309    gtk_window_set_default_icon_name ( "transmission" );
310
311    argfiles = checkfilenames( argc-1, argv+1 );
312    didlock = didinit && sendremote( argfiles, sendquit );
313    setupsighandlers( ); /* set up handlers for fatal signals */
314
315    if( ( didinit || cf_init( tr_getPrefsDirectory(), &err ) ) &&
316        ( didlock || cf_lock( &err ) ) )
317    {
318        cbdata->core = tr_core_new( );
319
320        /* create main window now to be a parent to any error dialogs */
321        GtkWindow * mainwind = GTK_WINDOW( tr_window_new( myUIManager, cbdata->core ) );
322        g_signal_connect( mainwind, "window-state-event", G_CALLBACK(windowStateChanged), cbdata );
323
324        /* set message level here before tr_init() */
325        msgwin_loadpref( );
326
327        appsetup( mainwind, argfiles, cbdata, startpaused, startminimized );
328    }
329    else
330    {
331        gtk_widget_show( errmsg_full( NULL, (callbackfunc_t)gtk_main_quit,
332                                      NULL, "%s", err ) );
333        g_free( err );
334    }
335
336    freestrlist(argfiles);
337
338    gtk_main();
339
340    return 0;
341}
342
343static gboolean
344sendremote( GList * files, gboolean sendquit )
345{
346    const gboolean didlock = cf_lock( NULL );
347
348    /* send files if there's another instance, otherwise start normally */
349    if( !didlock && files )
350        exit( ipc_sendfiles_blocking( files ) ? 0 : 1 );
351
352    /* either send a quit message or exit if no other instance */
353    if( sendquit )
354        exit( didlock ? 0 : !ipc_sendquit_blocking() );
355
356    return didlock;
357}
358
359static void
360appsetup( TrWindow * wind, GList * args,
361          struct cbdata * cbdata,
362          gboolean forcepause, gboolean minimized )
363{
364    /* fill out cbdata */
365    cbdata->wind       = NULL;
366    cbdata->icon       = NULL;
367    cbdata->msgwin     = NULL;
368    cbdata->prefs      = NULL;
369    cbdata->timer      = 0;
370    cbdata->closing    = FALSE;
371    cbdata->errqueue   = NULL;
372    cbdata->minimized  = minimized;
373
374    actions_set_core( cbdata->core );
375
376    /* set up core handlers */
377    g_signal_connect( cbdata->core, "error", G_CALLBACK( coreerr ), cbdata );
378    g_signal_connect( cbdata->core, "destination-prompt",
379                      G_CALLBACK( coreprompt ), cbdata );
380    g_signal_connect_swapped( cbdata->core, "quit",
381                              G_CALLBACK( wannaquit ), cbdata );
382    g_signal_connect( cbdata->core, "prefs-changed",
383                      G_CALLBACK( prefschanged ), cbdata );
384
385    /* apply a few prefs */
386
387    if( minimized )
388        tr_core_set_pref_bool( cbdata->core, PREF_KEY_SYSTRAY, TRUE );
389    initializeFromPrefs( cbdata );
390
391    /* add torrents from command-line and saved state */
392    tr_core_load( cbdata->core, forcepause );
393
394    if( NULL != args )
395    {
396        tr_ctor * ctor = tr_ctorNew( tr_core_handle( cbdata->core ) );
397        if( forcepause )
398            tr_ctorSetPaused( ctor, TR_FORCE, TRUE );
399        tr_core_add_list( cbdata->core, args, ctor );
400    }
401    tr_core_torrents_added( cbdata->core );
402
403    /* set up the ipc socket */
404    ipc_socket_setup( GTK_WINDOW( wind ), cbdata->core );
405
406    /* set up main window */
407    winsetup( cbdata, wind );
408
409    /* start model update timer */
410    cbdata->timer = g_timeout_add( UPDATE_INTERVAL, updatemodel, cbdata );
411    updatemodel( cbdata );
412
413    /* show the window */
414    if( minimized ) {
415        gtk_window_iconify( wind );
416        gtk_window_set_skip_taskbar_hint( cbdata->wind, cbdata->icon != NULL );
417    }
418    gtk_widget_show( GTK_WIDGET( wind ) );
419}
420
421
422/**
423 * hideMainWindow, and the timeout hack in toggleMainWindow,
424 * are loosely cribbed from Colin Walters' tr-shell.c in Rhythmbox
425 */
426static gboolean
427idle_hide_mainwindow( gpointer window )
428{
429    gtk_widget_hide( window );
430    return FALSE;
431}
432static void
433hideMainWindow( struct cbdata * cbdata )
434{
435#if defined(STATUS_ICON_SUPPORTED) && defined(GDK_WINDOWING_X11)
436    GdkRectangle  bounds;
437    gulong        data[4];
438    Display      *dpy;
439    GdkWindow    *gdk_window;
440
441    gtk_status_icon_get_geometry( GTK_STATUS_ICON( cbdata->icon ), NULL, &bounds, NULL );
442    gdk_window = GTK_WIDGET (cbdata->wind)->window;
443    dpy = gdk_x11_drawable_get_xdisplay (gdk_window);
444
445    data[0] = bounds.x;
446    data[1] = bounds.y;
447    data[2] = bounds.width;
448    data[3] = bounds.height;
449
450    XChangeProperty (dpy,
451                     GDK_WINDOW_XID (gdk_window),
452                     gdk_x11_get_xatom_by_name_for_display (gdk_drawable_get_display (gdk_window),
453                     "_NET_WM_ICON_GEOMETRY"),
454                     XA_CARDINAL, 32, PropModeReplace,
455                     (guchar*)&data, 4);
456
457    gtk_window_set_skip_taskbar_hint( cbdata->wind, TRUE );
458#endif
459    gtk_window_iconify( cbdata->wind );
460}
461
462static void
463clearTag( guint * tag )
464{
465    if( *tag )
466        g_source_remove( *tag );
467    *tag = 0;
468}
469
470static void
471toggleMainWindow( struct cbdata * cbdata )
472{
473    GtkWindow * window = GTK_WINDOW( cbdata->wind );
474    const int hide = !cbdata->minimized;
475    static int x=0, y=0;
476
477    if( hide )
478    {
479        gtk_window_get_position( window, &x, &y );
480        clearTag( &cbdata->idle_hide_mainwindow_tag );
481        hideMainWindow( cbdata );
482        cbdata->idle_hide_mainwindow_tag = g_timeout_add( 250, idle_hide_mainwindow, window );
483    }
484    else
485    {
486        gtk_window_set_skip_taskbar_hint( window, FALSE );
487        gtk_window_move( window, x, y );
488        gtk_widget_show( GTK_WIDGET( window ) );
489        gtk_window_deiconify( window );
490#if GTK_CHECK_VERSION(2,8,0)
491        gtk_window_present_with_time( window, gtk_get_current_event_time( ) );
492#else
493        gtk_window_present( window );
494#endif
495    }
496}
497
498static gboolean
499winclose( GtkWidget * w UNUSED, GdkEvent * event UNUSED, gpointer gdata )
500{
501    struct cbdata * cbdata = gdata;
502
503    if( cbdata->icon != NULL )
504        action_activate ("toggle-main-window");
505    else
506        askquit( cbdata->core, cbdata->wind, wannaquit, cbdata );
507
508    return TRUE; /* don't propagate event further */
509}
510
511static void
512rowChangedCB( GtkTreeModel  * model UNUSED,
513              GtkTreePath   * path,
514              GtkTreeIter   * iter UNUSED,
515              gpointer        sel)
516{
517    if( gtk_tree_selection_path_is_selected ( sel, path ) )
518        refreshTorrentActions( GTK_TREE_SELECTION(sel) );
519}
520
521static void
522winsetup( struct cbdata * cbdata, TrWindow * wind )
523{
524    GtkTreeModel * model;
525    GtkTreeSelection * sel;
526
527    g_assert( NULL == cbdata->wind );
528    cbdata->wind = GTK_WINDOW( wind );
529
530    sel = tr_window_get_selection( cbdata->wind );
531    g_signal_connect( sel, "changed", G_CALLBACK(selectionChangedCB), NULL );
532    selectionChangedCB( sel, NULL );
533    model = tr_core_model( cbdata->core );
534    g_signal_connect( model, "row-changed", G_CALLBACK(rowChangedCB), sel );
535    g_signal_connect( wind, "delete-event", G_CALLBACK( winclose ), cbdata );
536    refreshTorrentActions( sel );
537   
538    setupdrag( GTK_WIDGET(wind), cbdata );
539}
540
541static void
542makeicon( struct cbdata * cbdata )
543{
544    if( cbdata->icon == NULL )
545        cbdata->icon = tr_icon_new( cbdata->core );
546}
547
548static gpointer
549quitThreadFunc( gpointer gdata )
550{
551    struct cbdata * cbdata = gdata;
552
553    tr_close( tr_core_handle( cbdata->core ) );
554
555    /* shutdown the gui */
556    if( cbdata->prefs )
557        gtk_widget_destroy( GTK_WIDGET( cbdata->prefs ) );
558    if( cbdata->wind )
559        gtk_widget_destroy( GTK_WIDGET( cbdata->wind ) );
560    g_object_unref( cbdata->core );
561    if( cbdata->icon )
562        g_object_unref( cbdata->icon );
563    if( cbdata->errqueue ) {
564        g_list_foreach( cbdata->errqueue, (GFunc)g_free, NULL );
565        g_list_free( cbdata->errqueue );
566    }
567
568    g_hash_table_destroy( cbdata->details2tor );
569    g_hash_table_destroy( cbdata->tor2details );
570    g_free( cbdata );
571
572    /* exit the gtk main loop */
573    gtk_main_quit( );
574    return NULL;
575}
576
577/* since there are no buttons in the dialog, gtk tries to
578 * select one of the labels, which looks ugly... so force
579 * the dialog's primary and secondary labels to be unselectable */
580static void
581deselectLabels( GtkWidget * w, gpointer unused UNUSED )
582{
583    if( GTK_IS_LABEL( w ) )
584        gtk_label_set_selectable( GTK_LABEL(w), FALSE );
585    else if( GTK_IS_CONTAINER( w ) )
586        gtk_container_foreach( GTK_CONTAINER(w), deselectLabels, NULL );
587}
588
589static void
590do_exit_cb( GtkWidget *w UNUSED, gpointer data UNUSED )
591{
592    exit( 0 );
593}
594
595static void
596wannaquit( void * vdata )
597{
598    char * str;
599    GtkWidget * r, * p, * b, * w, *c;
600    struct cbdata * cbdata = vdata;
601
602    /* stop the update timer */
603    if( cbdata->timer ) {
604        g_source_remove( cbdata->timer );
605        cbdata->timer = 0;
606    }
607
608    c = GTK_WIDGET( cbdata->wind );
609    gtk_container_remove( GTK_CONTAINER( c ), gtk_bin_get_child( GTK_BIN( c ) ) );
610
611    r = gtk_alignment_new(0.5, 0.5, 0.01, 0.01);
612    gtk_container_add(GTK_CONTAINER(c), r);
613
614    p = gtk_table_new(2, 2, FALSE);
615    gtk_container_add(GTK_CONTAINER(r), p);
616
617    w = gtk_image_new_from_stock( GTK_STOCK_NETWORK, GTK_ICON_SIZE_DIALOG );
618    gtk_table_attach(GTK_TABLE(p), w, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 2, 2);
619
620    w = gtk_label_new("");
621    str = g_strdup_printf( "<b>%s</b>\n%s", _("Closing Connections"), _("Sending upload/download totals to tracker...") );
622    gtk_label_set_markup(GTK_LABEL(w), str );
623    gtk_table_attach(GTK_TABLE(p), w, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 10, 0);
624    g_free( str );
625
626    b = gtk_alignment_new(0.0, 1.0, 0.01, 0.01);
627    w = gtk_button_new_with_label( _( "_Quit Immediately" ) );
628    gtk_button_set_image( GTK_BUTTON(w), gtk_image_new_from_stock( GTK_STOCK_QUIT, GTK_ICON_SIZE_BUTTON ) );
629    g_signal_connect(w, "clicked", G_CALLBACK(do_exit_cb), NULL);
630    gtk_container_add(GTK_CONTAINER(b), w);
631    gtk_table_attach(GTK_TABLE(p), b, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 10, 0);
632
633    gtk_widget_show_all(r);
634
635    /* clear the UI */
636    gtk_list_store_clear( GTK_LIST_STORE( tr_core_model( cbdata->core ) ) );
637
638    /* shut down libT */
639    g_thread_create( quitThreadFunc, vdata, TRUE, NULL );
640}
641
642static void
643gotdrag( GtkWidget         * widget UNUSED,
644         GdkDragContext    * dc,
645         gint                x UNUSED,
646         gint                y UNUSED,
647         GtkSelectionData  * sel,
648         guint               info UNUSED,
649         guint               time,
650         gpointer            gdata )
651{
652    struct cbdata * data = gdata;
653    GList * paths = NULL;
654    GList * freeme = NULL;
655
656#if 0
657    int i;
658    char *sele = gdk_atom_name(sel->selection);
659    char *targ = gdk_atom_name(sel->target);
660    char *type = gdk_atom_name(sel->type);
661
662    g_message( "dropped file: sel=%s targ=%s type=%s fmt=%i len=%i",
663               sele, targ, type, sel->format, sel->length );
664    g_free(sele);
665    g_free(targ);
666    g_free(type);
667    if( sel->format == 8 ) {
668        for( i=0; i<sel->length; ++i )
669            fprintf(stderr, "%02X ", sel->data[i]);
670        fprintf(stderr, "\n");
671    }
672#endif
673
674    if( ( sel->format == 8 ) &&
675        ( sel->selection == gdk_atom_intern( "XdndSelection", FALSE ) ) )
676    {
677        int i;
678        char * str = g_strndup( (char*)sel->data, sel->length );
679        gchar ** files = g_strsplit_set( str, "\r\n", -1 );
680        for( i=0; files && files[i]; ++i )
681        {
682            char * filename;
683            if( !*files[i] ) /* empty filename... */
684                continue;
685
686            /* decode the filename */
687            filename = decode_uri( files[i] );
688            freeme = g_list_prepend( freeme, filename );
689            if( !g_utf8_validate( filename, -1, NULL ) )
690                continue;
691
692            /* walk past "file://", if present */
693            if( g_str_has_prefix( filename, "file:" ) ) {
694                filename += 5;
695                while( g_str_has_prefix( filename, "//" ) )
696                    ++filename;
697            }
698
699            /* if the file doesn't exist, the first part
700               might be a hostname ... walk past it. */
701            if( !g_file_test( filename, G_FILE_TEST_EXISTS ) ) {
702                char * pch = strchr( filename + 1, '/' );
703                if( pch != NULL )
704                    filename = pch;
705            }
706
707            /* finally, add it to the list of torrents to try adding */
708            if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
709                paths = g_list_prepend( paths, filename );
710        }
711
712        /* try to add any torrents we found */
713        if( paths != NULL )
714        {
715            tr_ctor * ctor = tr_ctorNew( tr_core_handle( data->core ) );
716            paths = g_list_reverse( paths );
717            tr_core_add_list( data->core, paths, ctor );
718            tr_core_torrents_added( data->core );
719            g_list_free( paths );
720        }
721
722        freestrlist( freeme );
723        g_strfreev( files );
724        g_free( str );
725    }
726
727    gtk_drag_finish(dc, (NULL != paths), FALSE, time);
728}
729
730static void
731setupdrag(GtkWidget *widget, struct cbdata *data) {
732  GtkTargetEntry targets[] = {
733    { "STRING",     0, 0 },
734    { "text/plain", 0, 0 },
735    { "text/uri-list", 0, 0 },
736  };
737
738  g_signal_connect(widget, "drag_data_received", G_CALLBACK(gotdrag), data);
739
740  gtk_drag_dest_set(widget, GTK_DEST_DEFAULT_ALL, targets,
741                    ALEN(targets), GDK_ACTION_COPY | GDK_ACTION_MOVE);
742}
743
744static void
745coreerr( TrCore * core UNUSED, enum tr_core_err code, const char * msg,
746         gpointer gdata )
747{
748    struct cbdata * cbdata = gdata;
749    char          * joined;
750
751    switch( code )
752    {
753        case TR_CORE_ERR_ADD_TORRENT:
754            cbdata->errqueue = g_list_append( cbdata->errqueue,
755                                              g_strdup( msg ) );
756            return;
757        case TR_CORE_ERR_NO_MORE_TORRENTS:
758            if( NULL != cbdata->errqueue )
759            {
760                joined = joinstrlist( cbdata->errqueue, "\n" );
761                errmsg( cbdata->wind,
762                        ngettext( "Failed to load torrent file:\n%s",
763                                  "Failed to load torrent files:\n%s",
764                                  g_list_length( cbdata->errqueue ) ),
765                        joined );
766                g_list_foreach( cbdata->errqueue, (GFunc) g_free, NULL );
767                g_list_free( cbdata->errqueue );
768                cbdata->errqueue = NULL;
769                g_free( joined );
770            }
771            return;
772        case TR_CORE_ERR_SAVE_STATE:
773            errmsg( cbdata->wind, "%s", msg );
774            return;
775    }
776
777    g_assert_not_reached();
778}
779
780#if GTK_CHECK_VERSION(2,8,0)
781static void
782on_main_window_focus_in( GtkWidget      * widget UNUSED,
783                         GdkEventFocus  * event UNUSED,
784                         gpointer         gdata )
785{
786    struct cbdata * cbdata = gdata;
787    gtk_window_set_urgency_hint( GTK_WINDOW( cbdata->wind ), FALSE );
788}
789#endif
790
791static void
792coreprompt( TrCore                 * core,
793            GList                  * paths,
794            gpointer                 ctor,
795            gpointer                 gdata )
796{
797    struct cbdata * cbdata = gdata;
798    const int len = g_list_length( paths );
799    GtkWidget * w;
800
801    if( len > 1 )
802        w = promptfordir( cbdata->wind, core, paths, ctor );
803    else {
804        if( len == 1 )
805            tr_ctorSetMetainfoFromFile( ctor, paths->data );
806        w = makeaddwind( cbdata->wind, core, ctor );
807    }
808
809#if GTK_CHECK_VERSION(2,8,0)
810    if( cbdata->wind )
811        gtk_window_set_urgency_hint( GTK_WINDOW( cbdata->wind ), TRUE );
812    g_signal_connect( w, "focus-in-event",
813                      G_CALLBACK(on_main_window_focus_in),  cbdata );
814#endif
815}
816
817static void
818initializeFromPrefs( struct cbdata * cbdata )
819{
820    prefschanged( NULL, PREF_KEY_SYSTRAY, cbdata );
821}
822
823static void
824prefschanged( TrCore * core UNUSED, const char * key, gpointer data )
825{
826    struct cbdata * cbdata = data;
827    tr_handle     * tr     = tr_core_handle( cbdata->core );
828
829    if( !strcmp( key, PREF_KEY_ENCRYPTED_ONLY ) )
830    {
831        const gboolean crypto_only = pref_flag_get( key );
832        tr_setEncryptionMode( tr, crypto_only ? TR_ENCRYPTION_REQUIRED
833                                              : TR_ENCRYPTION_PREFERRED );
834    }
835    else if( !strcmp( key, PREF_KEY_PORT ) )
836    {
837        const int port = pref_int_get( key );
838        tr_setBindPort( tr, port );
839    }
840    else if( !strcmp( key, PREF_KEY_DL_LIMIT_ENABLED ) )
841    {
842        const gboolean b = pref_flag_get( key );
843        tr_setUseGlobalSpeedLimit( tr, TR_DOWN, b );
844    }
845    else if( !strcmp( key, PREF_KEY_DL_LIMIT ) )
846    {
847        const int limit = pref_int_get( key );
848        tr_setGlobalSpeedLimit( tr, TR_DOWN, limit );
849    }
850    else if( !strcmp( key, PREF_KEY_UL_LIMIT_ENABLED ) )
851    {
852        const gboolean b = pref_flag_get( key );
853        tr_setUseGlobalSpeedLimit( tr, TR_UP, b );
854    }
855    else if( !strcmp( key, PREF_KEY_UL_LIMIT ) )
856    {
857        const int limit = pref_int_get( key );
858        tr_setGlobalSpeedLimit( tr, TR_UP, limit );
859    }
860    else if( !strcmp( key, PREF_KEY_NAT ) )
861    {
862        const gboolean enabled = pref_flag_get( key );
863        tr_natTraversalEnable( tr, enabled );
864    }
865    else if( !strcmp( key, PREF_KEY_SYSTRAY ) )
866    {
867        if( pref_flag_get( key ) )
868        {
869            makeicon( cbdata );
870        }
871        else if( cbdata->icon )
872        {
873            g_object_unref( cbdata->icon );
874            cbdata->icon = NULL;
875        }
876    }
877    else if( !strcmp( key, PREF_KEY_PEX ) )
878    {
879        const gboolean enabled = pref_flag_get( key );
880        tr_setPexEnabled( tr_core_handle( cbdata->core ), enabled );
881    }
882}
883
884gboolean
885updatemodel(gpointer gdata) {
886  struct cbdata *data = gdata;
887
888  if( !data->closing && 0 < global_sigcount )
889  {
890      wannaquit( data );
891      return FALSE;
892  }
893
894  /* update the torrent data in the model */
895  tr_core_update( data->core );
896
897  /* update the main window's statusbar and toolbar buttons */
898  if( data->wind )
899      tr_window_update( data->wind );
900
901  /* update the message window */
902  msgwin_update();
903
904  return TRUE;
905}
906
907static void
908about ( GtkWindow * parent )
909{
910    const char *authors[] =
911    {
912        "Charles Kerr (Backend; GTK+)",
913        "Mitchell Livingston (Backend; OS X)",
914        "Eric Petit (Backend; OS X)",
915        "Josh Elsasser (Daemon; Backend; GTK+)",
916        "Bryan Varner (BeOS)", 
917        NULL
918    };
919
920    gtk_show_about_dialog( parent,
921        "name", g_get_application_name(),
922        "comments", _("A fast and easy BitTorrent client"),
923        "version", LONG_VERSION_STRING,
924        "website", "http://www.transmissionbt.com/",
925        "copyright",_("Copyright 2005-2008 The Transmission Project"),
926        "logo-icon-name", "transmission",
927#ifdef SHOW_LICENSE
928        "license", LICENSE,
929        "wrap-license", TRUE,
930#endif
931        "authors", authors,
932        "translator-credits", _("translator-credits"),
933        NULL );
934}
935
936static void
937startTorrentForeach (GtkTreeModel * model,
938                     GtkTreePath  * path UNUSED,
939                     GtkTreeIter  * iter,
940                     gpointer       data UNUSED)
941{
942    TrTorrent * tor = NULL;
943    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
944    tr_torrent_start( tor );
945    g_object_unref( G_OBJECT( tor ) );
946}
947
948static void
949stopTorrentForeach (GtkTreeModel * model,
950                    GtkTreePath  * path UNUSED,
951                    GtkTreeIter  * iter,
952                    gpointer       data UNUSED)
953{
954    TrTorrent * tor = NULL;
955    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
956    tr_torrent_stop( tor );
957    g_object_unref( G_OBJECT( tor ) );
958}
959
960static void
961updateTrackerForeach (GtkTreeModel * model,
962                      GtkTreePath  * path UNUSED,
963                      GtkTreeIter  * iter,
964                      gpointer       data UNUSED)
965{
966    TrTorrent * tor = NULL;
967    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
968    tr_manualUpdate( tr_torrent_handle( tor ) );
969    g_object_unref( G_OBJECT( tor ) );
970}
971
972static void
973detailsClosed( gpointer user_data, GObject * details )
974{
975    struct cbdata * data = user_data;
976    gpointer hashString = g_hash_table_lookup( data->details2tor, details );
977    g_hash_table_remove( data->details2tor, details );
978    g_hash_table_remove( data->tor2details, hashString );
979}
980
981static void
982showInfoForeach (GtkTreeModel * model,
983                 GtkTreePath  * path UNUSED,
984                 GtkTreeIter  * iter,
985                 gpointer       user_data )
986{
987    const char * hashString;
988    struct cbdata * data = user_data;
989    TrTorrent * tor = NULL;
990    GtkWidget * w;
991
992    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
993    hashString = tr_torrent_info(tor)->hashString;
994    w = g_hash_table_lookup( data->tor2details, hashString );
995    if( w != NULL )
996        gtk_window_present( GTK_WINDOW( w ) );
997    else {
998        w = torrent_inspector_new( GTK_WINDOW( data->wind ), tor );
999        gtk_widget_show( w );
1000        g_hash_table_insert( data->tor2details, (gpointer)hashString, w );
1001        g_hash_table_insert( data->details2tor, w, (gpointer)hashString );
1002        g_object_weak_ref( G_OBJECT( w ), detailsClosed, data );
1003    }
1004
1005    g_object_unref( G_OBJECT( tor ) );
1006}
1007
1008static void
1009recheckTorrentForeach (GtkTreeModel * model,
1010                       GtkTreePath  * path UNUSED,
1011                       GtkTreeIter  * iter,
1012                       gpointer       data UNUSED)
1013{
1014    TrTorrent * gtor = NULL;
1015    gtk_tree_model_get( model, iter, MC_TORRENT, &gtor, -1 );
1016    tr_torrentVerify( tr_torrent_handle( gtor ) );
1017    g_object_unref( G_OBJECT( gtor ) );
1018}
1019
1020static gboolean
1021msgwinclosed( void )
1022{
1023  action_toggle( "toggle-message-log", FALSE );
1024  return FALSE;
1025}
1026
1027static void
1028accumulateSelectedTorrents( GtkTreeModel * model,
1029                            GtkTreePath  * path UNUSED,
1030                            GtkTreeIter  * iter,
1031                            gpointer       gdata )
1032{
1033    GList ** data = ( GList** ) gdata;
1034    TrTorrent * tor = NULL;
1035    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
1036    *data = g_list_append( *data, tor );
1037}
1038
1039static void
1040closeSelected( struct cbdata * data, gboolean doDelete )
1041{
1042    GList * l = NULL;
1043    GtkTreeSelection * s = tr_window_get_selection( data->wind );
1044    gtk_tree_selection_selected_foreach( s, accumulateSelectedTorrents, &l );
1045    gtk_tree_selection_unselect_all( s );
1046    if( l ) {
1047        if( doDelete )
1048            confirmDelete( data->wind, data->core, l );
1049        else {
1050            GList * m;
1051            for( m=l; m; m=m->next )
1052                tr_core_remove_torrent( data->core, m->data, FALSE );
1053            g_list_foreach( l, (GFunc)g_object_unref, NULL );
1054            g_list_free( l );
1055        }
1056         
1057    }
1058}
1059
1060void
1061doAction ( const char * action_name, gpointer user_data )
1062{
1063    struct cbdata * data = user_data;
1064    gboolean changed = FALSE;
1065
1066    if ( !strcmp (action_name, "open-torrent-menu") || !strcmp( action_name, "open-torrent-toolbar" ))
1067    {
1068        tr_core_add_list( data->core, NULL,
1069                          tr_ctorNew( tr_core_handle( data->core ) ) );
1070    }
1071    else if (!strcmp (action_name, "show-stats"))
1072    {
1073        GtkWidget * dialog = stats_dialog_create( data->wind,
1074                                                  data->core );
1075        gtk_widget_show( dialog );
1076    }
1077    else if (!strcmp (action_name, "start-torrent"))
1078    {
1079        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1080        gtk_tree_selection_selected_foreach( s, startTorrentForeach, NULL );
1081        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1082    }
1083    else if (!strcmp (action_name, "pause-torrent"))
1084    {
1085        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1086        gtk_tree_selection_selected_foreach( s, stopTorrentForeach, NULL );
1087        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1088    }
1089    else if (!strcmp (action_name, "verify-torrent"))
1090    {
1091        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1092        gtk_tree_selection_selected_foreach( s, recheckTorrentForeach, NULL );
1093        changed |= gtk_tree_selection_count_selected_rows( s ) != 0;
1094    }
1095    else if (!strcmp (action_name, "show-torrent-details"))
1096    {
1097        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1098        gtk_tree_selection_selected_foreach( s, showInfoForeach, data );
1099    }
1100    else if (!strcmp( action_name, "update-tracker"))
1101    {
1102        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1103        gtk_tree_selection_selected_foreach( s, updateTrackerForeach, data->wind );
1104    }
1105    else if (!strcmp (action_name, "new-torrent"))
1106    {
1107        GtkWidget * w = make_meta_ui( GTK_WINDOW( data->wind ),
1108                                      tr_core_handle( data->core ) );
1109        gtk_widget_show_all( w );
1110    }
1111    else if( !strcmp( action_name, "close-torrent" ) )
1112    {
1113        closeSelected( data, FALSE );
1114    }
1115    else if( !strcmp( action_name, "delete-torrent" ) )
1116    {
1117        closeSelected( data, TRUE );
1118    }
1119    else if (!strcmp (action_name, "close"))
1120    {
1121        if( data->wind != NULL )
1122            winclose( NULL, NULL, data );
1123    }
1124    else if (!strcmp (action_name, "quit"))
1125    {
1126        askquit( data->core, data->wind, wannaquit, data );
1127    }
1128    else if (!strcmp (action_name, "select-all"))
1129    {
1130        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1131        gtk_tree_selection_select_all( s );
1132    }
1133    else if (!strcmp (action_name, "unselect-all"))
1134    {
1135        GtkTreeSelection * s = tr_window_get_selection(data->wind);
1136        gtk_tree_selection_unselect_all( s );
1137    }
1138    else if (!strcmp (action_name, "edit-preferences"))
1139    {
1140        if( NULL == data->prefs )
1141        {
1142            data->prefs = tr_prefs_dialog_new( G_OBJECT(data->core), data->wind );
1143            g_signal_connect( data->prefs, "destroy",
1144                             G_CALLBACK( gtk_widget_destroyed ), &data->prefs );
1145            gtk_widget_show( GTK_WIDGET( data->prefs ) );
1146        }
1147    }
1148    else if (!strcmp (action_name, "toggle-message-log"))
1149    {
1150        if( !data->msgwin )
1151        {
1152            GtkWidget * win = msgwin_create( data->core );
1153            g_signal_connect( win, "destroy", G_CALLBACK( msgwinclosed ), 
1154                             NULL );
1155            data->msgwin = win;
1156        }
1157        else
1158        {
1159            action_toggle("toggle-message-log", FALSE);
1160            gtk_widget_destroy( data->msgwin );
1161            data->msgwin = NULL;
1162        }
1163    }
1164    else if (!strcmp (action_name, "show-about-dialog"))
1165    {
1166        about( data->wind );
1167    }
1168    else if (!strcmp (action_name, "toggle-main-window"))
1169    {
1170        toggleMainWindow( data );
1171    }
1172    else g_error ("Unhandled action: %s", action_name );
1173
1174    if( changed )
1175        updatemodel( data );
1176}
Note: See TracBrowser for help on using the repository browser.