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: actions.c 13188 2012-02-03 15:51:36Z jordan $
11 */
12
13#include <string.h>
14
15#include <glib/gi18n.h>
16#include <gtk/gtk.h>
17
18#include <libtransmission/transmission.h>
19
20#include "actions.h"
21#include "conf.h"
22#include "tr-core.h"
23#include "tr-prefs.h"
24
25#include "icon-lock.h"
26#include "icon-logo-24.h"
27#include "icon-logo-48.h"
28#include "icon-ratio.h"
29#include "icon-turtle.h"
30#include "icon-utilities.h"
31
32#define UNUSED G_GNUC_UNUSED
33
34static TrCore * myCore = NULL;
35static GtkActionGroup * myGroup = NULL;
36
37static void
38action_cb( GtkAction * a, gpointer user_data )
39{
40    gtr_actions_handler( gtk_action_get_name( a ), user_data );
41}
42
43static GtkRadioActionEntry sort_radio_entries[] =
44{
45    { "sort-by-activity",  NULL, N_( "Sort by _Activity" ),  NULL, NULL, 0 },
46    { "sort-by-name",      NULL, N_( "Sort by _Name" ),      NULL, NULL, 1 },
47    { "sort-by-progress",  NULL, N_( "Sort by _Progress" ),  NULL, NULL, 2 },
48    { "sort-by-queue",     NULL, N_( "Sort by _Queue" ),     NULL, NULL, 3 },
49    { "sort-by-ratio",     NULL, N_( "Sort by Rati_o" ),     NULL, NULL, 4 },
50    { "sort-by-state",     NULL, N_( "Sort by Stat_e" ),     NULL, NULL, 5 },
51    { "sort-by-age",       NULL, N_( "Sort by A_ge" ),       NULL, NULL, 6 },
52    { "sort-by-time-left", NULL, N_( "Sort by Time _Left" ), NULL, NULL, 7 },
53    { "sort-by-size",      NULL, N_( "Sort by Si_ze" ),      NULL, NULL, 8 }
54};
55
56static void
57sort_changed_cb( GtkAction            * action UNUSED,
58                 GtkRadioAction *              current,
59                 gpointer user_data            UNUSED )
60{
61    const char * key = PREF_KEY_SORT_MODE;
62    const int    i = gtk_radio_action_get_current_value( current );
63    const char * val = sort_radio_entries[i].name;
64
65    gtr_core_set_pref( myCore, key, val );
66}
67
68static GtkToggleActionEntry show_toggle_entries[] =
69{
70    { "toggle-main-window", NULL, N_( "_Show Transmission" ), NULL, NULL, G_CALLBACK( action_cb ), TRUE },
71    { "toggle-message-log", NULL, N_( "Message _Log" ), NULL, NULL, G_CALLBACK( action_cb ), FALSE }
72};
73
74static void
75toggle_pref_cb( GtkToggleAction *  action,
76                gpointer user_data UNUSED )
77{
78    const char *   key = gtk_action_get_name( GTK_ACTION( action ) );
79    const gboolean val = gtk_toggle_action_get_active( action );
80
81    gtr_core_set_pref_bool( myCore, key, val );
82}
83
84static GtkToggleActionEntry  pref_toggle_entries[] =
85{
86    { "alt-speed-enabled", NULL, N_( "Enable Alternative Speed _Limits" ), NULL, NULL, G_CALLBACK( toggle_pref_cb ), FALSE },
87    { "compact-view",      NULL, N_( "_Compact View" ), "<alt>C", NULL, G_CALLBACK( toggle_pref_cb ), FALSE },
88    { "sort-reversed",     NULL, N_( "Re_verse Sort Order" ), NULL, NULL, G_CALLBACK( toggle_pref_cb ), FALSE },
89    { "show-filterbar",    NULL, N_( "_Filterbar" ), NULL, NULL, G_CALLBACK( toggle_pref_cb ), FALSE },
90    { "show-statusbar",    NULL, N_( "_Statusbar" ), NULL, NULL, G_CALLBACK( toggle_pref_cb ), FALSE },
91    { "show-toolbar",      NULL, N_( "_Toolbar" ), NULL, NULL, G_CALLBACK( toggle_pref_cb ), FALSE }
92};
93
94static GtkActionEntry entries[] =
95{
96    { "file-menu", NULL, N_( "_File" ), NULL, NULL, NULL  },
97    { "torrent-menu", NULL, N_( "_Torrent" ), NULL, NULL, NULL  },
98    { "view-menu", NULL, N_( "_View" ), NULL, NULL, NULL  },
99    { "sort-menu", NULL, N_( "_Sort Torrents By" ), NULL, NULL, NULL },
100    { "queue-menu", NULL, N_( "_Queue" ), NULL, NULL, NULL },
101    { "edit-menu", NULL, N_( "_Edit" ), NULL, NULL, NULL },
102    { "help-menu", NULL, N_( "_Help" ), NULL, NULL, NULL },
103    { "copy-magnet-link-to-clipboard",  GTK_STOCK_COPY, N_("Copy _Magnet Link to Clipboard" ), "", NULL,  G_CALLBACK( action_cb ) },
104    { "open-torrent-from-url",  GTK_STOCK_OPEN, N_("Open _URL���" ), "<control>U", N_( "Open URL���" ),  G_CALLBACK( action_cb ) },
105    { "open-torrent-toolbar",  GTK_STOCK_OPEN, NULL, NULL, N_( "Open a torrent" ),  G_CALLBACK( action_cb ) },
106    { "open-torrent-menu", GTK_STOCK_OPEN, NULL, NULL, N_( "Open a torrent" ), G_CALLBACK( action_cb ) },
107    { "torrent-start", GTK_STOCK_MEDIA_PLAY, N_( "_Start" ), "<control>S", N_( "Start torrent" ), G_CALLBACK( action_cb ) },
108    { "torrent-start-now", GTK_STOCK_MEDIA_PLAY, N_( "Start _Now" ), "<shift><control>S", N_( "Start torrent now" ), G_CALLBACK( action_cb ) },
109    { "show-stats", NULL, N_( "_Statistics" ), NULL, NULL, G_CALLBACK( action_cb ) },
110    { "donate", NULL, N_( "_Donate" ), NULL, NULL, G_CALLBACK( action_cb ) },
111    { "torrent-verify", NULL, N_( "_Verify Local Data" ), "<control>V", NULL, G_CALLBACK( action_cb ) },
112    { "torrent-stop", GTK_STOCK_MEDIA_PAUSE, N_( "_Pause" ), "<control>P", N_( "Pause torrent" ), G_CALLBACK( action_cb ) },
113    { "pause-all-torrents", GTK_STOCK_MEDIA_PAUSE, N_( "_Pause All" ), NULL, N_( "Pause all torrents" ), G_CALLBACK( action_cb ) },
114    { "start-all-torrents", GTK_STOCK_MEDIA_PLAY, N_( "_Start All" ), NULL, N_( "Start all torrents" ), G_CALLBACK( action_cb ) },
115    { "relocate-torrent", NULL, N_("Set _Location���" ), NULL, NULL, G_CALLBACK( action_cb ) },
116    { "remove-torrent", GTK_STOCK_REMOVE, NULL, "Delete", N_( "Remove torrent" ), G_CALLBACK( action_cb ) },
117    { "delete-torrent", GTK_STOCK_DELETE, N_( "_Delete Files and Remove" ), "<shift>Delete", NULL, G_CALLBACK( action_cb ) },
118    { "new-torrent", GTK_STOCK_NEW, N_( "_New���" ), NULL, N_( "Create a torrent" ), G_CALLBACK( action_cb ) },
119    { "quit", GTK_STOCK_QUIT, N_( "_Quit" ), NULL, NULL, G_CALLBACK( action_cb ) },
120    { "select-all", GTK_STOCK_SELECT_ALL, N_( "Select _All" ), "<control>A", NULL, G_CALLBACK( action_cb ) },
121    { "deselect-all", NULL, N_( "Dese_lect All" ), "<shift><control>A", NULL, G_CALLBACK( action_cb ) },
122    { "edit-preferences", GTK_STOCK_PREFERENCES, NULL, NULL, NULL, G_CALLBACK( action_cb ) },
123    { "show-torrent-properties", GTK_STOCK_PROPERTIES, NULL, "<alt>Return", N_( "Torrent properties" ), G_CALLBACK( action_cb ) },
124    { "open-torrent-folder",  GTK_STOCK_OPEN, N_( "Open Fold_er" ), "<control>E", NULL, G_CALLBACK( action_cb ) },
125    { "show-about-dialog", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK( action_cb ) },
126    { "help", GTK_STOCK_HELP, N_( "_Contents" ), "F1", NULL, G_CALLBACK( action_cb ) },
127    { "torrent-reannounce", GTK_STOCK_NETWORK, N_( "Ask Tracker for _More Peers" ), NULL, NULL, G_CALLBACK( action_cb ) },
128    { "queue-move-top", GTK_STOCK_GOTO_TOP, N_( "Move to _Top" ), NULL, NULL, G_CALLBACK( action_cb ) },
129    { "queue-move-up", GTK_STOCK_GO_UP, N_( "Move _Up" ), NULL, NULL, G_CALLBACK( action_cb ) },
130    { "queue-move-down", GTK_STOCK_GO_DOWN, N_( "Move _Down" ), NULL, NULL, G_CALLBACK( action_cb ) },
131    { "queue-move-bottom", GTK_STOCK_GOTO_BOTTOM, N_( "Move to _Bottom" ), NULL, NULL, G_CALLBACK( action_cb ) },
132    { "present-main-window", NULL, N_( "Present Main Window" ), NULL, NULL, G_CALLBACK( action_cb ) }
133};
134
135typedef struct
136{
137    const guint8*   raw;
138    const char *    name;
139}
140BuiltinIconInfo;
141
142static const BuiltinIconInfo my_fallback_icons[] =
143{
144    { tr_icon_logo_48,  WINDOW_ICON          },
145    { tr_icon_logo_24,  TRAY_ICON            },
146    { tr_icon_logo_48,  NOTIFICATION_ICON    },
147    { tr_icon_lock,     "transmission-lock"  },
148    { utilities_icon,   "utilities"          },
149    { blue_turtle,      "alt-speed-on"       },
150    { grey_turtle,      "alt-speed-off"      },
151    { ratio_icon,       "ratio"              }
152};
153
154static void
155register_my_icons( void )
156{
157    int              i;
158    const int        n = G_N_ELEMENTS( my_fallback_icons );
159    GtkIconFactory * factory = gtk_icon_factory_new( );
160    GtkIconTheme *   theme = gtk_icon_theme_get_default( );
161
162    gtk_icon_factory_add_default( factory );
163
164    for( i = 0; i < n; ++i )
165    {
166        const char * name = my_fallback_icons[i].name;
167
168        if( !gtk_icon_theme_has_icon( theme, name ) )
169        {
170            int          width;
171            GdkPixbuf *  p;
172            GtkIconSet * icon_set;
173
174            p =
175                gdk_pixbuf_new_from_inline( -1, my_fallback_icons[i].raw,
176                                            FALSE,
177                                            NULL );
178            width = gdk_pixbuf_get_width( p );
179            icon_set = gtk_icon_set_new_from_pixbuf( p );
180            gtk_icon_theme_add_builtin_icon( name, width, p );
181            gtk_icon_factory_add( factory, name, icon_set );
182
183            g_object_unref( p );
184            gtk_icon_set_unref( icon_set );
185        }
186    }
187
188    g_object_unref ( G_OBJECT ( factory ) );
189}
190
191static GtkUIManager * myUIManager = NULL;
192
193void
194gtr_actions_set_core( TrCore * core )
195{
196    myCore = core;
197}
198
199void
200gtr_actions_init( GtkUIManager * ui_manager, gpointer callback_user_data )
201{
202    int              i, n;
203    int              active;
204    const char *     match;
205    const int        n_entries = G_N_ELEMENTS( entries );
206    GtkActionGroup * action_group;
207
208    myUIManager = ui_manager;
209
210    register_my_icons( );
211
212    action_group = myGroup = gtk_action_group_new( "Actions" );
213    gtk_action_group_set_translation_domain( action_group, NULL );
214
215
216    match = gtr_pref_string_get( PREF_KEY_SORT_MODE );
217    for( i = 0, n = G_N_ELEMENTS( sort_radio_entries ), active = -1;
218         active == -1 && i < n; ++i )
219        if( !strcmp( sort_radio_entries[i].name, match ) )
220            active = i;
221
222    gtk_action_group_add_radio_actions( action_group,
223                                        sort_radio_entries,
224                                        G_N_ELEMENTS( sort_radio_entries ),
225                                        active,
226                                        G_CALLBACK( sort_changed_cb ),
227                                        NULL );
228
229    gtk_action_group_add_toggle_actions( action_group,
230                                         show_toggle_entries,
231                                         G_N_ELEMENTS( show_toggle_entries ),
232                                         callback_user_data );
233
234    for( i = 0, n = G_N_ELEMENTS( pref_toggle_entries ); i < n; ++i )
235        pref_toggle_entries[i].is_active =
236            gtr_pref_flag_get( pref_toggle_entries[i].name );
237
238    gtk_action_group_add_toggle_actions( action_group,
239                                         pref_toggle_entries,
240                                         G_N_ELEMENTS( pref_toggle_entries ),
241                                         callback_user_data );
242
243    gtk_action_group_add_actions( action_group,
244                                  entries, n_entries,
245                                  callback_user_data );
246
247    gtk_ui_manager_insert_action_group( ui_manager, action_group, 0 );
248    g_object_unref ( G_OBJECT( action_group ) );
249}
250
251/****
252*****
253****/
254
255static GHashTable * key_to_action = NULL;
256
257static void
258ensure_action_map_loaded( GtkUIManager * uim )
259{
260    GList * l;
261
262    if( key_to_action != NULL )
263        return;
264
265    key_to_action =
266        g_hash_table_new_full( g_str_hash, g_str_equal, g_free, NULL );
267
268    for( l = gtk_ui_manager_get_action_groups( uim ); l != NULL;
269         l = l->next )
270    {
271        GtkActionGroup * action_group = GTK_ACTION_GROUP( l->data );
272        GList *          ait, *actions = gtk_action_group_list_actions(
273            action_group );
274        for( ait = actions; ait != NULL; ait = ait->next )
275        {
276            GtkAction *  action = GTK_ACTION( ait->data );
277            const char * name = gtk_action_get_name( action );
278            g_hash_table_insert( key_to_action, g_strdup( name ), action );
279        }
280        g_list_free( actions );
281    }
282}
283
284static GtkAction*
285get_action( const char* name )
286{
287    ensure_action_map_loaded( myUIManager );
288    return ( GtkAction* ) g_hash_table_lookup( key_to_action, name );
289}
290
291void
292gtr_action_activate( const char * name )
293{
294    GtkAction * action = get_action( name );
295
296    g_assert( action != NULL );
297    gtk_action_activate( action );
298}
299
300void
301gtr_action_set_sensitive( const char * name, gboolean b )
302{
303    GtkAction * action = get_action( name );
304
305    g_assert( action != NULL );
306    g_object_set( action, "sensitive", b, NULL );
307}
308
309void
310gtr_action_set_important( const char * name, gboolean b )
311{
312    GtkAction * action = get_action( name );
313
314    g_assert( action != NULL );
315    g_object_set( action, "is-important", b, NULL );
316}
317
318void
319gtr_action_set_toggled( const char * name, gboolean b )
320{
321    GtkAction * action = get_action( name );
322
323    gtk_toggle_action_set_active( GTK_TOGGLE_ACTION( action ), b );
324}
325
326GtkWidget*
327gtr_action_get_widget( const char * path )
328{
329    return gtk_ui_manager_get_widget( myUIManager, path );
330}
331
332