source: trunk/gtk/torrent-inspector.c @ 2003

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

gtk: added "about" window; refreshed translation potfile; wrote a simple script so every release I don't have to re-learn how to make potfiles.

File size: 45.3 KB
Line 
1/******************************************************************************
2 * $Id:$
3 *
4 * Copyright (c) 2005-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <stddef.h>
26#include <stdlib.h>
27#include <glib/gi18n.h>
28#include <gtk/gtk.h>
29#include "transmission.h"
30#include "platform.h" /* for tr_getTorrentsDirectory */
31#include "tr_torrent.h"
32#include "dot-icons.h"
33#include "hig.h"
34#include "torrent-inspector.h"
35#include "util.h"
36
37#define UPDATE_INTERVAL_MSEC 1500
38
39/****
40*****  PIECES VIEW
41****/
42
43static int
44getGridSize (int pieceCount, int * n_rows, int * n_cols)
45{
46  const int MAX_ACROSS = 16;
47  if (pieceCount >= (MAX_ACROSS * MAX_ACROSS)) {
48    *n_rows = *n_cols = MAX_ACROSS;
49    return MAX_ACROSS * MAX_ACROSS;
50  }
51  else {
52    int i;
53    for (i=0; (i*i) < pieceCount; ++i);
54    *n_rows = *n_cols = i;
55    return pieceCount;
56  }
57}
58
59#define TO16(a) ((guint16)((a<<8)|(a)))
60#define RGB_2_GDK(R,G,B) { 0, TO16(R), TO16(G), TO16(B) }
61
62enum { DRAW_AVAIL, DRAW_PROG };
63
64static void
65release_gobject_array (gpointer data)
66{
67  int i;
68  GObject **objects = (GObject**) data;
69  for (i=0; objects[i]!=NULL; ++i)
70    g_object_unref (G_OBJECT(objects[i]));
71  g_free (objects);
72}
73
74static gboolean
75refresh_pieces (GtkWidget * da, GdkEventExpose * event UNUSED, gpointer gtor)
76{
77  tr_torrent_t * tor = tr_torrent_handle( TR_TORRENT(gtor) );
78  const tr_info_t * info = tr_torrent_info( TR_TORRENT(gtor) );
79  int mode = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(da), "draw-mode"));
80
81  GdkColormap * colormap = gtk_widget_get_colormap (da);
82  const int widget_w = da->allocation.width;
83  const int widget_h = da->allocation.height;
84  int n_rows, n_cols;
85  const int n_cells = getGridSize (info->pieceCount,  &n_rows, &n_cols);
86  const GdkRectangle grid_bounds = { 0, 0, widget_w, widget_h };
87  const double piece_w = grid_bounds.width / (double)n_cols;
88  const double piece_h = grid_bounds.height / (double)n_rows;
89  const int piece_w_int = (int) (piece_w + 1); /* pad for roundoff */
90  const int piece_h_int = (int) (piece_h + 1); /* pad for roundoff */
91  guint8 * prev_color = NULL;
92  gboolean first_time = FALSE;
93
94  int i, x, y;
95  int8_t * pieces = NULL;
96  float * completeness = NULL;
97
98  /**
99  ***  Get the Graphics Contexts...
100  **/
101
102  enum { ALL, LOTS, SOME, FEW, NONE,
103         BLACK, GRAY, BLINK,
104         N_COLORS };
105  GdkGC **gcs = (GdkGC**) g_object_get_data (G_OBJECT(da), "graphics-contexts");
106  if (gcs == NULL)
107  {
108    gcs = g_new (GdkGC*, N_COLORS+1);
109
110    const GdkColor colors [N_COLORS] = {
111      RGB_2_GDK (   0, 226, 255 ), /* all */
112      RGB_2_GDK (   0, 153, 204 ), /* lots */
113      RGB_2_GDK (   0, 102, 153 ), /* some */
114      RGB_2_GDK (   0,  51, 102 ), /* few */
115      RGB_2_GDK ( 255, 255, 255 ), /* none */
116      RGB_2_GDK (   0,   0,   0 ), /* black */
117      RGB_2_GDK ( 181, 181, 181 ), /* gray */
118      RGB_2_GDK ( 255, 164,   0 ), /* blink - orange */
119    };
120
121    for (i=0; i<N_COLORS; ++i) {
122      gcs[i] = gdk_gc_new (da->window);
123      gdk_gc_set_colormap (gcs[i], colormap);
124      gdk_gc_set_rgb_fg_color (gcs[i], &colors[i]);
125      gdk_gc_set_rgb_bg_color (gcs[i], &colors[i]);
126    };
127
128    gcs[N_COLORS] = NULL; /* a sentinel in the release function */
129    g_object_set_data_full (G_OBJECT(da), "graphics-contexts",
130                            gcs, release_gobject_array);
131  }
132
133  /**
134  ***  Get the cells' previous colors...
135  ***  (this is used for blinking when the color changes)
136  **/
137
138  prev_color = (guint8*) g_object_get_data (G_OBJECT(da), "prev-color");
139  if (prev_color == NULL)
140  {
141    first_time = TRUE;
142    prev_color = g_new0 (guint8, n_cells);
143    g_object_set_data_full (G_OBJECT(da), "prev-color", prev_color, g_free);
144  }
145
146  /**
147  ***  Get the piece data values...
148  **/
149
150  switch (mode) {
151    case DRAW_AVAIL:
152      pieces = g_new (gint8, n_cells);
153      tr_torrentAvailability ( tor, pieces, n_cells );
154      break;
155    case DRAW_PROG:
156      completeness = g_new (float, n_cells);
157      tr_torrentAmountFinished ( tor, completeness, n_cells );
158      break;
159    default: g_error("no mode defined!");
160  }
161
162  /**
163  ***  Draw...
164  **/
165
166  i = 0; 
167  for (y=0; y<n_rows; ++y) {
168    for (x=0; x<n_cols; ++x) {
169      const int draw_x = grid_bounds.x + (int)(x * piece_w);
170      const int draw_y = grid_bounds.y + (int)(y * piece_h);
171      int color = BLACK;
172      int border = BLACK;
173
174      if (i < n_cells)
175      {
176        border = GRAY;
177
178        if (mode == DRAW_AVAIL) {
179          const int8_t val = pieces[i];
180               if (val <  0) color = ALL;
181          else if (val == 0) color = NONE;
182          else if (val <= 4) color = FEW;
183          else if (val <= 8) color = SOME;
184          else               color = LOTS;
185        } else { /* completeness */
186          const float val = completeness[i];
187               if (val >= 1.00) color = ALL;
188          else if (val >= 0.66) color = LOTS;
189          else if (val >= 0.33) color = SOME;
190          else if (val >= 0.01) color = FEW;
191          else                  color = NONE;
192        }
193
194        /* draw a "blink" for one interval when a piece changes */
195        if (first_time)
196          prev_color[i] = color;
197        else if (color != prev_color[i]) {
198          prev_color[i] = color;
199          color = border = BLINK;
200        }
201      }
202
203      gdk_draw_rectangle (da->window, gcs[color], TRUE,
204                          draw_x, draw_y,
205                          piece_w_int, piece_h_int);
206
207      if (i < n_cells)
208        gdk_draw_rectangle (da->window, gcs[border], FALSE,
209                            draw_x, draw_y,
210                            piece_w_int, piece_h_int);
211
212      ++i;
213    }
214  }
215
216  gdk_draw_rectangle (da->window, gcs[GRAY], FALSE,
217                      grid_bounds.x, grid_bounds.y, 
218                      grid_bounds.width, grid_bounds.height-1);
219
220  g_free (pieces);
221  g_free (completeness);
222  return FALSE;
223}
224
225/****
226*****  PEERS TAB
227****/
228
229enum
230{
231  PEER_COL_ADDRESS,
232  PEER_COL_PORT,
233  PEER_COL_CLIENT,
234  PEER_COL_PROGRESS,
235  PEER_COL_IS_CONNECTED,
236  PEER_COL_IS_DOWNLOADING,
237  PEER_COL_DOWNLOAD_RATE,
238  PEER_COL_IS_UPLOADING,
239  PEER_COL_UPLOAD_RATE,
240  N_PEER_COLS
241};
242
243static const char* peer_column_names[N_PEER_COLS] =
244{
245  N_("Address"),
246  N_("Port"),
247  N_("Client"),
248  N_("Progress"),
249  "",
250  N_("Downloading"),
251  N_("DL Rate"),
252  N_("Uploading"),
253  N_("UL Rate")
254};
255
256static int compare_peers (const void * a, const void * b)
257{
258  const tr_peer_stat_t * pa = (const tr_peer_stat_t *) a;
259  const tr_peer_stat_t * pb = (const tr_peer_stat_t *) b;
260  return strcmp (pa->addr, pb->addr);
261}
262static int compare_addr_to_peer (const void * a, const void * b)
263{
264  const char * addr = (const char *) a;
265  const tr_peer_stat_t * peer = (const tr_peer_stat_t *) b;
266  return strcmp (addr, peer->addr);
267}
268
269static void
270peer_row_set (GtkTreeStore          * store,
271              GtkTreeIter           * iter,
272              const tr_peer_stat_t  * peer)
273{
274  const char * client = peer->client;
275
276  if (!client || !strcmp(client,"Unknown Client"))
277    client = "";
278
279  gtk_tree_store_set (store, iter,
280                      PEER_COL_ADDRESS, peer->addr,
281                      PEER_COL_PORT, peer->port,
282                      PEER_COL_CLIENT, client,
283                      PEER_COL_PROGRESS, (int)(100.0*peer->progress + 0.5),
284                      PEER_COL_IS_CONNECTED, peer->isConnected,
285                      PEER_COL_IS_DOWNLOADING, peer->isDownloading,
286                      PEER_COL_DOWNLOAD_RATE, peer->downloadFromRate,
287                      PEER_COL_IS_UPLOADING, peer->isUploading,
288                      PEER_COL_UPLOAD_RATE, peer->uploadToRate,
289                      -1);
290}
291
292static void
293append_peers_to_model (GtkTreeStore          * store,
294                       const tr_peer_stat_t  * 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_t * tor)
307{
308  GtkTreeStore * m = gtk_tree_store_new (N_PEER_COLS,
309                                         G_TYPE_STRING,  /* addr */
310                                         G_TYPE_INT,     /* port */
311                                         G_TYPE_STRING,  /* client */
312                                         G_TYPE_INT,     /* progress [0..100] */
313                                         G_TYPE_BOOLEAN, /* isConnected */
314                                         G_TYPE_BOOLEAN, /* isDownloading */
315                                         G_TYPE_FLOAT,   /* downloadFromRate */
316                                         G_TYPE_BOOLEAN, /* isUploading */
317                                         G_TYPE_FLOAT);  /* uploadToRate */
318
319  int n_peers = 0;
320  tr_peer_stat_t * peers = tr_torrentPeers (tor, &n_peers);
321  qsort (peers, n_peers, sizeof(tr_peer_stat_t), compare_peers);
322  append_peers_to_model (m, peers, n_peers);
323  tr_torrentPeersFree( peers, 0 );
324  return GTK_TREE_MODEL (m);
325}
326
327static void
328render_connection (GtkTreeViewColumn  * column UNUSED,
329                   GtkCellRenderer    * renderer,
330                   GtkTreeModel       * tree_model,
331                   GtkTreeIter        * iter,
332                   gpointer             data UNUSED)
333{
334  static GdkPixbuf * rdot = NULL;
335  static GdkPixbuf * gdot = NULL;
336  gboolean is_connected = FALSE;
337  gtk_tree_model_get (tree_model, iter, PEER_COL_IS_CONNECTED, &is_connected,
338                                        -1);
339  if (!rdot) rdot = gdk_pixbuf_new_from_inline (-1, red_dot, FALSE, NULL);
340  if (!gdot) gdot = gdk_pixbuf_new_from_inline (-1, green_dot, FALSE, NULL);
341  g_object_set (renderer, "xalign", (gfloat)0.0,
342                          "yalign", (gfloat)0.5,
343                          "pixbuf", (is_connected ? gdot : rdot),
344                          NULL);
345}
346
347static void
348render_ul_rate (GtkTreeViewColumn  * column UNUSED,
349                GtkCellRenderer    * renderer,
350                GtkTreeModel       * tree_model,
351                GtkTreeIter        * iter,
352                gpointer             data UNUSED)
353{
354  char * pch;
355  float rate = 0.0;
356  gtk_tree_model_get (tree_model, iter, PEER_COL_UPLOAD_RATE, &rate, -1);
357  pch = readablespeed (rate);
358  g_object_set (renderer, "text", pch, NULL);
359  g_free (pch); 
360}
361
362static void
363render_dl_rate (GtkTreeViewColumn  * column UNUSED,
364                GtkCellRenderer    * renderer,
365                GtkTreeModel       * tree_model,
366                GtkTreeIter        * iter,
367                gpointer             data UNUSED)
368{
369  char * pch;
370  float rate = 0.0;
371  gtk_tree_model_get (tree_model, iter, PEER_COL_DOWNLOAD_RATE, &rate, -1);
372  pch = readablespeed (rate);
373  g_object_set (renderer, "text", pch, NULL);
374  g_free (pch); 
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  gboolean is_connected = FALSE;
385  char * client = NULL;
386  gtk_tree_model_get (tree_model, iter, PEER_COL_IS_CONNECTED,  &is_connected,
387                                        PEER_COL_CLIENT, &client,
388                                        -1);
389  if (!is_connected)
390    *client = '\0';
391  g_object_set (renderer, "text", client, NULL);
392  g_free (client);
393}
394
395typedef struct
396{
397  TrTorrent * gtor;
398  GtkTreeModel * model; /* same object as store, but recast */
399  GtkTreeStore * store; /* same object as model, but recast */
400  GtkWidget * completeness;
401  GtkWidget * seeders_lb;
402  GtkWidget * leechers_lb;
403  GtkWidget * completed_lb;
404}
405PeerData;
406
407static void
408fmtpeercount (GtkWidget * l, int count)
409{
410  if( 0 > count ) {
411    gtk_label_set_text( GTK_LABEL(l), _("?") );
412  } else {
413    char str[16];
414    snprintf( str, sizeof str, "%i", count );
415    gtk_label_set_text( GTK_LABEL(l), str );
416  }
417}
418
419static void
420refresh_peers (GtkWidget * top)
421{
422  int i;
423  int n_peers;
424  GtkTreeIter iter;
425  PeerData * p = (PeerData*) g_object_get_data (G_OBJECT(top), "peer-data");
426  tr_torrent_t * tor = tr_torrent_handle ( p->gtor );
427  GtkTreeModel * model = p->model;
428  GtkTreeStore * store = p->store;
429  tr_peer_stat_t * peers;
430  const tr_stat_t * stat = tr_torrent_stat( p->gtor );
431
432  /**
433  ***  merge the peer diffs into the tree model.
434  ***
435  ***  this is more complicated than creating a new model,
436  ***  but is also (a) more efficient and (b) doesn't undo
437  ***  the view's visible area and sorting on every refresh.
438  **/
439
440  n_peers = 0;
441  peers = tr_torrentPeers (tor, &n_peers);
442  qsort (peers, n_peers, sizeof(tr_peer_stat_t), compare_peers);
443
444  i = 0;
445  if (gtk_tree_model_get_iter_first (model, &iter)) do
446  {
447    char * addr = NULL;
448    tr_peer_stat_t * peer = NULL;
449    gtk_tree_model_get (model, &iter, PEER_COL_ADDRESS, &addr, -1);
450    peer = bsearch (addr, peers, n_peers, sizeof(tr_peer_stat_t),
451                    compare_addr_to_peer);
452    g_free (addr);
453
454    if (peer) /* update a pre-existing row */
455    {
456      const int pos = peer - peers;
457      const int n_rhs = n_peers - (pos+1);
458      g_assert (n_rhs >= 0);
459
460      peer_row_set (store, &iter, peer);
461
462      /* remove it from the tr_peer_stat_t list */
463      g_memmove (peer, peer+1, sizeof(tr_peer_stat_t)*n_rhs);
464      --n_peers;
465    }
466    else if (!gtk_tree_store_remove (store, &iter))
467      break; /* we removed the model's last item */
468  }
469  while (gtk_tree_model_iter_next (model, &iter));
470
471  append_peers_to_model (store, peers, n_peers);  /* all these are new */
472
473  if (GDK_IS_DRAWABLE (p->completeness->window))
474    refresh_pieces (p->completeness, NULL, p->gtor);
475
476  fmtpeercount (p->seeders_lb, stat->seeders);
477  fmtpeercount (p->leechers_lb, stat->leechers);
478  fmtpeercount (p->completed_lb, stat->completedFromTracker );
479
480  free (peers);
481}
482
483static GtkWidget* peer_page_new ( TrTorrent * gtor )
484{
485  guint i;
486  GtkTreeModel *m;
487  GtkWidget *h, *v, *w, *ret, *da, *sw, *l, *vbox, *hbox;
488  tr_torrent_t * tor = tr_torrent_handle (gtor);
489  PeerData * p = g_new (PeerData, 1);
490  char name[64];
491
492  /* TODO: make this configurable? */
493  int view_columns[] = { PEER_COL_IS_CONNECTED,
494                         PEER_COL_ADDRESS,
495                         PEER_COL_CLIENT,
496                         PEER_COL_PROGRESS,
497                         PEER_COL_UPLOAD_RATE,
498                         PEER_COL_DOWNLOAD_RATE };
499
500  m  = peer_model_new (tor);
501  v = gtk_tree_view_new_with_model (m);
502  gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(v), TRUE);
503  g_object_unref (G_OBJECT(m));
504
505  for (i=0; i<G_N_ELEMENTS(view_columns); ++i)
506  {
507    const int col = view_columns[i];
508    const char * t = _(peer_column_names[col]);
509    gboolean resizable = TRUE;
510    GtkTreeViewColumn * c;
511    GtkCellRenderer * r;
512
513    switch (col)
514    {
515      case PEER_COL_ADDRESS:
516        r = gtk_cell_renderer_text_new ();
517        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
518        break;
519
520      case PEER_COL_PORT:
521        r = gtk_cell_renderer_text_new ();
522        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
523        break;
524
525      case PEER_COL_CLIENT:
526        r = gtk_cell_renderer_text_new ();
527        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
528        gtk_tree_view_column_set_cell_data_func (c, r, render_client,
529                                                 NULL, NULL);
530        break;
531
532      case PEER_COL_PROGRESS:
533        r = gtk_cell_renderer_progress_new ();
534        c = gtk_tree_view_column_new_with_attributes (
535              _("Progress"), r, "value", PEER_COL_PROGRESS, NULL);
536        break;
537
538      case PEER_COL_IS_CONNECTED:
539        resizable = FALSE;
540        r = gtk_cell_renderer_pixbuf_new ();
541        c = gtk_tree_view_column_new_with_attributes (t, r, NULL);
542        gtk_tree_view_column_set_sizing (c, GTK_TREE_VIEW_COLUMN_FIXED);
543        gtk_tree_view_column_set_fixed_width (c, 32);
544        gtk_tree_view_column_set_cell_data_func (c, r, render_connection,
545                                                 NULL, NULL);
546        break;
547
548      case PEER_COL_IS_DOWNLOADING:
549        r = gtk_cell_renderer_text_new ();
550        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
551        break;
552
553      case PEER_COL_DOWNLOAD_RATE:
554        r = gtk_cell_renderer_text_new ();
555        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
556        gtk_tree_view_column_set_cell_data_func (c, r, render_dl_rate,
557                                                 NULL, NULL);
558        break;
559
560      case PEER_COL_IS_UPLOADING:
561        r = gtk_cell_renderer_text_new ();
562        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
563        break;
564
565      case PEER_COL_UPLOAD_RATE:
566        r = gtk_cell_renderer_text_new ();
567        c = gtk_tree_view_column_new_with_attributes (t, r, "text", col, NULL);
568        gtk_tree_view_column_set_cell_data_func (c, r, render_ul_rate,
569                                                 NULL, NULL);
570        break;
571
572      default:
573        abort ();
574    }
575
576    gtk_tree_view_column_set_resizable (c, resizable);
577    gtk_tree_view_column_set_sort_column_id (c, col);
578    gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
579  }
580
581  /* the 'expander' column has a 10-pixel margin on the left
582     that doesn't look quite correct in any of these columns...
583     so create a non-visible column and assign it as the
584     'expander column. */
585  {
586    GtkTreeViewColumn *c = gtk_tree_view_column_new ();
587    gtk_tree_view_column_set_visible (c, FALSE);
588    gtk_tree_view_append_column (GTK_TREE_VIEW(v), c);
589    gtk_tree_view_set_expander_column (GTK_TREE_VIEW(v), c);
590  }
591
592  w = sw = gtk_scrolled_window_new (NULL, NULL);
593  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(w),
594                                  GTK_POLICY_NEVER,
595                                  GTK_POLICY_AUTOMATIC);
596  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(w),
597                                       GTK_SHADOW_IN);
598  gtk_container_add (GTK_CONTAINER(w), v);
599  gtk_widget_set_usize (w, 500u, 0u);
600
601
602  vbox = gtk_vbox_new (FALSE, GUI_PAD);
603  gtk_container_set_border_width (GTK_CONTAINER(vbox), GUI_PAD_BIG);
604
605    g_snprintf (name, sizeof(name), "<b>%s</b>", _("Piece Availability"));
606    l = gtk_label_new (NULL);
607    gtk_misc_set_alignment (GTK_MISC(l), 0.0f, 0.5f);
608    gtk_label_set_markup (GTK_LABEL(l), name);
609    gtk_box_pack_start (GTK_BOX(vbox), l, FALSE, FALSE, 0);
610
611    w = da = p->completeness = gtk_drawing_area_new ();
612    gtk_widget_set_usize (w, 0u, 100u);
613    g_object_set_data (G_OBJECT(w), "draw-mode", GINT_TO_POINTER(DRAW_AVAIL));
614    g_signal_connect (w, "expose-event", G_CALLBACK(refresh_pieces), gtor);
615
616    h = gtk_hbox_new (FALSE, GUI_PAD);
617    w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
618    gtk_widget_set_usize (w, GUI_PAD_BIG, 0);
619    gtk_box_pack_start (GTK_BOX(h), w, FALSE, FALSE, 0);
620    gtk_box_pack_start_defaults (GTK_BOX(h), da);
621    gtk_box_pack_start (GTK_BOX(vbox), h, FALSE, FALSE, 0);
622
623    /* a small vertical spacer */
624    w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
625    gtk_widget_set_usize (w, 0u, GUI_PAD);
626    gtk_box_pack_start (GTK_BOX(vbox), w, FALSE, FALSE, 0);
627
628    g_snprintf (name, sizeof(name), "<b>%s</b>", _("Peers"));
629    l = gtk_label_new (NULL);
630    gtk_misc_set_alignment (GTK_MISC(l), 0.0f, 0.5f);
631    gtk_label_set_markup (GTK_LABEL(l), name);
632    gtk_box_pack_start (GTK_BOX(vbox), l, FALSE, FALSE, 0);
633
634    h = gtk_hbox_new (FALSE, GUI_PAD);
635    w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
636    gtk_widget_set_usize (w, GUI_PAD_BIG, 0);
637    gtk_box_pack_start (GTK_BOX(h), w, FALSE, FALSE, 0);
638    gtk_box_pack_start_defaults (GTK_BOX(h), sw);
639    gtk_box_pack_start_defaults (GTK_BOX(vbox), h);
640
641    hbox = gtk_hbox_new (FALSE, GUI_PAD);
642    w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
643    gtk_widget_set_usize (w, GUI_PAD_BIG, 0);
644    gtk_box_pack_start (GTK_BOX(hbox), w, FALSE, FALSE, 0);
645        g_snprintf (name, sizeof(name), "<b>%s:</b>", _("Seeders"));
646        l = gtk_label_new (NULL);
647        gtk_label_set_markup (GTK_LABEL(l), name);
648        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
649        l = p->seeders_lb = gtk_label_new (NULL);
650        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
651    gtk_box_pack_start_defaults (GTK_BOX(hbox),
652                                 gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
653        g_snprintf (name, sizeof(name), "<b>%s:</b>", _("Leechers"));
654        l = gtk_label_new (NULL);
655        gtk_label_set_markup (GTK_LABEL(l), name);
656        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
657        l = p->leechers_lb = gtk_label_new (NULL);
658        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
659    gtk_box_pack_start_defaults (GTK_BOX(hbox),
660                                 gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f));
661        g_snprintf (name, sizeof(name), "<b>%s:</b>", _("Completed"));
662        l = gtk_label_new (NULL);
663        gtk_label_set_markup (GTK_LABEL(l), name);
664        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
665        l = p->completed_lb = gtk_label_new (NULL);
666        gtk_box_pack_start (GTK_BOX(hbox), l, FALSE, FALSE, 0);
667    gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
668
669  ret = vbox;
670  p->gtor = gtor;
671  p->model = m;
672  p->store = GTK_TREE_STORE(m);
673  g_object_set_data_full (G_OBJECT(ret), "peer-data", p, g_free);
674  return ret;
675}
676
677/****
678*****  INFO TAB
679****/
680
681static GtkWidget* info_page_new (tr_torrent_t * tor)
682{
683  int row = 0;
684  GtkWidget *t = hig_workarea_create ();
685  GtkWidget *l, *w, *fr;
686  char *pch;
687  char *dname, *bname;
688  const char * default_torrents_dir;
689  char buf[256];
690  char name[128];
691  const char * namefmt = "%s:";
692  GtkTextBuffer * b;
693  tr_tracker_info_t * track;
694  const tr_info_t* info = tr_torrentInfo(tor);
695
696  hig_workarea_add_section_title (t, &row, _("Torrent Information"));
697  hig_workarea_add_section_spacer (t, row, 5);
698
699    g_snprintf (name, sizeof(name), namefmt, _("Tracker"));
700    track = info->trackerList->list;
701    pch = track->port==80
702      ? g_strdup_printf ("http://%s%s", track->address, track->announce)
703      : g_strdup_printf ("http://%s:%d%s", track->address, track->port, track->announce);
704    l = gtk_label_new (pch);
705    hig_workarea_add_row (t, &row, name, l, NULL);
706    g_free (pch);
707
708    g_snprintf (name, sizeof(name), namefmt, _("Pieces"));
709    pch = readablesize (info->pieceSize);
710    g_snprintf (buf, sizeof(buf), "%d (%s)", info->pieceCount, pch);
711    l = gtk_label_new (buf);
712    hig_workarea_add_row (t, &row, name, l, NULL);
713    g_free (pch);
714
715    g_snprintf (name, sizeof(name), namefmt, _("Hash"));
716    l = gtk_label_new (info->hashString);
717    hig_workarea_add_row (t, &row, name, l, NULL);
718
719    g_snprintf (name, sizeof(name), namefmt, _("Secure"));
720    pch = (info->flags & TR_FLAG_PRIVATE)
721      ? _("Private Torrent, PEX disabled")
722      : _("Public Torrent");
723    l = gtk_label_new (pch);
724    hig_workarea_add_row (t, &row, name, l, NULL);
725
726    g_snprintf (name, sizeof(name), namefmt, _("Comment"));
727    b = gtk_text_buffer_new (NULL);
728    gtk_text_buffer_set_text (b, info->comment, -1);
729    w = gtk_text_view_new_with_buffer (b);
730    gtk_widget_set_size_request (w, 0u, 100u);
731    gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW(w), GTK_WRAP_WORD); 
732    gtk_text_view_set_editable (GTK_TEXT_VIEW(w), FALSE);
733    fr = gtk_frame_new (NULL);
734    gtk_frame_set_shadow_type (GTK_FRAME(fr), GTK_SHADOW_IN);
735    gtk_container_add (GTK_CONTAINER(fr), w);
736    hig_workarea_add_row (t, &row, name, fr, NULL);
737
738  hig_workarea_add_section_divider (t, &row);
739  hig_workarea_add_section_title (t, &row, _("Created By"));
740  hig_workarea_add_section_spacer (t, row, 2);
741 
742    g_snprintf (name, sizeof(name), namefmt, _("Creator"));
743    l = gtk_label_new (*info->creator ? info->creator : _("N/A"));
744    hig_workarea_add_row (t, &row, name, l, NULL);
745
746    g_snprintf (name, sizeof(name), namefmt, _("Date"));
747    pch = rfc822date ((guint64)info->dateCreated * 1000u);
748    l = gtk_label_new (pch);
749    hig_workarea_add_row (t, &row, name, l, NULL); 
750    g_free (pch);
751
752  hig_workarea_add_section_divider (t, &row);
753  hig_workarea_add_section_title (t, &row, _("Location"));
754  hig_workarea_add_section_spacer (t, row, 3);
755
756    g_snprintf (name, sizeof(name), namefmt, _("Downloaded Data"));
757    l = gtk_label_new (tr_torrentGetFolder (tor));
758    hig_workarea_add_row (t, &row, name, l, NULL); 
759
760    g_snprintf (name, sizeof(name), namefmt, _("Torrent File Path"));
761    default_torrents_dir = tr_getTorrentsDirectory();
762    dname = g_path_get_dirname (info->torrent);
763    l = gtk_label_new (strstr(dname,default_torrents_dir)==dname
764      ? _("Transmission Support Folder")
765      : dname);
766    hig_workarea_add_row (t, &row, name, l, NULL); 
767    g_free (dname);
768
769    g_snprintf (name, sizeof(name), namefmt, _("Torrent File Name"));
770    bname = g_path_get_basename (info->torrent);
771    l = gtk_label_new (bname);
772    hig_workarea_add_row (t, &row, name, l, NULL); 
773    g_free (bname);
774
775  hig_workarea_finish (t, &row);
776  return t;
777}
778
779/****
780*****  ACTIVITY TAB
781****/
782
783typedef struct
784{
785  GtkWidget * state_lb;
786  GtkWidget * valid_dl_lb;
787  GtkWidget * dl_lb;
788  GtkWidget * ul_lb;
789  GtkWidget * ratio_lb;
790  GtkWidget * err_lb;
791  GtkWidget * remaining_lb;
792  GtkWidget * swarm_lb;
793  GtkWidget * date_added_lb;
794  GtkWidget * last_activity_lb;
795  GtkWidget * availability_da;
796  TrTorrent * gtor;
797}
798Activity;
799
800static void
801refresh_activity (GtkWidget * top)
802{
803  Activity * a = (Activity*) g_object_get_data (G_OBJECT(top), "activity-data");
804  const tr_stat_t * stat = tr_torrent_stat( a->gtor );
805  const tr_info_t * info = tr_torrent_info( a->gtor );
806  guint64 size;
807  char *pch;
808
809  pch = tr_torrent_status_str( a->gtor );
810  gtk_label_set_text (GTK_LABEL(a->state_lb), pch);
811  g_free (pch);
812
813  size = info->totalSize - stat->left;
814  pch = readablesize (size);
815  gtk_label_set_text (GTK_LABEL(a->valid_dl_lb), pch);
816  g_free (pch);
817
818  pch = readablesize (stat->downloaded);
819  gtk_label_set_text (GTK_LABEL(a->dl_lb), pch);
820  g_free (pch);
821
822  pch = readablesize (stat->uploaded);
823  gtk_label_set_text (GTK_LABEL(a->ul_lb), pch);
824  g_free (pch);
825
826  pch = ratiostr (stat->downloaded, stat->uploaded);
827  gtk_label_set_text (GTK_LABEL(a->ratio_lb), pch);
828  g_free (pch);
829
830  pch = readablespeed (stat->swarmspeed);
831  gtk_label_set_text (GTK_LABEL(a->swarm_lb), pch);
832  g_free (pch);
833
834  pch = readablesize (stat->left);
835  gtk_label_set_text (GTK_LABEL(a->remaining_lb), pch);
836  g_free (pch);
837
838  gtk_label_set_text (GTK_LABEL(a->err_lb),
839                      *stat->errorString ? stat->errorString : _("None"));
840
841  pch = stat->startDate ? rfc822date (stat->startDate)
842                        : g_strdup_printf (_("?"));
843  gtk_label_set_text (GTK_LABEL(a->date_added_lb), pch);
844  g_free (pch);
845
846  pch = stat->activityDate ? rfc822date (stat->activityDate)
847                           : g_strdup_printf (_("?"));
848  gtk_label_set_text (GTK_LABEL(a->last_activity_lb), pch);
849  g_free (pch);
850
851  if (GDK_IS_DRAWABLE (a->availability_da->window))
852    refresh_pieces (a->availability_da, NULL, a->gtor);
853}
854 
855
856static GtkWidget*
857activity_page_new (TrTorrent * gtor)
858{
859  Activity * a = g_new (Activity, 1);
860  int row = 0;
861  GtkWidget *t = hig_workarea_create ();
862  GtkWidget *l, *w;
863  char name[128];
864  const char * namefmt = "%s:";
865
866  a->gtor = gtor;
867
868  hig_workarea_add_section_title (t, &row, _("Transfer"));
869  hig_workarea_add_section_spacer (t, row, 8);
870
871    g_snprintf (name, sizeof(name), namefmt, _("State"));
872    l = a->state_lb = gtk_label_new (NULL);
873    hig_workarea_add_row (t, &row, name, l, NULL);
874
875    g_snprintf (name, sizeof(name), namefmt, _("Valid DL"));
876    l = a->valid_dl_lb = gtk_label_new (NULL);
877    hig_workarea_add_row (t, &row, name, l, NULL);
878
879    g_snprintf (name, sizeof(name), namefmt, _("Downloaded"));
880    l = a->dl_lb = gtk_label_new (NULL);
881    hig_workarea_add_row (t, &row, name, l, NULL);
882
883    g_snprintf (name, sizeof(name), namefmt, _("Uploaded"));
884    l = a->ul_lb = gtk_label_new (NULL);
885    hig_workarea_add_row (t, &row, name, l, NULL);
886
887    g_snprintf (name, sizeof(name), namefmt, _("Ratio"));
888    l = a->ratio_lb = gtk_label_new (NULL);
889    hig_workarea_add_row (t, &row, name, l, NULL);
890
891    g_snprintf (name, sizeof(name), namefmt, _("Remaining"));
892    l = a->remaining_lb = gtk_label_new (NULL);
893    hig_workarea_add_row (t, &row, name, l, NULL);
894
895    g_snprintf (name, sizeof(name), namefmt, _("Swarm Rate"));
896    l = a->swarm_lb = gtk_label_new (NULL);
897    hig_workarea_add_row (t, &row, name, l, NULL);
898
899    g_snprintf (name, sizeof(name), namefmt, _("Error"));
900    l = a->err_lb = gtk_label_new (NULL);
901    hig_workarea_add_row (t, &row, name, l, NULL);
902
903    g_snprintf (name, sizeof(name), namefmt, _("Completeness"));
904    w = a->availability_da = gtk_drawing_area_new ();
905    gtk_widget_set_usize (w, 0u, 100u);
906    g_object_set_data (G_OBJECT(w), "draw-mode", GINT_TO_POINTER(DRAW_PROG));
907    g_signal_connect (w, "expose-event", G_CALLBACK(refresh_pieces), gtor);
908    hig_workarea_add_row (t, &row, name, w, NULL);
909
910  hig_workarea_add_section_divider (t, &row);
911  hig_workarea_add_section_title (t, &row, _("Dates"));
912  hig_workarea_add_section_spacer (t, row, 3);
913
914    g_snprintf (name, sizeof(name), namefmt, _("Added"));
915    l = a->date_added_lb = gtk_label_new (NULL);
916    hig_workarea_add_row (t, &row, name, l, NULL);
917
918    g_snprintf (name, sizeof(name), namefmt, _("Last Activity"));
919    l = a->last_activity_lb = gtk_label_new (NULL);
920    hig_workarea_add_row (t, &row, name, l, NULL);
921
922  hig_workarea_add_section_divider (t, &row);
923  hig_workarea_finish (t, &row);
924  g_object_set_data_full (G_OBJECT(t), "activity-data", a, g_free);
925  return t;
926}
927
928/****
929*****  FILES TAB
930****/
931
932#define STRIPROOT( path )                                                     \
933    ( g_path_is_absolute( (path) ) ? g_path_skip_root( (path) ) : (path) )
934
935enum
936{
937  FC_STOCK,
938  FC_LABEL,
939  FC_PROG,
940  FC_KEY,
941  FC_INDEX,
942  FC_SIZE,
943  N_FILE_COLS
944};
945
946typedef struct
947{
948  TrTorrent * gtor;
949  GtkTreeModel * model; /* same object as store, but recast */
950  GtkTreeStore * store; /* same object as model, but recast */
951}
952FileData;
953
954static void
955parsepath( GtkTreeStore  * store,
956           GtkTreeIter   * ret,
957           const char    * path,
958           int             index,
959           uint64_t        size )
960{
961    GtkTreeModel * model;
962    GtkTreeIter  * parent, start, iter;
963    char         * file, * lower, * mykey, *escaped=0;
964    const char   * stock;
965
966    model  = GTK_TREE_MODEL( store );
967    parent = NULL;
968    file   = g_path_get_basename( path );
969    if( 0 != strcmp( file, path ) )
970    {
971        char * dir = g_path_get_dirname( path );
972        parsepath( store, &start, dir, index, size );
973        parent = &start;
974        g_free( dir );
975    }
976
977    lower = g_utf8_casefold( file, -1 );
978    mykey = g_utf8_collate_key( lower, -1 );
979    if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
980    {
981        gboolean stop;
982        char * modelkey;
983        gtk_tree_model_get( model, &iter, FC_KEY, &modelkey, -1 );
984        stop = (modelkey!=NULL) && !strcmp(mykey,modelkey);
985        g_free (modelkey);
986        if (stop) goto done;
987    }
988    while( gtk_tree_model_iter_next( model, &iter ) );
989
990    gtk_tree_store_append( store, &iter, parent );
991    if( NULL == ret )
992    {
993        stock = GTK_STOCK_FILE;
994    }
995    else
996    {
997        stock = GTK_STOCK_DIRECTORY;
998        size  = 0;
999        index = -1;
1000    }
1001
1002    escaped = g_markup_escape_text (file, -1); 
1003    gtk_tree_store_set( store, &iter, FC_INDEX, index, FC_LABEL, escaped,
1004                        FC_KEY, mykey, FC_STOCK, stock, FC_SIZE, size, -1 );
1005  done:
1006    g_free( escaped );
1007    g_free( mykey );
1008    g_free( lower );
1009    g_free( file );
1010    if( NULL != ret )
1011      *ret = iter;
1012}
1013
1014static uint64_t
1015getdirtotals( GtkTreeStore * store, GtkTreeIter * parent )
1016{
1017    GtkTreeModel * model;
1018    GtkTreeIter    iter;
1019    uint64_t       mysize, subsize;
1020    char         * sizestr, * name, * label;
1021
1022    model  = GTK_TREE_MODEL( store );
1023    mysize = 0;
1024    if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
1025    {
1026        if( gtk_tree_model_iter_has_child( model, &iter ) )
1027        {
1028            subsize = getdirtotals( store, &iter );
1029            gtk_tree_store_set( store, &iter, FC_SIZE, subsize, -1 );
1030        }
1031        else
1032        {
1033            gtk_tree_model_get( model, &iter, FC_SIZE, &subsize, -1 );
1034        }
1035        gtk_tree_model_get( model, &iter, FC_LABEL, &name, -1 );
1036        sizestr = readablesize( subsize );
1037        label = g_markup_printf_escaped( "<small>%s (%s)</small>",
1038                                          name, sizestr );
1039        g_free( sizestr );
1040        g_free( name );
1041        gtk_tree_store_set( store, &iter, FC_LABEL, label, -1 );
1042        g_free( label );
1043        mysize += subsize;
1044    }
1045    while( gtk_tree_model_iter_next( model, &iter ) );
1046
1047    return mysize;
1048}
1049
1050static void
1051updateprogress( GtkTreeModel * model,
1052                GtkTreeStore * store,
1053                GtkTreeIter  * parent,
1054                const float  * progress,
1055                guint64      * setmeGotSize,
1056                guint64      * setmeTotalSize)
1057{
1058    GtkTreeIter iter;
1059    guint64 gotSize=0, totalSize=0;
1060
1061    if( gtk_tree_model_iter_children( model, &iter, parent ) ) do
1062    {
1063        guint64 subGot, subTotal;
1064
1065        if (gtk_tree_model_iter_has_child( model, &iter ) )
1066        {
1067            updateprogress( model, store, &iter, progress, &subGot, &subTotal);
1068        }
1069        else
1070        {
1071            int index, percent;
1072            gtk_tree_model_get( model, &iter, FC_SIZE, &subTotal,
1073                                              FC_INDEX, &index,
1074                                              -1 );
1075            g_assert( 0 <= index );
1076            percent = (int)(progress[index]*100.0 + 0.5); /* [0...100] */
1077            subGot = (guint64)(subTotal * percent/100.0);
1078        }
1079
1080        if (!subTotal) subTotal = 1; /* avoid div by zero */
1081        g_assert (subGot <= subTotal);
1082        gtk_tree_store_set (store, &iter,
1083                            FC_PROG, (int)(100.0*subGot/subTotal + 0.5), -1);
1084
1085        gotSize += subGot;
1086        totalSize += subTotal;
1087    }
1088    while( gtk_tree_model_iter_next( model, &iter ) );
1089
1090    *setmeGotSize = gotSize;
1091    *setmeTotalSize = totalSize;
1092}
1093
1094GtkWidget *
1095file_page_new ( TrTorrent * gtor )
1096{
1097    GtkWidget * ret;
1098    FileData * data;
1099    tr_info_t         * inf;
1100    GtkTreeStore      * store;
1101    int                 ii;
1102    GtkWidget         * view, * scroll;
1103    GtkCellRenderer   * rend;
1104    GtkTreeViewColumn * col;
1105    GtkTreeSelection  * sel;
1106
1107    store = gtk_tree_store_new ( N_FILE_COLS,
1108                                 G_TYPE_STRING,  /* stock */
1109                                 G_TYPE_STRING,  /* label */
1110                                 G_TYPE_INT,     /* prog [0..100] */
1111                                 G_TYPE_STRING,  /* key */
1112                                 G_TYPE_INT,     /* index */
1113                                 G_TYPE_UINT64); /* size */
1114
1115    /* set up the model */
1116    inf = tr_torrent_info( gtor );
1117    for( ii = 0; ii < inf->fileCount; ii++ )
1118    {
1119        parsepath( store, NULL, STRIPROOT( inf->files[ii].name ),
1120                   ii, inf->files[ii].length );
1121    }
1122    getdirtotals( store, NULL );
1123    gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( store ),
1124                                          FC_KEY, GTK_SORT_ASCENDING );
1125
1126    /* create the view */
1127    view = gtk_tree_view_new_with_model( GTK_TREE_MODEL( store ) );
1128    /* add file column */
1129   
1130    col = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
1131        "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
1132        "expand", TRUE,
1133        "title", _("File"),
1134        NULL));
1135    rend = gtk_cell_renderer_pixbuf_new();
1136    gtk_tree_view_column_pack_start( col, rend, FALSE );
1137    gtk_tree_view_column_add_attribute( col, rend, "stock-id", FC_STOCK );
1138    /* add text renderer */
1139    rend = gtk_cell_renderer_text_new();
1140    g_object_set( rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
1141    gtk_tree_view_column_pack_start( col, rend, TRUE );
1142    gtk_tree_view_column_add_attribute( col, rend, "markup", FC_LABEL );
1143    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
1144    /* add progress column */
1145    rend = gtk_cell_renderer_progress_new();
1146    col = gtk_tree_view_column_new_with_attributes (
1147      _("Progress"), rend, "value", FC_PROG, NULL);
1148    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
1149    /* set up view */
1150    sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
1151    gtk_tree_view_expand_all( GTK_TREE_VIEW( view ) );
1152    gtk_tree_view_set_search_column( GTK_TREE_VIEW( view ), FC_LABEL );
1153
1154    /* create the scrolled window and stick the view in it */
1155    scroll = gtk_scrolled_window_new( NULL, NULL );
1156    gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW( scroll ),
1157                                    GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC );
1158    gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(scroll),
1159                                         GTK_SHADOW_IN);
1160    gtk_container_add( GTK_CONTAINER( scroll ), view );
1161    gtk_widget_set_usize (scroll, 0u, 200u);
1162    gtk_container_set_border_width (GTK_CONTAINER(scroll), GUI_PAD);
1163
1164    ret = scroll;
1165    data = g_new (FileData, 1);
1166    data->gtor = gtor;
1167    data->model = GTK_TREE_MODEL(store);
1168    data->store = store;
1169    g_object_set_data_full (G_OBJECT(ret), "file-data", data, g_free);
1170    return ret;
1171}
1172
1173static void
1174refresh_files (GtkWidget * top)
1175{
1176    guint64 foo, bar;
1177    FileData * data = (FileData*) g_object_get_data (G_OBJECT(top), "file-data");
1178    tr_torrent_t * tor = tr_torrent_handle( data->gtor );
1179    float * progress = tr_torrentCompletion ( tor );
1180    updateprogress (data->model, data->store, NULL, progress, &foo, &bar);
1181    free( progress );
1182}
1183
1184
1185/****
1186*****  OPTIONS
1187****/
1188
1189static void
1190ul_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
1191{
1192  tr_torrent_set_upload_cap_enabled (TR_TORRENT(gtor), 
1193                                     gtk_toggle_button_get_active(tb));
1194}
1195
1196static void
1197dl_speed_toggled_cb (GtkToggleButton *tb, gpointer gtor)
1198{
1199  tr_torrent_set_download_cap_enabled (TR_TORRENT(gtor), 
1200                                       gtk_toggle_button_get_active(tb));
1201}
1202
1203static void
1204seeding_cap_toggled_cb (GtkToggleButton *tb, gpointer gtor)
1205{
1206  tr_torrent_set_seeding_cap_enabled (TR_TORRENT(gtor), 
1207                                      gtk_toggle_button_get_active(tb));
1208}
1209
1210static void
1211sensitize_from_check_cb (GtkToggleButton *toggle, gpointer w)
1212{
1213  gtk_widget_set_sensitive (GTK_WIDGET(w),
1214                            gtk_toggle_button_get_active(toggle));
1215}
1216
1217static void
1218ul_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
1219{
1220  tr_torrent_set_upload_cap_speed (TR_TORRENT(gtor), 
1221                                   gtk_spin_button_get_value_as_int (spin));
1222}
1223
1224static void
1225dl_speed_spun_cb (GtkSpinButton *spin, gpointer gtor)
1226{
1227  tr_torrent_set_download_cap_speed (TR_TORRENT(gtor), 
1228                                     gtk_spin_button_get_value_as_int (spin));
1229}
1230
1231static void
1232seeding_ratio_spun_cb (GtkSpinButton *spin, gpointer gtor)
1233{
1234  tr_torrent_set_seeding_cap_ratio (TR_TORRENT(gtor),
1235                                    gtk_spin_button_get_value(spin));
1236}
1237
1238GtkWidget*
1239options_page_new ( TrTorrent * gtor )
1240{
1241  int row;
1242  GtkAdjustment *a;
1243  GtkWidget *t, *w, *tb;
1244
1245  row = 0;
1246  t = hig_workarea_create ();
1247  hig_workarea_add_section_title (t, &row, _("Transfer Bandwidth"));
1248  hig_workarea_add_section_spacer (t, row, 2);
1249
1250    tb = gtk_check_button_new_with_mnemonic (_("Limit _Download Speed (KiB/s):"));
1251    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), gtor->dl_cap_enabled);
1252    g_signal_connect (tb, "toggled", G_CALLBACK(dl_speed_toggled_cb), gtor);
1253    a = (GtkAdjustment*) gtk_adjustment_new (gtor->dl_cap, 0.0, G_MAXDOUBLE, 1, 1, 1);
1254    w = gtk_spin_button_new (a, 1, 0);
1255    g_signal_connect (w, "value-changed", G_CALLBACK(dl_speed_spun_cb), gtor);
1256    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1257    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1258    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1259
1260    tb = gtk_check_button_new_with_mnemonic (_("Limit _Upload Speed (KiB/s):"));
1261    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), gtor->ul_cap_enabled);
1262    g_signal_connect (tb, "toggled", G_CALLBACK(ul_speed_toggled_cb), gtor);
1263    a = (GtkAdjustment*) gtk_adjustment_new (gtor->ul_cap, 0.0, G_MAXDOUBLE, 1, 1, 1);
1264    w = gtk_spin_button_new (a, 1, 0);
1265    g_signal_connect (w, "value-changed", G_CALLBACK(ul_speed_spun_cb), gtor);
1266    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1267    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1268    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1269
1270  hig_workarea_add_section_divider (t, &row);
1271  hig_workarea_add_section_title (t, &row, _("Seeding"));
1272  hig_workarea_add_section_spacer (t, row, 1);
1273
1274    tb = gtk_check_button_new_with_mnemonic (_("Stop Seeding at Ratio:"));
1275    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(tb), gtor->seeding_cap_enabled);
1276    g_signal_connect (tb, "toggled", G_CALLBACK(seeding_cap_toggled_cb), gtor);
1277    a = (GtkAdjustment*) gtk_adjustment_new (gtor->seeding_cap, 0.0, G_MAXDOUBLE, 1, 1, 1);
1278    w = gtk_spin_button_new (a, 1, 1);
1279    g_signal_connect (w, "value-changed", G_CALLBACK(seeding_ratio_spun_cb), gtor);
1280    g_signal_connect (tb, "toggled", G_CALLBACK(sensitize_from_check_cb), w);
1281    sensitize_from_check_cb (GTK_TOGGLE_BUTTON(tb), w);
1282    hig_workarea_add_row_w (t, &row, tb, w, NULL);
1283
1284  hig_workarea_finish (t, &row);
1285  return t;
1286}
1287
1288static void
1289refresh_options (GtkWidget * top UNUSED)
1290{
1291}
1292
1293/****
1294*****  DIALOG
1295****/
1296
1297static void
1298torrent_destroyed (gpointer dialog, GObject * dead_torrent UNUSED)
1299{
1300  gtk_widget_destroy (GTK_WIDGET(dialog));
1301}
1302
1303static void
1304remove_tag (gpointer tag)
1305{
1306  g_source_remove (GPOINTER_TO_UINT(tag)); /* stop the periodic refresh */
1307}
1308
1309static void
1310response_cb (GtkDialog *dialog, int response UNUSED, gpointer gtor)
1311{
1312  g_object_weak_unref (G_OBJECT(gtor), torrent_destroyed, dialog);
1313  gtk_widget_destroy (GTK_WIDGET(dialog));
1314}
1315
1316static gboolean
1317periodic_refresh (gpointer data)
1318{
1319  refresh_peers    (g_object_get_data (G_OBJECT(data), "peers-top"));
1320  refresh_activity (g_object_get_data (G_OBJECT(data), "activity-top"));
1321  refresh_files    (g_object_get_data (G_OBJECT(data), "files-top"));
1322  refresh_options  (g_object_get_data (G_OBJECT(data), "options-top"));
1323
1324  return TRUE;
1325}
1326
1327GtkWidget*
1328torrent_inspector_new ( GtkWindow * parent, TrTorrent * gtor )
1329{
1330  guint tag;
1331  char *size, *pch;
1332  GtkWidget *d, *n, *w;
1333  tr_torrent_t * tor = tr_torrent_handle (gtor);
1334  tr_info_t * info = tr_torrent_info (gtor);
1335
1336  /* create the dialog */
1337  pch = g_strdup_printf ("%s: %s",
1338                         g_get_application_name(),
1339                         _("Torrent Inspector"));
1340  d = gtk_dialog_new_with_buttons (pch, parent, 0,
1341                                   GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
1342                                   NULL);
1343  gtk_window_set_role (GTK_WINDOW(d), "tr-info" );
1344  g_signal_connect (d, "response", G_CALLBACK (response_cb), gtor);
1345  g_object_weak_ref (G_OBJECT(gtor), torrent_destroyed, d);
1346  g_free (pch);
1347
1348
1349  /* add label with file name and size */
1350  size = readablesize( info->totalSize );
1351  pch = g_markup_printf_escaped( "<b><big>%s</big>\n%s</b>", info->name, size );
1352  w = gtk_label_new (NULL);
1353  gtk_label_set_markup (GTK_LABEL(w), pch);
1354  gtk_label_set_justify (GTK_LABEL(w), GTK_JUSTIFY_CENTER);
1355  gtk_misc_set_alignment (GTK_MISC(w), 0.5f, 0.5f);
1356  gtk_box_pack_start (GTK_BOX(GTK_DIALOG(d)->vbox), w, 0, 0, GUI_PAD);
1357  g_free (pch);
1358  g_free (size);
1359
1360  /* add the notebook */
1361  n = gtk_notebook_new ();
1362
1363  w = activity_page_new (gtor);
1364  g_object_set_data (G_OBJECT(d), "activity-top", w);
1365  gtk_notebook_append_page (GTK_NOTEBOOK(n), w, 
1366                            gtk_label_new_with_mnemonic (_("_Activity")));
1367
1368  w = peer_page_new (gtor);
1369  g_object_set_data (G_OBJECT(d), "peers-top", w);
1370  gtk_notebook_append_page (GTK_NOTEBOOK(n),  w,
1371                            gtk_label_new_with_mnemonic (_("_Peers")));
1372
1373  gtk_notebook_append_page (GTK_NOTEBOOK(n),
1374                            info_page_new (tor),
1375                            gtk_label_new_with_mnemonic (_("_Info")));
1376
1377  w = file_page_new (gtor);
1378  g_object_set_data (G_OBJECT(d), "files-top", w);
1379  gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
1380                            gtk_label_new_with_mnemonic (_("_Files")));
1381
1382  w = options_page_new (gtor);
1383  g_object_set_data (G_OBJECT(d), "options-top", w);
1384  gtk_notebook_append_page (GTK_NOTEBOOK(n), w,
1385                            gtk_label_new_with_mnemonic (_("_Options")));
1386
1387  gtk_box_pack_start_defaults (GTK_BOX(GTK_DIALOG(d)->vbox), n);
1388
1389  tag = g_timeout_add (UPDATE_INTERVAL_MSEC, periodic_refresh, d);
1390  g_object_set_data_full (G_OBJECT(d), "tag",
1391                          GUINT_TO_POINTER(tag), remove_tag);
1392
1393  /* return the results */
1394  periodic_refresh (d);
1395  gtk_widget_show_all (GTK_DIALOG(d)->vbox);
1396  return d;
1397}
Note: See TracBrowser for help on using the repository browser.