source: trunk/gtk/details.c @ 5122

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

(gtk) make the filename naming scheme a little more consistent.

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