source: trunk/gtk/tr_window.c @ 1587

Last change on this file since 1587 was 1587, checked in by joshe, 15 years ago

Set roles for non-dialog windows to help WMs out a bit.

  • Property svn:keywords set to Date Rev Author Id
File size: 25.8 KB
Line 
1/******************************************************************************
2 * $Id: tr_window.c 1587 2007-03-24 10:20:00Z joshe $
3 *
4 * Copyright (c) 2005-2007 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 <string.h>
26
27#include <gtk/gtk.h>
28#include <glib/gi18n.h>
29
30#include "transmission.h"
31
32#include "tr_cell_renderer_progress.h"
33#include "tr_torrent.h"
34#include "tr_window.h"
35#include "util.h"
36
37#define ITEM_ACTION             "tr-window-item-action"
38
39enum
40{
41    PROP_MODEL = 1,
42    PROP_ELLIPSIZE,
43    PROP_SELECTION,
44    PROP_DOUBLECLICK,
45    PROP_DRAG,
46};
47
48static void
49tr_window_init( GTypeInstance * instance, gpointer g_class );
50static void
51tr_window_set_property( GObject * object, guint property_id,
52                        const GValue * value, GParamSpec * pspec );
53static void
54tr_window_get_property( GObject * object, guint property_id,
55                        GValue * value, GParamSpec * pspec);
56static void
57tr_window_class_init( gpointer g_class, gpointer g_class_data );
58static void
59tr_window_dispose( GObject * obj );
60static GtkTreeView *
61makeview( TrWindow * self );
62static void
63stylekludge( GObject * obj, GParamSpec * spec, gpointer data );
64static void
65fixbuttons( GtkTreeSelection *sel, TrWindow * self );
66static void
67formatname( GtkTreeViewColumn * col, GtkCellRenderer * rend,
68            GtkTreeModel * model, GtkTreeIter * iter, gpointer data );
69static void
70formatprog( GtkTreeViewColumn * col, GtkCellRenderer * rend,
71            GtkTreeModel * model, GtkTreeIter * iter, gpointer data );
72static gboolean
73listclick( GtkWidget * view, GdkEventButton * event, gpointer data );
74static gboolean
75listpopup( GtkWidget * view SHUTUP, gpointer data );
76static void
77popupmenu( TrWindow * self, GdkEventButton * event );
78static void
79itemclick( GObject * obj, gpointer data );
80static void
81doubleclick( GtkWidget * view, GtkTreePath * path,
82             GtkTreeViewColumn * col SHUTUP, gpointer data );
83static void
84emitaction( TrWindow * self, int id );
85static void
86orstatus( GtkTreeModel * model, GtkTreePath * path SHUTUP, GtkTreeIter * iter,
87          gpointer data );
88static void
89istorsel( GtkTreeModel * model, GtkTreePath * path SHUTUP, GtkTreeIter * iter,
90          gpointer data );
91
92GType
93tr_window_get_type( void )
94{
95    static GType type = 0;
96
97    if( 0 == type )
98    {
99        static const GTypeInfo info =
100        {
101            sizeof( TrWindowClass ),
102            NULL,                       /* base_init */
103            NULL,                       /* base_finalize */
104            tr_window_class_init,       /* class_init */
105            NULL,                       /* class_finalize */
106            NULL,                       /* class_data */
107            sizeof( TrWindow ),
108            0,                          /* n_preallocs */
109            tr_window_init,             /* instance_init */
110            NULL,
111        };
112        type = g_type_register_static( GTK_TYPE_WINDOW, "TrWindow", &info, 0 );
113    }
114
115    return type;
116}
117
118static void
119tr_window_class_init( gpointer g_class, gpointer g_class_data SHUTUP )
120{
121    GObjectClass  * gobject_class;
122    TrWindowClass * trwindow_class;
123    GParamSpec   * pspec;
124
125    gobject_class = G_OBJECT_CLASS( g_class );
126    gobject_class->set_property = tr_window_set_property;
127    gobject_class->get_property = tr_window_get_property;
128    gobject_class->dispose      = tr_window_dispose;
129
130    pspec = g_param_spec_object( "model", _("Model"),
131                                 _("The GtkTreeModel for the list view."),
132                                 GTK_TYPE_TREE_MODEL, G_PARAM_READWRITE );
133    g_object_class_install_property( gobject_class, PROP_MODEL, pspec );
134
135    pspec = g_param_spec_boolean( "ellipsize", _("Ellipsize"),
136                                 _("Ellipsize torrent names."),
137                                 FALSE, G_PARAM_READWRITE );
138    g_object_class_install_property( gobject_class, PROP_ELLIPSIZE, pspec );
139
140    pspec = g_param_spec_object( "selection", _("Selection"),
141                                 _("The GtkTreeSelection for the list view."),
142                                 GTK_TYPE_TREE_SELECTION, G_PARAM_READABLE );
143    g_object_class_install_property( gobject_class, PROP_SELECTION, pspec );
144
145    pspec = g_param_spec_int( "double-click-action", _("Double-click action"),
146                              _("The action id to signal on a double click."),
147                              G_MININT, G_MAXINT, -1, G_PARAM_READWRITE );
148    g_object_class_install_property( gobject_class, PROP_DOUBLECLICK, pspec );
149
150    pspec = g_param_spec_object( "drag-widget", _("Drag widget"),
151                                 _("The GtkWidget used for drag-and-drop."),
152                                 GTK_TYPE_WIDGET, G_PARAM_READABLE );
153    g_object_class_install_property( gobject_class, PROP_DRAG, pspec );
154
155    trwindow_class = TR_WINDOW_CLASS( g_class );
156    trwindow_class->actionsig =
157        g_signal_new( "action", G_TYPE_FROM_CLASS( g_class ),
158                       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
159                       g_cclosure_marshal_VOID__INT,
160                       G_TYPE_NONE, 1, G_TYPE_INT );
161}
162
163static void
164tr_window_init( GTypeInstance * instance, gpointer g_class SHUTUP )
165{
166    TrWindow * self = ( TrWindow * )instance;
167    GtkWidget * vbox, * scroll, * status, * tools, * menu, * file, * item;
168
169    vbox   = gtk_vbox_new( FALSE, 0 );
170    scroll = gtk_scrolled_window_new( NULL, NULL );
171    status = gtk_statusbar_new();
172    tools  = gtk_toolbar_new();
173    menu   = gtk_menu_bar_new();
174    file   = gtk_menu_new();
175    item   = gtk_menu_item_new_with_mnemonic( _("_File") );
176
177    self->scroll          = GTK_SCROLLED_WINDOW( scroll );
178    self->view            = makeview( self );
179    self->status          = GTK_STATUSBAR( status );
180    self->toolbar         = GTK_TOOLBAR( tools );
181    self->menu            = GTK_MENU_SHELL( file );
182    /* this should have been set by makeview() */
183    g_assert( NULL != self->namerend );
184    self->doubleclick     = -1;
185    self->actions         = NULL;
186    self->accel           = gtk_accel_group_new();
187    self->stupidpopuphack = NULL;
188    self->disposed        = FALSE;
189
190    gtk_window_add_accel_group( GTK_WINDOW( self ), self->accel );
191    gtk_menu_set_accel_group( GTK_MENU( self->menu ), self->accel );
192
193    gtk_menu_item_set_submenu( GTK_MENU_ITEM( item ), file );
194    gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
195    gtk_box_pack_start( GTK_BOX( vbox ), menu, FALSE, FALSE, 0 );
196
197    gtk_toolbar_set_tooltips( self->toolbar, TRUE );
198    gtk_toolbar_set_show_arrow( self->toolbar, FALSE );
199    gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( self->toolbar ),
200                        FALSE, FALSE, 0 );
201
202    gtk_scrolled_window_set_policy( self->scroll,
203                                    GTK_POLICY_NEVER, GTK_POLICY_ALWAYS );
204    gtk_container_add( GTK_CONTAINER( scroll ), GTK_WIDGET( self->view ) );
205    gtk_box_pack_start( GTK_BOX( vbox ), scroll, TRUE, TRUE, 0 );
206
207    gtk_statusbar_push( self->status, 0, "" );
208    gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( self->status ),
209                        FALSE, FALSE, 0 );
210
211    gtk_container_set_focus_child( GTK_CONTAINER( vbox ), scroll );
212    gtk_widget_show_all( vbox );
213    gtk_container_add( GTK_CONTAINER( self ), vbox );
214    gtk_window_set_title( GTK_WINDOW( self ), g_get_application_name());
215    gtk_window_set_role( GTK_WINDOW( self ), "tr-main" );
216}
217
218static void
219tr_window_set_property( GObject * object, guint property_id,
220                        const GValue * value SHUTUP, GParamSpec * pspec)
221{
222    TrWindow         * self = ( TrWindow * )object;
223    PangoEllipsizeMode elip;
224
225    if( self->disposed )
226    {
227        return;
228    }
229
230    switch( property_id )
231    {
232        case PROP_MODEL:
233            gtk_tree_view_set_model( self->view, g_value_get_object( value ) );
234            break;
235        case PROP_ELLIPSIZE:
236            g_assert( NULL != self->namerend );
237            elip = ( g_value_get_boolean( value ) ?
238                     PANGO_ELLIPSIZE_END : PANGO_ELLIPSIZE_NONE );
239            g_object_set( self->namerend, "ellipsize", elip, NULL );
240            break;
241        case PROP_DOUBLECLICK:
242            self->doubleclick = g_value_get_int( value );
243            break;
244        default:
245            G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
246            break;
247    }
248}
249
250static void
251tr_window_get_property( GObject * object, guint property_id,
252                        GValue * value SHUTUP, GParamSpec * pspec )
253{
254    TrWindow         * self = ( TrWindow * )object;
255    PangoEllipsizeMode elip;
256
257    if( self->disposed )
258    {
259        return;
260    }
261
262    switch( property_id )
263    {
264        case PROP_MODEL:
265            g_value_set_object( value, gtk_tree_view_get_model( self->view ) );
266            break;
267        case PROP_ELLIPSIZE:
268            g_assert( NULL != self->namerend );
269            g_object_get( self->namerend, "ellipsize", &elip, NULL);
270            g_value_set_boolean( value, ( PANGO_ELLIPSIZE_NONE != elip ) );
271            break;
272        case PROP_SELECTION:
273            g_value_set_object( value,
274                                gtk_tree_view_get_selection( self->view ) );
275            break;
276        case PROP_DOUBLECLICK:
277            g_value_set_int( value, self->doubleclick );
278            break;
279        case PROP_DRAG:
280            g_value_set_object( value, self->view );
281            break;
282        default:
283            G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, pspec );
284            break;
285    }
286}
287
288static void
289tr_window_dispose( GObject * obj )
290{
291    TrWindow     * self = ( TrWindow * )obj;
292    GObjectClass * parent;
293
294    if( self->disposed )
295    {
296        return;
297    }
298    self->disposed = TRUE;
299
300    g_list_foreach( self->actions, ( GFunc )action_free, NULL );
301    g_list_free( self->actions );
302    g_object_unref( self->accel );
303    if( NULL != self->stupidpopuphack )
304    {
305        gtk_widget_destroy( self->stupidpopuphack );
306    }
307    self->stupidpopuphack = NULL;
308
309    /* Chain up to the parent class */
310    parent = g_type_class_peek( g_type_parent( TR_WINDOW_TYPE ) );
311    parent->dispose( obj );
312}
313
314GtkWidget *
315tr_window_new( void )
316{
317    return g_object_new( TR_WINDOW_TYPE, NULL );
318}
319
320void
321tr_window_action_add( TrWindow * self, int id, int flags, const char * name,
322                      const char * icon, const char * description, guint key )
323{
324    struct action * act;
325    GtkWidget     * sep;
326
327    TR_IS_WINDOW( self );
328    if( self->disposed )
329    {
330        return;
331    }
332
333    act = action_new( id, flags, name, icon );
334
335    if( ACTF_TOOL & flags )
336    {
337        act->tool = action_maketool( act, ITEM_ACTION,
338                                     G_CALLBACK( itemclick ), self );
339        gtk_tool_item_set_tooltip( GTK_TOOL_ITEM( act->tool ),
340                                   self->toolbar->tooltips, description, "" );
341        gtk_toolbar_insert( self->toolbar, GTK_TOOL_ITEM( act->tool ), -1 );
342    }
343
344    if( ACTF_MENU & flags )
345    {
346        act->menu = action_makemenu( act, ITEM_ACTION, self->accel,
347                                     "<transmission-mainwind>/file", key,
348                                     G_CALLBACK( itemclick ), self );
349        gtk_menu_shell_append( self->menu, act->menu );
350    }
351
352    if( ACTF_SEPARATOR & flags )
353    {
354        sep = gtk_separator_menu_item_new();
355        gtk_widget_show( sep );
356        gtk_menu_shell_append( self->menu, sep );
357    }
358
359    self->actions = g_list_append( self->actions, act );
360}
361
362void
363tr_window_update( TrWindow * self, float downspeed, float upspeed )
364{
365    char * downstr, * upstr, * str;
366
367    TR_IS_WINDOW( self );
368    if( self->disposed )
369    {
370        return;
371    }
372
373    /* update the status bar */
374    downstr = readablesize( downspeed * 1024.0 );
375    upstr   = readablesize( upspeed * 1024.0 );
376    str     = g_strdup_printf( _("     Total DL: %s/s     Total UL: %s/s"),
377                               downstr, upstr );
378    g_free( downstr );
379    g_free( upstr );
380    gtk_statusbar_pop( self->status, 0 );
381    gtk_statusbar_push( self->status, 0, str );
382    g_free( str );
383
384    /* the selection's status may have changed so update the buttons */
385    fixbuttons( NULL, self );
386}
387
388static void
389setelip( void * arg )
390{
391    g_object_set( arg, "ellipsize", TRUE, NULL );
392}
393
394void
395tr_window_size_hack( TrWindow * self )
396{
397    TR_IS_WINDOW( self );
398
399    windowsizehack( GTK_WIDGET( self ), GTK_WIDGET( self->scroll ),
400                    GTK_WIDGET( self->view ), setelip, self );
401}
402
403static GtkTreeView *
404makeview( TrWindow * self )
405{
406    GtkWidget         * view;
407    GtkTreeViewColumn * col;
408    GtkTreeSelection  * sel;
409    GtkCellRenderer   * namerend, * progrend;
410    char              * str;
411
412    TR_IS_WINDOW( self );
413
414    view     = gtk_tree_view_new();
415    namerend = gtk_cell_renderer_text_new();
416    self->namerend = G_OBJECT( namerend );
417    /* note that this renderer is set to ellipsize, just not here */
418    col = gtk_tree_view_column_new_with_attributes( _("Name"), namerend,
419                                                    NULL );
420    gtk_tree_view_column_set_cell_data_func( col, namerend, formatname,
421                                             NULL, NULL );
422    gtk_tree_view_column_set_expand( col, TRUE );
423    gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_AUTOSIZE );
424    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
425
426    progrend = tr_cell_renderer_progress_new();
427    /* this string is only used to determine the size of the progress bar */
428    str = g_markup_printf_escaped( "<big>%s</big>", _("  fnord    fnord  ") );
429    g_object_set( progrend, "bar-sizing", str, NULL );
430    g_free(str);
431    col = gtk_tree_view_column_new_with_attributes( _("Progress"), progrend,
432                                                    NULL);
433    gtk_tree_view_column_set_cell_data_func( col, progrend, formatprog,
434                                             NULL, NULL );
435    gtk_tree_view_column_set_sizing( col, GTK_TREE_VIEW_COLUMN_AUTOSIZE );
436    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
437
438    /* XXX this shouldn't be necessary */
439    g_signal_connect( view, "notify::style",
440                      G_CALLBACK( stylekludge ), progrend );
441
442    gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( view ), TRUE );
443    sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
444    gtk_tree_selection_set_mode( GTK_TREE_SELECTION( sel ),
445                                 GTK_SELECTION_MULTIPLE );
446
447    g_signal_connect( G_OBJECT( sel ), "changed",
448                      G_CALLBACK( fixbuttons ), self );
449    g_signal_connect( G_OBJECT( view ), "button-press-event",
450                      G_CALLBACK( listclick ), self );
451    g_signal_connect( G_OBJECT( view ), "popup-menu",
452                      G_CALLBACK( listpopup ), self );
453    g_signal_connect( G_OBJECT( view ), "row-activated",
454                      G_CALLBACK( doubleclick ), self );
455
456    return GTK_TREE_VIEW( view );
457}
458
459/* kludge to have the progress bars notice theme changes */
460static void
461stylekludge( GObject * obj, GParamSpec * spec, gpointer data )
462{
463    if( 0 == strcmp( "style", spec->name ) )
464    {
465        tr_cell_renderer_progress_reset_style(
466            TR_CELL_RENDERER_PROGRESS( data ) );
467        gtk_widget_queue_draw( GTK_WIDGET( obj ) );
468    }
469}
470
471/* disable buttons and menuitems the user shouldn't be able to click on */
472static void
473fixbuttons( GtkTreeSelection *sel, TrWindow * self ) {
474    gboolean        selected, avail;
475    GList         * ii;
476    int             status;
477    struct action * act;
478
479    TR_IS_WINDOW( self );
480    if( self->disposed )
481    {
482        return;
483    }
484
485    if( NULL == sel )
486    {
487        sel = gtk_tree_view_get_selection( self->view );
488    }
489    status = 0;
490    gtk_tree_selection_selected_foreach( sel, orstatus, &status );
491    selected = ( 0 < gtk_tree_selection_count_selected_rows( sel ) );
492
493    for( ii = g_list_first( self->actions ); NULL != ii; ii = ii->next )
494    {
495        act = ii->data;
496        if( ACTF_ALWAYS & act->flags )
497        {
498            continue;
499        }
500        avail = ACT_ISAVAIL( act->flags, status );
501        if( ACTF_TOOL & act->flags )
502        {
503            g_assert( NULL != act->tool );
504            gtk_widget_set_sensitive( act->tool, selected && avail );
505        }
506        if( ACTF_MENU & act->flags )
507        {
508            g_assert( NULL != act->menu );
509            gtk_widget_set_sensitive( act->menu, selected && avail );
510        }
511    }
512}
513
514static void
515formatname( GtkTreeViewColumn * col SHUTUP, GtkCellRenderer * rend,
516            GtkTreeModel * model, GtkTreeIter * iter, gpointer data SHUTUP )
517{
518    char  * name, * mb, * terr, * str, * top, * bottom, * timestr;
519    guint64 size;
520    gfloat  prog;
521    int     status, err, eta, tpeers, upeers, dpeers;
522
523    gtk_tree_model_get( model, iter, MC_NAME, &name, MC_STAT, &status,
524                        MC_ERR, &err, MC_SIZE, &size, MC_PROG, &prog,
525                        MC_ETA, &eta, MC_PEERS, &tpeers, MC_UPEERS, &upeers,
526                        MC_DPEERS, &dpeers, -1 );
527
528    tpeers = MAX( tpeers, 0 );
529    upeers = MAX( upeers, 0 );
530    dpeers = MAX( dpeers, 0 );
531    mb = readablesize(size);
532    prog *= 100;
533
534    if( TR_STATUS_CHECK & status )
535    {
536        top = g_strdup_printf( _("Checking existing files (%.1f%%)"), prog );
537    }
538    else if( TR_STATUS_DOWNLOAD & status )
539    {
540        if( 0 > eta )
541        {
542            top = g_strdup_printf( _("Stalled (%.1f%%)"), prog );
543        }
544        else
545        {
546            timestr = readabletime(eta);
547            top = g_strdup_printf( _("Finishing in %s (%.1f%%)"),
548                                   timestr, prog );
549            g_free(timestr);
550        }
551    }
552    else if(TR_STATUS_SEED & status)
553    {
554        top = g_strdup_printf(
555            ngettext( "Seeding, uploading to %d of %d peer",
556                      "Seeding, uploading to %d of %d peers", tpeers ),
557            dpeers, tpeers );
558    }
559    else if( TR_STATUS_STOPPING & status )
560    {
561        top = g_strdup( _("Stopping...") );
562    }
563    else if( TR_STATUS_PAUSE & status )
564    {
565        top = g_strdup_printf( _("Stopped (%.1f%%)"), prog );
566    }
567    else
568    {
569        top = g_strdup( "" );
570        g_assert_not_reached();
571    }
572
573    if( TR_OK != err )
574    {
575        gtk_tree_model_get( model, iter, MC_TERR, &terr, -1 );
576        bottom = g_strconcat( _("Error: "), terr, NULL );
577        g_free( terr );
578    }
579    else if( TR_STATUS_DOWNLOAD & status )
580    {
581        bottom = g_strdup_printf( ngettext( "Downloading from %i of %i peer",
582                                            "Downloading from %i of %i peers",
583                                            tpeers ), upeers, tpeers );
584    }
585    else
586    {
587        bottom = NULL;
588    }
589
590    str = g_markup_printf_escaped( "<big>%s (%s)</big>\n<small>%s\n%s</small>",
591                                   name, mb, top,
592                                   ( NULL == bottom ? "" : bottom ) );
593    g_object_set( rend, "markup", str, NULL );
594    g_free( name );
595    g_free( mb );
596    g_free( str );
597    g_free( top );
598    g_free( bottom );
599}
600
601static void
602formatprog( GtkTreeViewColumn * col SHUTUP, GtkCellRenderer * rend,
603            GtkTreeModel * model, GtkTreeIter * iter, gpointer data SHUTUP )
604{
605    char  * dlstr, * ulstr, * str, * marked;
606    gfloat  prog, dl, ul;
607    guint64 down, up;
608
609    gtk_tree_model_get( model, iter, MC_PROG, &prog, MC_DRATE, &dl,
610                        MC_URATE, &ul, MC_DOWN, &down, MC_UP, &up, -1 );
611    prog = MAX( prog, 0.0 );
612    prog = MIN( prog, 1.0 );
613
614    ulstr = readablesize( ul * 1024.0 );
615    if( 1.0 == prog )
616    {
617        dlstr = ratiostr( down, up );
618        str = g_strdup_printf( _("Ratio: %s\nUL: %s/s"), dlstr, ulstr );
619    }
620    else
621    {
622        dlstr = readablesize( dl * 1024.0 );
623        str = g_strdup_printf( _("DL: %s/s\nUL: %s/s"), dlstr, ulstr );
624    }
625    marked = g_markup_printf_escaped( "<small>%s</small>", str );
626    g_object_set( rend, "markup", str, "progress", prog, NULL );
627    g_free( dlstr );
628    g_free( ulstr );
629    g_free( str );
630    g_free( marked );
631}
632
633/* show a popup menu for a right-click on the list */
634static gboolean
635listclick( GtkWidget * view, GdkEventButton * event, gpointer data )
636{
637    TrWindow         * self;
638    GtkTreeSelection * sel;
639    GtkTreePath      * path;
640    GtkTreeModel     * model;
641    GtkTreeIter        iter;
642    int                status;
643    TrTorrent        * tor, * issel;
644
645    if( GDK_BUTTON_PRESS != event->type || 3 != event->button )
646    {
647        return FALSE;
648    }
649
650    TR_IS_WINDOW( data );
651    self = TR_WINDOW( data );
652    if( self->disposed )
653    {
654        return FALSE;
655    }
656
657    sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
658    model = gtk_tree_view_get_model( GTK_TREE_VIEW( view ) );
659
660    /* find what row, if any, the user clicked on */
661    if( gtk_tree_view_get_path_at_pos( GTK_TREE_VIEW( view ),
662                                        event->x, event->y, &path,
663                                        NULL, NULL, NULL ) )
664    {
665        if( gtk_tree_model_get_iter( model, &iter, path ) )
666        {
667            /* get torrent and status for the right-clicked row */
668            gtk_tree_model_get( model, &iter, MC_TORRENT, &tor,
669                                MC_STAT, &status, -1 );
670            issel = tor;
671            gtk_tree_selection_selected_foreach( sel, istorsel, &issel );
672            g_object_unref( tor );
673            /* if the clicked row isn't selected, select only it */
674            if( NULL != issel )
675            {
676                gtk_tree_selection_unselect_all( sel );
677                gtk_tree_selection_select_iter( sel, &iter );
678            }
679        }
680        gtk_tree_path_free( path );
681    }
682    else
683    {
684        gtk_tree_selection_unselect_all( sel );
685    }
686
687    popupmenu( self, event );
688
689    return TRUE;
690}
691
692static gboolean
693listpopup( GtkWidget * view SHUTUP, gpointer data )
694{
695    popupmenu( TR_WINDOW( data ), NULL );
696    return TRUE;
697}
698
699static void
700popupmenu( TrWindow * self, GdkEventButton * event )
701{
702    GtkTreeSelection * sel;
703    int                count, status;
704    GtkWidget        * menu, * item;
705    GList            * ii;
706    struct action    * act;
707
708    TR_IS_WINDOW( self );
709    if( self->disposed )
710    {
711        return;
712    }
713
714    sel   = gtk_tree_view_get_selection( self->view );
715    count = gtk_tree_selection_count_selected_rows( sel );
716    menu  = gtk_menu_new();
717
718    if( NULL != self->stupidpopuphack )
719    {
720        gtk_widget_destroy( self->stupidpopuphack );
721    }
722    self->stupidpopuphack = menu;
723
724    status = 0;
725    gtk_tree_selection_selected_foreach( sel, orstatus, &status );
726
727    for( ii = g_list_first( self->actions ); NULL != ii; ii = ii->next )
728    {
729        act = ii->data;
730        if( ACTF_SEPARATOR & act->flags )
731        {
732            item = gtk_separator_menu_item_new();
733            gtk_widget_show( item );
734            gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
735        }
736        else if( ACTF_MENU & act->flags && ACT_ISAVAIL( act->flags, status ) )
737        {
738            item = action_makemenu( act, ITEM_ACTION, NULL, NULL, 0,
739                                    G_CALLBACK( itemclick ), self );
740            gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
741        }
742    }
743
744    gtk_widget_show( menu );
745
746    gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL,
747                    ( NULL == event ? 0 : event->button ),
748                    gdk_event_get_time( (GdkEvent*)event ) );
749}
750
751static void
752itemclick( GObject * obj, gpointer data )
753{
754    TrWindow      * self;
755    struct action * act;
756
757    TR_IS_WINDOW( data );
758    self = TR_WINDOW( data );
759    act = g_object_get_data( obj, ITEM_ACTION );
760
761    emitaction( self, act->id );
762}
763
764static void
765doubleclick( GtkWidget * view SHUTUP, GtkTreePath * path,
766             GtkTreeViewColumn * col SHUTUP, gpointer data )
767{
768    TrWindow         * self;
769    GtkTreeSelection * sel;
770
771    TR_IS_WINDOW( data );
772    self = TR_WINDOW( data );
773    if( self->disposed || 0 > self->doubleclick )
774    {
775        return;
776    }
777
778    sel = gtk_tree_view_get_selection( self->view );
779    gtk_tree_selection_select_path( sel, path );
780
781    emitaction( self, self->doubleclick );
782}
783
784static void
785emitaction( TrWindow * self, int id )
786{
787    TrWindowClass * class;
788
789    TR_IS_WINDOW( self );
790    if( self->disposed )
791    {
792        return;
793    }
794
795    class = g_type_class_peek( TR_WINDOW_TYPE );
796    g_signal_emit( self, class->actionsig, 0, id );
797}
798
799/* use with gtk_tree_selection_selected_foreach to | status of selected rows */
800static void
801orstatus( GtkTreeModel * model, GtkTreePath * path SHUTUP, GtkTreeIter * iter,
802          gpointer data )
803{
804    int * allstatus, thisstatus;
805
806  allstatus = data;
807  gtk_tree_model_get( model, iter, MC_STAT, &thisstatus, -1 );
808  *allstatus |= thisstatus;
809}
810
811/* data should be a TrTorrent**, will set torrent to NULL if it's selected */
812static void
813istorsel( GtkTreeModel * model, GtkTreePath * path SHUTUP, GtkTreeIter * iter,
814          gpointer data )
815{
816    TrTorrent ** torref, * tor;
817
818    torref = data;
819    if( NULL != *torref )
820    {
821        gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
822        if( tor == *torref )
823        {
824            *torref = NULL;
825        }
826        g_object_unref( tor );
827    }
828}
Note: See TracBrowser for help on using the repository browser.