source: trunk/gtk/details.c @ 6280

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

#1069: Unknown seeders and leechers count in `Peers' tab

  • Property svn:keywords set to Date Rev Author Id
File size: 44.2 KB
Line 
1/*
2 * This file Copyright (C) 2007-2008 Charles Kerr <charles@rebelbase.com>
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: details.c 6280 2008-07-02 01:04:07Z charles $
11 */
12
13#include <errno.h>
14#include <stddef.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <glib/gi18n.h>
18#include <gtk/gtk.h>
19
20#include <libtransmission/transmission.h>
21#include <libtransmission/utils.h> /* tr_httpIsValidURL */
22
23#include "actions.h"
24#include "details.h"
25#include "file-list.h"
26#include "tr-torrent.h"
27#include "tracker-list.h"
28#include "hig.h"
29#include "util.h"
30
31#define UPDATE_INTERVAL_MSEC 2000
32
33/****
34*****  PIECES VIEW
35****/
36
37/* define SHOW_PIECES */
38
39#ifdef SHOW_PIECES
40static int
41getGridSize (int pieceCount, int * n_rows, int * n_cols)
42{
43  const int MAX_ACROSS = 16;
44  if (pieceCount >= (MAX_ACROSS * MAX_ACROSS)) {
45    *n_rows = *n_cols = MAX_ACROSS;
46    return MAX_ACROSS * MAX_ACROSS;
47  }
48  else {
49    int i;
50    for (i=0; (i*i) < pieceCount; ++i);
51    *n_rows = *n_cols = i;
52    return pieceCount;
53  }
54}
55
56#define TO16(a) ((guint16)((a<<8)|(a)))
57#define RGB_2_GDK(R,G,B) { 0, TO16(R), TO16(G), TO16(B) }
58
59enum { DRAW_AVAIL, DRAW_PROG };
60
61static void
62release_gobject_array (gpointer data)
63{
64  int i;
65  GObject **objects = (GObject**) data;
66  for (i=0; objects[i]!=NULL; ++i)
67    g_object_unref (G_OBJECT(objects[i]));
68  g_free (objects);
69}
70
71static gboolean
72refresh_pieces( GtkWidget * da, GdkEventExpose * event UNUSED, gpointer gtor )
73{
74  tr_torrent * tor = tr_torrent_handle( TR_TORRENT(gtor) );
75  const tr_info * info = tr_torrent_info( TR_TORRENT(gtor) );
76  int mode = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(da), "draw-mode"));
77
78  GdkColormap * colormap = gtk_widget_get_colormap (da);
79  const int widget_w = da->allocation.width;
80  const int widget_h = da->allocation.height;
81  int n_rows, n_cols;
82  const int n_cells = getGridSize (info->pieceCount,  &n_rows, &n_cols);
83  const GdkRectangle grid_bounds = { 0, 0, widget_w, widget_h };
84  const double piece_w = grid_bounds.width / (double)n_cols;
85  const double piece_h = grid_bounds.height / (double)n_rows;
86  const int piece_w_int = (int) (piece_w + 1); /* pad for roundoff */
87  const int piece_h_int = (int) (piece_h + 1); /* pad for roundoff */
88  const gboolean rtl = gtk_widget_get_direction( da ) == GTK_TEXT_DIR_RTL;
89
90  guint8 * prev_color = NULL;
91  gboolean first_time = FALSE;
92
93  int i, x, y;
94  int8_t * pieces = NULL;
95  float * completeness = NULL;
96
97  /**
98  ***  Get the Graphics Contexts...
99  **/
100
101  enum { ALL, LOTS, SOME, FEW, NONE,
102         BLACK, GRAY, BLINK,
103         N_COLORS };
104  GdkGC **gcs = (GdkGC**) g_object_get_data (G_OBJECT(da), "graphics-contexts");
105  if (gcs == NULL)
106  {
107    const GdkColor colors [N_COLORS] = {
108      RGB_2_GDK ( 114, 159, 207 ), /* all */
109      RGB_2_GDK (  52, 101, 164 ), /* lots */
110      RGB_2_GDK (  32,  74, 135 ), /* some */
111      RGB_2_GDK (  85,  87,  83 ), /* few */
112      RGB_2_GDK ( 238, 238, 236 ), /* none - tango aluminum highlight */
113      RGB_2_GDK (  46,  52,  54 ), /* black - tango slate shadow */
114      RGB_2_GDK ( 186, 189, 182 ), /* gray - tango aluminum shadow */
115      RGB_2_GDK ( 252, 233,  79 ), /* blink - tango butter highlight */
116    };
117
118    gcs = g_new (GdkGC*, N_COLORS+1);
119
120    for (i=0; i<N_COLORS; ++i) {
121      gcs[i] = gdk_gc_new (da->window);
122      gdk_gc_set_colormap (gcs[i], colormap);
123      gdk_gc_set_rgb_fg_color (gcs[i], &colors[i]);
124      gdk_gc_set_rgb_bg_color (gcs[i], &colors[i]);
125    };
126
127    gcs[N_COLORS] = NULL; /* a sentinel in the release function */
128    g_object_set_data_full (G_OBJECT(da), "graphics-contexts",
129                            gcs, release_gobject_array);
130  }
131
132  /**
133  ***  Get the cells' previous colors...
134  ***  (this is used for blinking when the color changes)
135  **/
136
137  prev_color = (guint8*) g_object_get_data (G_OBJECT(da), "prev-color");
138  if (prev_color == NULL)
139  {
140    first_time = TRUE;
141    prev_color = g_new0 (guint8, n_cells);
142    g_object_set_data_full (G_OBJECT(da), "prev-color", prev_color, g_free);
143  }
144
145  /**
146  ***  Get the piece data values...
147  **/
148
149  switch (mode) {
150    case DRAW_AVAIL:
151      pieces = g_new (int8_t, n_cells);
152      tr_torrentAvailability ( tor, pieces, n_cells );
153      break;
154    case DRAW_PROG:
155      completeness = g_new (float, n_cells);
156      tr_torrentAmountFinished ( tor, completeness, n_cells );
157      break;
158    default: g_error("no mode defined!");
159  }
160
161  /**
162  ***  Draw...
163  **/
164
165  i = 0; 
166  for (y=0; y<n_rows; ++y) {
167    for (x=0; x<n_cols; ++x) {
168      int draw_x = grid_bounds.x + (int)(x * piece_w);
169      int draw_y = grid_bounds.y + (int)(y * piece_h);
170      int color = BLACK;
171      int border = BLACK;
172
173      if( rtl )
174          draw_x = grid_bounds.x + grid_bounds.width - (int)((x+1) * piece_w);
175      else
176          draw_x = grid_bounds.x + (int)(x * piece_w);
177      draw_y = grid_bounds.y + (int)(y * piece_h);
178
179      if (i < n_cells)
180      {
181        border = GRAY;
182
183        if (mode == DRAW_AVAIL) {
184          const int8_t val = pieces[i];
185               if (val <  0) color = ALL;
186          else if (val == 0) color = NONE;
187          else if (val <= 4) color = FEW;
188          else if (val <= 8) color = SOME;
189          else               color = LOTS;
190        } else { /* completeness */
191          const float val = completeness[i];
192               if (val >= 1.00) color = ALL;
193          else if (val >= 0.66) color = LOTS;
194          else if (val >= 0.33) color = SOME;
195          else if (val >= 0.01) color = FEW;
196          else                  color = NONE;
197        }
198
199        /* draw a "blink" for one interval when a piece changes */
200        if (first_time)
201          prev_color[i] = color;
202        else if (color != prev_color[i]) {
203          prev_color[i] = color;
204          color = border = BLINK;
205        }
206      }
207
208      gdk_draw_rectangle (da->window, gcs[color], TRUE,
209                          draw_x, draw_y,
210                          piece_w_int, piece_h_int);
211
212      if (i < n_cells)
213        gdk_draw_rectangle (da->window, gcs[border], FALSE,
214                            draw_x, draw_y,
215                            piece_w_int, piece_h_int);
216
217      ++i;
218    }
219  }
220
221  gdk_draw_rectangle (da->window, gcs[GRAY], FALSE,
222                      grid_bounds.x, grid_bounds.y, 
223                      grid_bounds.width-1, grid_bounds.height-1);
224
225  g_free (pieces);
226  g_free (completeness);
227  return FALSE;
228}
229#endif
230
231/****
232*****  PEERS TAB
233****/
234
235enum
236{
237  WEBSEED_COL_URL,
238  WEBSEED_COL_DOWNLOAD_RATE,
239  N_WEBSEED_COLS
240};
241
242static const char * webseed_column_names[N_WEBSEED_COLS] =
243{
244    N_( "Web Seeds" ),
245    /* 'download speed' column header. terse to keep the column narrow. */
246    N_( "Down" )
247};
248
249static GtkTreeModel*
250webseed_model_new( const tr_torrent * tor )
251{
252    int i;
253    const tr_info * inf = tr_torrentInfo( tor );
254    float * speeds = tr_torrentWebSpeeds( tor );
255    GtkListStore * store = gtk_list_store_new( N_WEBSEED_COLS,
256                                               G_TYPE_STRING,
257                                               G_TYPE_FLOAT );
258    for( i=0; i<inf->webseedCount; ++i )
259    {
260        GtkTreeIter iter;
261        gtk_list_store_append( store, &iter );
262        gtk_list_store_set( store, &iter, WEBSEED_COL_URL, inf->webseeds[i],
263                                          WEBSEED_COL_DOWNLOAD_RATE, speeds[i],
264                                          -1 );
265    }
266
267    tr_free( speeds );
268    return GTK_TREE_MODEL( store );
269}
270
271enum
272{
273  PEER_COL_ADDRESS,
274  PEER_COL_DOWNLOAD_RATE,
275  PEER_COL_UPLOAD_RATE,
276  PEER_COL_CLIENT,
277  PEER_COL_PROGRESS,
278  PEER_COL_IS_ENCRYPTED,
279  PEER_COL_STATUS,
280  N_PEER_COLS
281};
282
283static const char* peer_column_names[N_PEER_COLS] =
284{
285  N_("Address"),
286  /* 'download speed' column header. terse to keep the column narrow. */
287  N_("Down"),
288  /* 'upload speed' column header.  terse to keep the column narrow. */
289  N_("Up"),
290  N_("Client"),
291  /* 'percent done' column header. terse to keep the column narrow. */
292  N_("%"),
293  " ",
294  N_("Status")
295};
296
297static int compare_peers (const void * a, const void * b)
298{
299  const tr_peer_stat * pa = a;
300  const tr_peer_stat * pb = b;
301  return strcmp (pa->addr, pb->addr);
302}
303static int compare_addr_to_peer (const void * a, const void * b)
304{
305  const char * addr = (const char *) a;
306  const tr_peer_stat * peer = b;
307  return strcmp (addr, peer->addr);
308}
309
310static void
311peer_row_set (GtkListStore        * store,
312              GtkTreeIter         * iter,
313              const tr_peer_stat  * peer)
314{
315  const char * client = peer->client;
316
317  if (!client || !strcmp(client,"Unknown Client"))
318    client = " ";
319
320  gtk_list_store_set( store, iter,
321                      PEER_COL_ADDRESS, peer->addr,
322                      PEER_COL_CLIENT, client,
323                      PEER_COL_IS_ENCRYPTED, peer->isEncrypted,
324                      PEER_COL_PROGRESS, (int)(100.0*peer->progress),
325                      PEER_COL_DOWNLOAD_RATE, peer->downloadFromRate,
326                      PEER_COL_UPLOAD_RATE, peer->uploadToRate,
327                      PEER_COL_STATUS, peer->flagStr,
328                      -1);
329}
330
331static void
332append_peers_to_model (GtkListStore        * store,
333                       const tr_peer_stat  * peers,
334                       int                   n_peers)
335{
336  int i;
337  for (i=0; i<n_peers; ++i) {
338    GtkTreeIter iter;
339    gtk_list_store_append( store, &iter );
340    peer_row_set (store, &iter, &peers[i]);
341  }
342}
343
344static GtkTreeModel*
345peer_model_new (tr_torrent * tor)
346{
347  GtkListStore * m = gtk_list_store_new (N_PEER_COLS,
348                                         G_TYPE_STRING,   /* addr */
349                                         G_TYPE_FLOAT,    /* downloadFromRate */
350                                         G_TYPE_FLOAT,    /* uploadToRate */
351                                         G_TYPE_STRING,   /* client */
352                                         G_TYPE_INT,      /* progress [0..100] */
353                                         G_TYPE_BOOLEAN,  /* isEncrypted */
354                                         G_TYPE_STRING ); /* flagString */
355
356  int n_peers = 0;
357  tr_peer_stat * peers = tr_torrentPeers (tor, &n_peers);
358  qsort (peers, n_peers, sizeof(tr_peer_stat), compare_peers);
359  append_peers_to_model (m, peers, n_peers);
360  tr_torrentPeersFree( peers, 0 );
361  return GTK_TREE_MODEL (m);
362}
363
364static void
365render_encrypted (GtkTreeViewColumn  * column UNUSED,
366                  GtkCellRenderer    * renderer,
367                  GtkTreeModel       * tree_model,
368                  GtkTreeIter        * iter,
369                  gpointer             data UNUSED)
370{
371  gboolean is_encrypted = FALSE;
372  gtk_tree_model_get (tree_model, iter, PEER_COL_IS_ENCRYPTED, &is_encrypted,
373                                        -1);
374  g_object_set (renderer, "xalign", (gfloat)0.0,
375                          "yalign", (gfloat)0.5,
376                          "stock-id", (is_encrypted ? "transmission-lock" : NULL),
377                          NULL);
378}
379
380static void
381render_ul_rate (GtkTreeViewColumn  * column UNUSED,
382                GtkCellRenderer    * renderer,
383                GtkTreeModel       * tree_model,
384                GtkTreeIter        * iter,
385                gpointer             data UNUSED)
386{
387  float rate = 0.0;
388  gtk_tree_model_get (tree_model, iter, PEER_COL_UPLOAD_RATE, &rate, -1);
389  if( rate < 0.01 )
390    g_object_set (renderer, "text", "", NULL);
391  else {
392    char speedStr[64];
393    tr_strlspeed( speedStr, rate, sizeof(speedStr) );
394    g_object_set( renderer, "text", speedStr, NULL );
395  }
396}
397
398static void
399render_dl_rate (GtkTreeViewColumn  * column UNUSED,
400                GtkCellRenderer    * renderer,
401                GtkTreeModel       * tree_model,
402                GtkTreeIter        * iter,
403                gpointer             data UNUSED)
404{
405  float rate = 0.0;
406  gtk_tree_model_get (tree_model, iter, PEER_COL_DOWNLOAD_RATE, &rate, -1);
407  if( rate < 0.01 )
408    g_object_set (renderer, "text", "", NULL);
409  else {
410    char speedStr[64];
411    tr_strlspeed( speedStr, rate, sizeof(speedStr) );
412    g_object_set( renderer, "text", speedStr, NULL );
413  }
414}
415
416static void
417render_client (GtkTreeViewColumn   * column UNUSED,
418               GtkCellRenderer     * renderer,
419               GtkTreeModel        * tree_model,
420               GtkTreeIter         * iter,
421               gpointer              data UNUSED)
422{
423  char * client = NULL;
424  gtk_tree_model_get (tree_model, iter, PEER_COL_CLIENT, &client,
425                                        -1);
426  g_object_set (renderer, "text", (client ? client : ""), NULL);
427  g_free (client);
428}
429
430typedef struct
431{
432  TrTorrent * gtor;
433  GtkTreeModel * model; /* same object as store, but recast */
434  GtkListStore * store; /* same object as model, but recast */
435  GtkListStore * webseeds;
436  GtkWidget * completeness;
437  GtkWidget * seeders_lb;
438  GtkWidget * leechers_lb;
439  GtkWidget * completed_lb;
440}
441PeerData;
442
443static void
444fmtpeercount (GtkWidget * l, int count)
445{
446  if( 0 > count ) {
447    gtk_label_set_text( GTK_LABEL(l), "?" );
448  } else {
449    char str[16];
450    g_snprintf( str, sizeof str, "%'d", count );
451    gtk_label_set_text( GTK_LABEL(l), str );
452  }
453}
454
455static void
456refresh_peers (GtkWidget * top)
457{
458  int i;
459  int seedCount = 0;
460  int leechCount = 0;
461  int n_peers;
462  GtkTreeIter iter;
463  PeerData * p = (PeerData*) g_object_get_data (G_OBJECT(top), "peer-data");
464  tr_torrent * tor = tr_torrent_handle ( p->gtor );
465  GtkTreeModel * model = p->model;
466  GtkListStore * store = p->store;
467  tr_peer_stat * peers;
468  const tr_stat * stat = tr_torrent_stat( p->gtor );
469  const tr_info * inf = tr_torrent_info( p->gtor );
470
471  if( inf->webseedCount )
472  {
473    float * speeds = tr_torrentWebSpeeds( tor );
474    for( i=0; i<inf->webseedCount; ++i )
475    {
476        GtkTreeIter iter;
477        gtk_tree_model_iter_nth_child( GTK_TREE_MODEL( p->webseeds ), &iter, NULL, i );
478        gtk_list_store_set( p->webseeds, &iter, WEBSEED_COL_DOWNLOAD_RATE, speeds[i],
479                                                -1 );
480    }
481    tr_free( speeds );
482  }
483
484  /**
485  ***  merge the peer diffs into the tree model.
486  ***
487  ***  this is more complicated than creating a new model,
488  ***  but is also (a) more efficient and (b) doesn't undo
489  ***  the view's visible area and sorting on every refresh.
490  **/
491
492  n_peers = 0;
493  peers = tr_torrentPeers (tor, &n_peers);
494  qsort (peers, n_peers, sizeof(tr_peer_stat), compare_peers);
495  for( i=0; i<n_peers; ++i ) {
496    if( peers[i].progress >= 1.0 )
497      ++seedCount;
498    else
499      ++leechCount;
500  }
501
502  i = 0;
503  if (gtk_tree_model_get_iter_first (model, &iter)) do
504  {
505    char * addr = NULL;
506    tr_peer_stat * peer = NULL;
507    gtk_tree_model_get (model, &iter, PEER_COL_ADDRESS, &addr, -1);
508    peer = bsearch (addr, peers, n_peers, sizeof(tr_peer_stat),
509                    compare_addr_to_peer);
510    g_free (addr);
511
512    if (peer) /* update a pre-existing row */
513    {
514      const int pos = peer - peers;
515      const int n_rhs = n_peers - (pos+1);
516      g_assert (n_rhs >= 0);
517
518      peer_row_set (store, &iter, peer);
519
520      /* remove it from the tr_peer_stat list */
521      g_memmove (peer, peer+1, sizeof(tr_peer_stat)*n_rhs);
522      --n_peers;
523    }
524    else if (!gtk_list_store_remove (store, &iter))
525      break; /* we removed the model's last item */
526  }
527  while (gtk_tree_model_iter_next (model, &iter));
528
529  append_peers_to_model (store, peers, n_peers);  /* all these are new */
530
531#ifdef SHOW_PIECES
532  if (GDK_IS_DRAWABLE (p->completeness->window))
533    refresh_pieces (p->completeness, NULL, p->gtor);
534#endif
535
536  /* use the tracker-supplied information if it's available;
537   * otherwise, use the counts of connected peers as a fallback */
538  fmtpeercount (p->seeders_lb, stat->seeders >= 0 ? stat->seeders : seedCount );
539  fmtpeercount (p->leechers_lb, stat->leechers >= 0 ? stat->leechers : leechCount );
540  fmtpeercount (p->completed_lb, stat->timesCompleted );
541
542  free( peers );
543}
544
545#if GTK_CHECK_VERSION(2,12,0)
546static gboolean
547onPeerViewQueryTooltip( GtkWidget  * widget,
548                        gint         x,
549                        gint         y,
550                        gboolean     keyboard_tip,
551                        GtkTooltip * tooltip,
552                        gpointer     user_data UNUSED )
553{
554    gboolean show_tip = FALSE;
555    GtkTreeModel * model;
556    GtkTreeIter iter;
557
558    if( gtk_tree_view_get_tooltip_context( GTK_TREE_VIEW( widget ),
559                                           &x, &y, keyboard_tip,
560                                           &model, NULL, &iter ) )
561    {
562        const char * pch;
563        char * str = NULL;
564        GString * gstr = g_string_new( NULL );
565        gtk_tree_model_get( model, &iter, PEER_COL_STATUS, &str, -1 );
566        for( pch=str; pch && *pch; ++pch ) {
567            const char * txt = NULL;
568            switch( *pch ) {
569                case 'O': txt = _( "Optimistic unchoke" ); break;
570                case 'D': txt = _( "Downloading from peer" ); break;
571                case 'd': txt = _( "We'd download from peer if they'd let us" ); break;
572                case 'U': txt = _( "Uploading to peer" ); break;
573                case 'u': txt = _( "We'd upload to peer if they'd ask us" ); break;
574                case 'K': txt = _( "Peer has unchoked us, but we're not interested" ); break;
575                case '?': txt = _( "We unchoked the peer, but they're not interested" ); break;
576                case 'E': txt = _( "Encrypted connection" ); break;
577                case 'X': txt = _( "Peer was discovered through Peer Exchange (PEX)" ); break;
578                case 'I': txt = _( "Peer is an incoming connection" ); break;
579            }
580            if( txt )
581                g_string_append_printf( gstr, "%c: %s\n", *pch, txt );
582        }
583        if( gstr->len ) /* remove the last linefeed */
584            g_string_set_size( gstr, gstr->len-1 );
585        gtk_tooltip_set_text( tooltip, gstr->str );
586        g_string_free( gstr, TRUE );
587        g_free( str );
588        show_tip = TRUE;
589    }
590
591    return show_tip;
592
593}
594#endif
595
596static GtkWidget*
597peer_page_new ( TrTorrent * gtor )
598{
599  guint i;
600  GtkTreeModel *m;
601  GtkWidget *v, *w, *ret, *sw, *l, *vbox, *hbox;
602  GtkWidget *webtree = NULL;
603  tr_torrent * tor = tr_torrent_handle (gtor);
604  PeerData * p = g_new (PeerData, 1);
605  const tr_info * inf = tr_torrent_info( gtor );
606
607  /* TODO: make this configurable? */
608  int view_columns[] = { PEER_COL_IS_ENCRYPTED,
609                         PEER_COL_UPLOAD_RATE,
610                         PEER_COL_DOWNLOAD_RATE,
611                         PEER_COL_PROGRESS,
612                         PEER_COL_STATUS,
613                         PEER_COL_ADDRESS,
614                         PEER_COL_CLIENT };
615
616 
617  if( inf->webseedCount )
618  {
619    GtkTreeViewColumn * c;
620    GtkCellRenderer * r;
621    const char * t;
622    GtkWidget * fr;
623
624    m = webseed_model_new( tr_torrent_handle( gtor ) );
625    webtree = gtk_tree_view_new_with_model( m );
626    gtk_tree_view_set_rules_hint( GTK_TREE_VIEW( webtree ), TRUE );
627    p->webseeds = GTK_LIST_STORE( m );
628    g_object_unref( G_OBJECT( m ) );
629
630    t = _( webseed_column_names[WEBSEED_COL_URL] );
631    r = gtk_cell_renderer_text_new ();
632    g_object_set( G_OBJECT( r ), "ellipsize", PANGO_ELLIPSIZE_END, NULL );
633    c = gtk_tree_view_column_new_with_attributes( t, r, "text", WEBSEED_COL_URL, NULL);
634    g_object_set( G_OBJECT( c ), "expand", TRUE, NULL );
635    gtk_tree_view_column_set_sort_column_id( c, WEBSEED_COL_URL );
636    gtk_tree_view_append_column( GTK_TREE_VIEW( webtree ), c );
637
638    t = _( webseed_column_names[WEBSEED_COL_DOWNLOAD_RATE] );
639    r = gtk_cell_renderer_text_new ();
640    c = gtk_tree_view_column_new_with_attributes (t, r, "text", WEBSEED_COL_DOWNLOAD_RATE, NULL);
641    gtk_tree_view_column_set_cell_data_func (c, r, render_dl_rate,
642                                                   NULL, NULL);
643    gtk_tree_view_column_set_sort_column_id( c, WEBSEED_COL_DOWNLOAD_RATE );
644    gtk_tree_view_append_column( GTK_TREE_VIEW( webtree ), c );
645
646    fr = gtk_frame_new( NULL );
647    gtk_frame_set_shadow_type( GTK_FRAME( fr ), GTK_SHADOW_IN );
648    gtk_container_add( GTK_CONTAINER( fr ), webtree );
649    webtree = fr;
650  } 
651
652  m  = peer_model_new (tor);
653  v = GTK_WIDGET( g_object_new( GTK_TYPE_TREE_VIEW,
654                                "model", m,
655                                "rules-hint", TRUE,
656#if GTK_CHECK_VERSION(2,12,0)
657                                "has-tooltip", TRUE,
658#endif
659                                NULL ) );
660#if GTK_CHECK_VERSION(2,12,0)
661  g_signal_connect( v, "query-tooltip",
662                    G_CALLBACK(onPeerViewQueryTooltip), NULL );
663#endif
664  gtk_widget_set_size_request( v, 550, 0 );
665  g_object_unref (G_OBJECT(m));
666
667  for (i=0; i<G_N_ELEMENTS(view_columns); ++i)
668  {
669    const int col = view_columns[i];
670    const char * t = _(peer_column_names[col]);
671    gboolean resizable = TRUE;
672    GtkTreeViewColumn * c;
673    GtkCellRenderer * r;
674
675    switch (col)
676    {
677      case PEER_COL_ADDRESS:
678        r = gtk_cell_renderer_text_new ();
679        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
680        break;
681
682      case PEER_COL_CLIENT:
683        r = gtk_cell_renderer_text_new ();
684        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
685        gtk_tree_view_column_set_cell_data_func (c, r, render_client,
686                                                 NULL, NULL);
687        break;
688
689      case PEER_COL_PROGRESS:
690        r = gtk_cell_renderer_progress_new ();
691        c = gtk_tree_view_column_new_with_attributes (t, r, "value", PEER_COL_PROGRESS, NULL);
692        break;
693
694      case PEER_COL_IS_ENCRYPTED:
695        resizable = FALSE;
696        r = gtk_cell_renderer_pixbuf_new ();
697        c = gtk_tree_view_column_new_with_attributes (t, r, NULL);
698        gtk_tree_view_column_set_sizing (c, GTK_TREE_VIEW_COLUMN_FIXED);
699        gtk_tree_view_column_set_fixed_width (c, 20);
700        gtk_tree_view_column_set_cell_data_func (c, r, render_encrypted,
701                                                 NULL, NULL);
702        break;
703
704      case PEER_COL_DOWNLOAD_RATE:
705        r = gtk_cell_renderer_text_new ();
706        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
707        gtk_tree_view_column_set_cell_data_func (c, r, render_dl_rate,
708                                                 NULL, NULL);
709        break;
710
711      case PEER_COL_UPLOAD_RATE:
712        r = gtk_cell_renderer_text_new ();
713        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
714        gtk_tree_view_column_set_cell_data_func (c, r, render_ul_rate,
715                                                 NULL, NULL);
716        break;
717
718      case PEER_COL_STATUS:
719        r = gtk_cell_renderer_text_new( );
720        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
721        break;
722
723      default:
724        abort ();
725    }
726
727    gtk_tree_view_column_set_resizable (c, resizable);
728    gtk_tree_view_column_set_sort_column_id (c, col);
729    gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
730  }
731
732  /* the 'expander' column has a 10-pixel margin on the left
733     that doesn't look quite correct in any of these columns...
734     so create a non-visible column and assign it as the
735     'expander column. */
736  {
737    GtkTreeViewColumn *c = gtk_tree_view_column_new ();
738    gtk_tree_view_column_set_visible (c, FALSE);
739    gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
740    gtk_tree_view_set_expander_column (GTK_TREE_VIEW(v), c);
741  }
742
743  w = sw = gtk_scrolled_window_new (NULL, NULL);
744  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(w),
745                                  GTK_POLICY_NEVER,
746                                  GTK_POLICY_AUTOMATIC);
747  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(w),
748                                       GTK_SHADOW_IN);
749  gtk_container_add (GTK_CONTAINER(w), v);
750
751
752  vbox = gtk_vbox_new (FALSE, GUI_PAD);
753  gtk_container_set_border_width (GTK_CONTAINER(vbox), GUI_PAD_BIG);
754
755    if( webtree )
756        gtk_box_pack_start( GTK_BOX( vbox ), webtree, FALSE, FALSE, 0 );
757
758    //h = gtk_hbox_new (FALSE, GUI_PAD);
759    //gtk_box_pack_start_defaults (GTK_BOX(h), sw);
760    gtk_box_pack_start_defaults( GTK_BOX( vbox ), sw );
761
762    hbox = gtk_hbox_new (FALSE, GUI_PAD);
763        l = gtk_label_new (NULL);
764        gtk_label_set_markup (GTK_LABEL(l), _( "<b>Seeders:</b>" ) );
765        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
766        l = p->seeders_lb = gtk_label_new (NULL);
767        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
768    gtk_box_pack_start_defaults (GTK_BOX(hbox),
769                                 gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
770        l = gtk_label_new (NULL);
771        gtk_label_set_markup (GTK_LABEL(l), _( "<b>Leechers:</b>" ) );
772        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
773        l = p->leechers_lb = gtk_label_new (NULL);
774        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
775    gtk_box_pack_start_defaults (GTK_BOX(hbox),
776                                 gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
777        l = gtk_label_new (NULL);
778        gtk_label_set_markup (GTK_LABEL(l), _( "<b>Times Completed:</b>" ) );
779        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
780        l = p->completed_lb = gtk_label_new (NULL);
781        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
782    gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
783
784  ret = vbox;
785  p->gtor = gtor;
786  p->model = m;
787  p->store = GTK_LIST_STORE(m);
788  g_object_set_data_full (G_OBJECT(ret), "peer-data", p, g_free);
789  return ret;
790}
791
792/****
793*****  INFO TAB
794****/
795
796static void
797refresh_time_lb( GtkWidget * l, time_t t )
798{
799    const char * never = _( "Never" );
800    if( !t )
801        gtk_label_set_text( GTK_LABEL( l ), never );
802    else {
803        char * str = rfc822date( t );
804        gtk_label_set_text( GTK_LABEL( l ), str );
805        g_free( str );
806    }
807}
808
809static GtkWidget*
810info_page_new (tr_torrent * tor)
811{
812  int row = 0;
813  GtkWidget *t = hig_workarea_create ();
814  GtkWidget *l, *w, *fr;
815  char *pch;
816  char sizeStr[128];
817  char countStr[128];
818  char buf[256];
819  GtkTextBuffer * b;
820  const tr_info * info = tr_torrentInfo(tor);
821
822  hig_workarea_add_section_title (t, &row, _("Details"));
823
824    g_snprintf( countStr, sizeof( countStr ),
825                ngettext( "%d Piece", "%d Pieces", info->pieceCount ),
826                info->pieceCount );
827    tr_strlsize( sizeStr, info->pieceSize, sizeof(sizeStr) );
828    g_snprintf( buf, sizeof( buf ),
829                /* %1$s is number of pieces;
830                   %2$s is how big each piece is */
831                _( "%1$s @ %2$s" ),
832              countStr, sizeStr );
833
834    l = gtk_label_new (buf);
835    hig_workarea_add_row (t, &row, _("Pieces:"), l, NULL);
836
837    l = g_object_new( GTK_TYPE_LABEL, "label", info->hashString, "selectable", TRUE,
838                                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
839    hig_workarea_add_row (t, &row, _("Hash:"), l, NULL);
840
841    pch = (info->isPrivate )
842      ? _("Private to this tracker -- PEX disabled")
843      : _("Public torrent");
844    l = gtk_label_new (pch);
845    hig_workarea_add_row (t, &row, _("Privacy:"), l, NULL);
846
847    b = gtk_text_buffer_new (NULL);
848    if( info->comment )
849        gtk_text_buffer_set_text (b, info->comment, -1);
850    w = gtk_text_view_new_with_buffer (b);
851    gtk_widget_set_size_request (w, 0u, 100u);
852    gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW(w), GTK_WRAP_WORD); 
853    gtk_text_view_set_editable (GTK_TEXT_VIEW(w), FALSE);
854    fr = gtk_frame_new (NULL);
855    gtk_frame_set_shadow_type (GTK_FRAME(fr), GTK_SHADOW_IN);
856    gtk_container_add (GTK_CONTAINER(fr), w);
857    w = hig_workarea_add_row (t, &row, _("Comment:"), fr, NULL);
858    gtk_misc_set_alignment (GTK_MISC(w), 0.0f, 0.0f);
859
860  hig_workarea_add_section_divider (t, &row);
861  hig_workarea_add_section_title (t, &row, _("Origins"));
862 
863    l = gtk_label_new (*info->creator ? info->creator : _("Unknown"));
864    hig_workarea_add_row (t, &row, _("Creator:"), l, NULL);
865
866    l = gtk_label_new( NULL );
867    refresh_time_lb( l, info->dateCreated );
868    hig_workarea_add_row (t, &row, _("Date:"), l, NULL); 
869
870  hig_workarea_add_section_divider (t, &row);
871  hig_workarea_add_section_title (t, &row, _("Location"));
872
873    l = g_object_new( GTK_TYPE_LABEL, "label", tr_torrentGetDownloadDir( tor ), "selectable", TRUE,
874                                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
875    hig_workarea_add_row (t, &row, _( "Destination folder:" ), l, NULL); 
876
877    l = g_object_new( GTK_TYPE_LABEL, "label", info->torrent, "selectable", TRUE,
878                                      "ellipsize", PANGO_ELLIPSIZE_END, NULL );
879    hig_workarea_add_row (t, &row, _( "Torrent file:" ), l, NULL); 
880
881  hig_workarea_finish (t, &row);
882  return t;
883}
884
885/****
886*****  ACTIVITY TAB
887****/
888
889typedef struct
890{
891  GtkWidget * state_lb;
892  GtkWidget * progress_lb;
893  GtkWidget * have_lb;
894  GtkWidget * dl_lb;
895  GtkWidget * ul_lb;
896  GtkWidget * failed_lb;
897  GtkWidget * ratio_lb;
898  GtkWidget * err_lb;
899  GtkWidget * swarm_lb;
900  GtkWidget * date_added_lb;
901  GtkWidget * last_activity_lb;
902  GtkWidget * availability_da;
903  TrTorrent * gtor;
904}
905Activity;
906
907static void
908refresh_activity (GtkWidget * top)
909{
910  Activity * a = g_object_get_data (G_OBJECT(top), "activity-data");
911  char *pch;
912  char sizeStr[64];
913  char sizeStr2[64];
914  char buf[128];
915  const tr_stat * stat = tr_torrent_stat( a->gtor );
916  const double complete = stat->percentComplete * 100.0;
917  const double done = stat->percentDone * 100.0;
918
919  pch = tr_torrent_status_str( a->gtor );
920  gtk_label_set_text (GTK_LABEL(a->state_lb), pch);
921  g_free (pch);
922
923  if( (int)complete == (int)done )
924    pch = g_strdup_printf( _( "%.1f%%" ), complete );
925  else
926    /* %1$.1f is percent of how much of what we want's been downloaded,
927       %2$.1f is percent of how much of the whole torrent we've downloaded */
928    pch = g_strdup_printf( _( "%1$.1f%% (%2$.1f%% selected)" ), complete, done );
929  gtk_label_set_text (GTK_LABEL(a->progress_lb), pch);
930  g_free (pch);
931
932  tr_strlsize( sizeStr,  stat->haveValid + stat->haveUnchecked, sizeof(sizeStr) );
933  tr_strlsize( sizeStr2, stat->haveValid,                       sizeof(sizeStr2) );
934  /* %1$s is total size of what we've saved to disk
935     %2$s is how much of it's passed the checksum test */
936  g_snprintf( buf, sizeof(buf), _("%1$s (%2$s verified)"), sizeStr, sizeStr2 );
937  gtk_label_set_text( GTK_LABEL( a->have_lb ), buf );
938
939  tr_strlsize( sizeStr, stat->downloadedEver, sizeof(sizeStr) );
940  gtk_label_set_text( GTK_LABEL(a->dl_lb), sizeStr );
941
942  tr_strlsize( sizeStr, stat->uploadedEver, sizeof(sizeStr) );
943  gtk_label_set_text( GTK_LABEL(a->ul_lb), sizeStr );
944
945  tr_strlsize( sizeStr, stat->corruptEver, sizeof( sizeStr ) );
946  gtk_label_set_text( GTK_LABEL( a->failed_lb ), sizeStr );
947
948  tr_strlratio( buf, stat->ratio, sizeof( buf ) );
949  gtk_label_set_text( GTK_LABEL( a->ratio_lb ), buf );
950
951  tr_strlspeed( buf, stat->swarmSpeed, sizeof(buf) );
952  gtk_label_set_text (GTK_LABEL(a->swarm_lb), buf );
953
954  gtk_label_set_text (GTK_LABEL(a->err_lb),
955                      *stat->errorString ? stat->errorString : _("None"));
956
957  refresh_time_lb( a->date_added_lb, stat->addedDate );
958
959  refresh_time_lb( a->last_activity_lb, stat->activityDate );
960
961#ifdef SHOW_PIECES
962  if (GDK_IS_DRAWABLE (a->availability_da->window))
963    refresh_pieces (a->availability_da, NULL, a->gtor);
964#endif
965}
966 
967
968static GtkWidget*
969activity_page_new (TrTorrent * gtor)
970{
971  Activity * a = g_new (Activity, 1);
972  int row = 0;
973  GtkWidget *t = hig_workarea_create ();
974  GtkWidget *l;
975
976  a->gtor = gtor;
977
978  hig_workarea_add_section_title (t, &row, _("Transfer"));
979
980    l = a->state_lb = gtk_label_new (NULL);
981    hig_workarea_add_row (t, &row, _("State:"), l, NULL);
982
983    l = a->progress_lb = gtk_label_new (NULL);
984    hig_workarea_add_row (t, &row, _("Progress:"), l, NULL);
985
986    l = a->have_lb = gtk_label_new (NULL);
987    /* "Have" refers to how much of the torrent we have */
988    hig_workarea_add_row (t, &row, _("Have:"), l, NULL);
989
990    l = a->dl_lb = gtk_label_new (NULL);
991    hig_workarea_add_row (t, &row, _("Downloaded:"), l, NULL);
992
993    l = a->ul_lb = gtk_label_new (NULL);
994    hig_workarea_add_row (t, &row, _("Uploaded:"), l, NULL);
995
996    /* how much downloaded data was corrupt */
997    l = a->failed_lb = gtk_label_new (NULL);
998    hig_workarea_add_row (t, &row, _("Failed DL:"), l, NULL);
999
1000    l = a->ratio_lb = gtk_label_new (NULL);
1001    hig_workarea_add_row (t, &row, _("Ratio:"), l, NULL);
1002
1003    l = a->swarm_lb = gtk_label_new (NULL);
1004    hig_workarea_add_row (t, &row, _("Swarm rate:"), l, NULL);
1005
1006    l = a->err_lb = gtk_label_new (NULL);
1007    hig_workarea_add_row (t, &row, _("Error:"), l, NULL);
1008
1009#ifdef SHOW_PIECES
1010  hig_workarea_add_section_divider (t, &row);
1011  hig_workarea_add_section_title (t, &row, _("Completion"));
1012
1013    w = a->availability_da = gtk_drawing_area_new ();
1014    gtk_widget_set_size_request (w, 0u, 100u);
1015    g_object_set_data (G_OBJECT(w), "draw-mode", GINT_TO_POINTER(DRAW_PROG));
1016    g_signal_connect (w, "expose-event", G_CALLBACK(refresh_pieces), gtor);
1017    hig_workarea_add_wide_control( t, &row, w );
1018#endif
1019
1020  hig_workarea_add_section_divider (t, &row);
1021  hig_workarea_add_section_title (t, &row, _("Dates"));
1022
1023    l = a->date_added_lb = gtk_label_new (NULL);
1024    hig_workarea_add_row (t, &row, _("Started at:"), l, NULL);
1025
1026    l = a->last_activity_lb = gtk_label_new (NULL);
1027    hig_workarea_add_row (t, &row, _("Last activity at:"), l, NULL);
1028
1029  hig_workarea_add_section_divider (t, &row);
1030  hig_workarea_finish (t, &row);
1031  g_object_set_data_full (G_OBJECT(t), "activity-data", a, g_free);
1032  return t;
1033}
1034
1035
1036/****
1037*****  OPTIONS
1038****/
1039
1040static void
1041speed_toggled_cb( GtkToggleButton * tb, gpointer gtor, int up_or_down )
1042{
1043  tr_torrent * tor = tr_torrent_handle (gtor);
1044  gboolean b = gtk_toggle_button_get_active(tb);
1045  tr_torrentSetSpeedMode( tor, up_or_down, b ? TR_SPEEDLIMIT_SINGLE
1046                                             : TR_SPEEDLIMIT_GLOBAL );
1047}
1048static void
1049ul_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
1050{
1051  speed_toggled_cb( tb, gtor, TR_UP );
1052}
1053static void
1054dl_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
1055{
1056  speed_toggled_cb( tb, gtor, TR_DOWN );
1057}
1058
1059
1060static void
1061sensitize_from_check_cb (GtkToggleButton *toggle, gpointer w)
1062{
1063  gtk_widget_set_sensitive (GTK_WIDGET(w),
1064                            gtk_toggle_button_get_active(toggle));
1065}
1066
1067static void
1068setSpeedLimit( GtkSpinButton* spin, gpointer gtor, int up_or_down )
1069{
1070  tr_torrent * tor = tr_torrent_handle (gtor);
1071  int kb_sec = gtk_spin_button_get_value_as_int (spin);
1072  tr_torrentSetSpeedLimit( tor, up_or_down, kb_sec );
1073}
1074static void
1075ul_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
1076{
1077  setSpeedLimit( spin, gtor, TR_UP );
1078}
1079static void
1080dl_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
1081{
1082  setSpeedLimit( spin, gtor, TR_DOWN );
1083}
1084
1085static void
1086max_peers_spun_cb( GtkSpinButton * spin, gpointer gtor )
1087{
1088  const uint16_t n = gtk_spin_button_get_value( spin );
1089  tr_torrentSetPeerLimit( tr_torrent_handle( gtor ), n );
1090}
1091
1092static GtkWidget*
1093options_page_new ( TrTorrent * gtor )
1094{
1095  uint16_t maxConnectedPeers;
1096  int i, row;
1097  gboolean b;
1098  GtkAdjustment *a;
1099  GtkWidget *t, *w, *tb;
1100  tr_torrent * tor = tr_torrent_handle (gtor);
1101
1102  row = 0;
1103  t = hig_workarea_create ();
1104  hig_workarea_add_section_title (t, &row, _("Bandwidth") );
1105
1106    tb = gtk_check_button_new_with_mnemonic (_("Limit _download speed (KB/s):"));
1107    b = tr_torrentGetSpeedMode(tor,TR_DOWN) == TR_SPEEDLIMIT_SINGLE;
1108    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), b );
1109    g_signal_connect (tb, "toggled", G_CALLBACK(dl_speed_toggled_cb), gtor);
1110
1111    i = tr_torrentGetSpeedLimit( tor, TR_DOWN );
1112    a = (GtkAdjustment*) gtk_adjustment_new (i, 0.0, G_MAXDOUBLE, 1, 1, 1);
1113    w = gtk_spin_button_new (a, 1, 0);
1114    g_signal_connect (w, "value-changed", G_CALLBACK(dl_speed_spun_cb), gtor);
1115    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1116    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1117    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1118
1119    tb = gtk_check_button_new_with_mnemonic (_("Limit _upload speed (KB/s):"));
1120    b = tr_torrentGetSpeedMode(tor,TR_UP) == TR_SPEEDLIMIT_SINGLE;
1121    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), b );
1122    g_signal_connect (tb, "toggled", G_CALLBACK(ul_speed_toggled_cb), gtor);
1123
1124    i = tr_torrentGetSpeedLimit( tor, TR_UP );
1125    a = (GtkAdjustment*) gtk_adjustment_new (i, 0.0, G_MAXDOUBLE, 1, 1, 1);
1126    w = gtk_spin_button_new (a, 1, 0);
1127    g_signal_connect (w, "value-changed", G_CALLBACK(ul_speed_spun_cb), gtor);
1128    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1129    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1130    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1131
1132  hig_workarea_add_section_divider (t, &row);
1133  hig_workarea_add_section_title (t, &row, _("Peer Connections"));
1134
1135    maxConnectedPeers = tr_torrentGetPeerLimit( tor );
1136    w = gtk_spin_button_new_with_range( 1, 3000, 5 );
1137    gtk_spin_button_set_value( GTK_SPIN_BUTTON( w ), maxConnectedPeers );
1138    hig_workarea_add_row( t, &row, _( "_Maximum peers:" ), w, w );
1139    g_signal_connect( w, "value-changed", G_CALLBACK( max_peers_spun_cb ), gtor );
1140
1141  hig_workarea_finish (t, &row);
1142  return t;
1143}
1144
1145static void
1146refresh_options (GtkWidget * top UNUSED)
1147{
1148}
1149
1150/****
1151*****  TRACKER
1152****/
1153
1154#define TRACKER_PAGE "tracker-page"
1155
1156struct tracker_page
1157{
1158    TrTorrent * gtor;
1159
1160    GtkTreeView * view;
1161    GtkListStore * store;
1162    GtkTreeSelection * sel;
1163
1164    GtkWidget * add_button;
1165    GtkWidget * remove_button;
1166    GtkWidget * save_button;
1167    GtkWidget * revert_button;
1168
1169    GtkWidget * last_scrape_time_lb;
1170    GtkWidget * last_scrape_response_lb;
1171    GtkWidget * next_scrape_countdown_lb;
1172
1173    GtkWidget * last_announce_time_lb;
1174    GtkWidget * last_announce_response_lb;
1175    GtkWidget * next_announce_countdown_lb;
1176    GtkWidget * manual_announce_countdown_lb;
1177};
1178
1179GtkWidget*
1180tracker_page_new( TrTorrent * gtor )
1181{
1182    GtkWidget * t;
1183    GtkWidget * l;
1184    GtkWidget * w;
1185    int row = 0;
1186    const char * s;
1187    struct tracker_page * page = g_new0( struct tracker_page, 1 );
1188    const tr_info * info = tr_torrent_info (gtor);
1189
1190    page->gtor = gtor;
1191
1192    t = hig_workarea_create( );
1193    hig_workarea_add_section_title( t, &row, _( "Trackers" ) );
1194
1195        w = tracker_list_new( gtor );
1196        hig_workarea_add_wide_control( t, &row, w );
1197
1198    hig_workarea_add_section_divider( t, &row );
1199    hig_workarea_add_section_title( t, &row, _( "Scrape" ) );
1200
1201        s = _( "Last scrape at:" );
1202        l = gtk_label_new( NULL );
1203        page->last_scrape_time_lb = l;
1204        hig_workarea_add_row( t, &row, s, l, NULL );
1205
1206        s = _( "Tracker responded:");
1207        l = gtk_label_new( NULL );
1208        page->last_scrape_response_lb = l;
1209        hig_workarea_add_row( t, &row, s, l, NULL );
1210
1211        s = _( "Next scrape in:" );
1212        l = gtk_label_new( NULL );
1213        page->next_scrape_countdown_lb = l;
1214        hig_workarea_add_row( t, &row, s, l, NULL );
1215
1216    hig_workarea_add_section_divider( t, &row );
1217    hig_workarea_add_section_title( t, &row, _( "Announce" ) );
1218
1219        l = gtk_label_new( info->trackers[0].announce );
1220        gtk_label_set_ellipsize( GTK_LABEL( l ), PANGO_ELLIPSIZE_END );
1221        hig_workarea_add_row (t, &row, _( "Tracker:" ), l, NULL);
1222
1223        s = _( "Last announce at:" );
1224        l = gtk_label_new( NULL );
1225        page->last_announce_time_lb = l;
1226        hig_workarea_add_row( t, &row, s, l, NULL );
1227
1228        s = _( "Tracker responded:");
1229        l = gtk_label_new( NULL );
1230        page->last_announce_response_lb = l;
1231        hig_workarea_add_row( t, &row, s, l, NULL );
1232
1233        s = _( "Next announce in:" );
1234        l = gtk_label_new( NULL );
1235        page->next_announce_countdown_lb = l;
1236        hig_workarea_add_row( t, &row, s, l, NULL );
1237
1238        /* how long until the tracker will honor user
1239         * pressing the "ask for more peers" button */
1240        s = _( "Manual announce allowed in:" );
1241        l = gtk_label_new( NULL );
1242        page->manual_announce_countdown_lb = l;
1243        hig_workarea_add_row( t, &row, s, l, NULL );
1244
1245    hig_workarea_finish( t, &row );
1246    g_object_set_data_full( G_OBJECT( t ), TRACKER_PAGE, page, g_free );
1247    return t;
1248}
1249
1250static void
1251refresh_countdown_lb( GtkWidget * l, time_t t,
1252                      const char * countdown_done )
1253{
1254    const time_t now = time( NULL );
1255
1256    if( !t || ( t < now ) )
1257        gtk_label_set_text( GTK_LABEL( l ), countdown_done );
1258    else {
1259        char buf[1024];
1260        const int seconds = t - now;
1261        tr_strltime( buf, seconds, sizeof( buf ) );
1262        gtk_label_set_text( GTK_LABEL( l ), buf );
1263    }
1264}
1265
1266static void
1267refresh_tracker( GtkWidget * w )
1268{
1269    GtkWidget * l;
1270    time_t t;
1271    struct tracker_page * page = g_object_get_data( G_OBJECT( w ), TRACKER_PAGE );
1272    const tr_stat * torStat = tr_torrent_stat( page->gtor );
1273
1274    l = page->last_scrape_time_lb;
1275    t = torStat->lastScrapeTime;
1276    refresh_time_lb( l, t );
1277
1278    l = page->last_scrape_response_lb;
1279    gtk_label_set_text( GTK_LABEL( l ), torStat->scrapeResponse );
1280
1281    l = page->next_scrape_countdown_lb;
1282    t = torStat->nextScrapeTime;
1283    refresh_countdown_lb( l, t, _( "Never" ) );
1284
1285    l = page->last_announce_time_lb;
1286    t = torStat->lastAnnounceTime;
1287    refresh_time_lb( l, t );
1288
1289    l = page->last_announce_response_lb;
1290    gtk_label_set_text( GTK_LABEL( l ), torStat->announceResponse );
1291
1292    l = page->next_announce_countdown_lb;
1293    t = torStat->nextAnnounceTime;
1294    refresh_countdown_lb( l, t, _( "Never" ) );
1295
1296    l = page->manual_announce_countdown_lb;
1297    t = torStat->manualAnnounceTime;
1298    refresh_countdown_lb( l, t, _( "Now" ) );
1299}
1300
1301/****
1302*****  DIALOG
1303****/
1304
1305static void
1306torrent_destroyed (gpointer dialog, GObject * dead_torrent UNUSED)
1307{
1308  gtk_widget_destroy (GTK_WIDGET(dialog));
1309}
1310
1311static void
1312remove_tag (gpointer tag)
1313{
1314  g_source_remove (GPOINTER_TO_UINT(tag)); /* stop the periodic refresh */
1315}
1316
1317static void
1318response_cb (GtkDialog *dialog, int response UNUSED, gpointer gtor)
1319{
1320  g_object_weak_unref (G_OBJECT(gtor), torrent_destroyed, dialog);
1321  gtk_widget_destroy (GTK_WIDGET(dialog));
1322}
1323
1324static gboolean
1325periodic_refresh (gpointer data)
1326{
1327  refresh_tracker   (g_object_get_data (G_OBJECT(data), "tracker-top"));
1328  refresh_peers     (g_object_get_data (G_OBJECT(data), "peers-top"));
1329  refresh_activity  (g_object_get_data (G_OBJECT(data), "activity-top"));
1330  refresh_options   (g_object_get_data (G_OBJECT(data), "options-top"));
1331  return TRUE;
1332}
1333
1334GtkWidget*
1335torrent_inspector_new ( GtkWindow * parent, TrTorrent * gtor )
1336{
1337  guint tag;
1338  GtkWidget *d, *n, *w;
1339  tr_torrent * tor = tr_torrent_handle (gtor);
1340  char sizeStr[64];
1341  char title[512];
1342  const tr_info * info = tr_torrent_info (gtor);
1343
1344  /* create the dialog */
1345  tr_strlsize( sizeStr, info->totalSize, sizeof(sizeStr) );
1346  /* %1$s is torrent name
1347     %2$s its file size */
1348  g_snprintf( title, sizeof(title), _( "Details for %1$s (%2$s)" ), info->name, sizeStr );
1349  d = gtk_dialog_new_with_buttons (title, parent, 0,
1350                                   GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1351                                   NULL);
1352  gtk_window_set_role (GTK_WINDOW(d), "tr-info" );
1353  g_signal_connect (d, "response", G_CALLBACK (response_cb), gtor);
1354  gtk_dialog_set_has_separator( GTK_DIALOG( d ), FALSE );
1355  gtk_container_set_border_width( GTK_CONTAINER( d ), GUI_PAD );
1356  g_object_weak_ref (G_OBJECT(gtor), torrent_destroyed, d);
1357 
1358
1359  /* add the notebook */
1360  n = gtk_notebook_new ();
1361  gtk_container_set_border_width ( GTK_CONTAINER ( n ), GUI_PAD );
1362
1363  w = activity_page_new (gtor);
1364  g_object_set_data (G_OBJECT(d), "activity-top", w);
1365  gtk_notebook_append_page (GTK_NOTEBOOK(n), w, 
1366                            gtk_label_new (_("Activity")));
1367
1368  w = peer_page_new (gtor);
1369  g_object_set_data (G_OBJECT(d), "peers-top", w);
1370  gtk_notebook_append_page (GTK_NOTEBOOK(n),  w,
1371                            gtk_label_new (_("Peers")));
1372
1373  w = tracker_page_new( gtor );
1374  g_object_set_data( G_OBJECT( d ), "tracker-top", w );
1375  gtk_notebook_append_page( GTK_NOTEBOOK( n ), w,
1376                            gtk_label_new( _( "Tracker" ) ) );
1377
1378  gtk_notebook_append_page (GTK_NOTEBOOK(n),
1379                            info_page_new (tor),
1380                            gtk_label_new (_("Information")));
1381
1382  w = file_list_new( gtor );
1383  gtk_container_set_border_width( GTK_CONTAINER( w ), GUI_PAD_BIG );
1384  g_object_set_data (G_OBJECT(d), "files-top", w);
1385  gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
1386                            gtk_label_new (_("Files")));
1387
1388  w = options_page_new (gtor);
1389  g_object_set_data (G_OBJECT(d), "options-top", w);
1390  gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
1391                            gtk_label_new (_("Options")));
1392
1393  gtk_box_pack_start_defaults (GTK_BOX(GTK_DIALOG(d)->vbox), n);
1394
1395  tag = g_timeout_add (UPDATE_INTERVAL_MSEC, periodic_refresh, d);
1396  g_object_set_data_full (G_OBJECT(d), "tag",
1397                          GUINT_TO_POINTER(tag), remove_tag);
1398
1399  /* return the results */
1400  periodic_refresh (d);
1401  gtk_widget_show_all (GTK_DIALOG(d)->vbox);
1402  return d;
1403}
Note: See TracBrowser for help on using the repository browser.