source: trunk/gtk/details.c @ 5590

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

(gtk) #860: Nobody knows what the UI DEI etc. it the Details>Peers status column mean

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