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

Last change on this file since 2267 was 2267, checked in by charles, 15 years ago

(gtk) sort the files in the torrent inspector's file tab.

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