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

Last change on this file since 2149 was 2149, checked in by livings124, 14 years ago

Merge file selection and torrent creation into the main branch.

The new code for these features is under a new license.

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    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    gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( store ),
1288                                          FC_KEY, GTK_SORT_ASCENDING );
1289
1290    /* create the view */
1291    view = gtk_tree_view_new_with_model( GTK_TREE_MODEL( store ) );
1292    g_object_set_data (G_OBJECT(view), "torrent-handle", tor );
1293    g_signal_connect( view, "popup-menu",
1294                      G_CALLBACK(on_popup_menu), NULL );
1295    g_signal_connect( view, "button-press-event",
1296                      G_CALLBACK(on_tree_view_button_pressed), on_popup_menu);
1297
1298    /* add file column */
1299   
1300    col = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
1301        "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
1302        "expand", TRUE,
1303        "title", _("File"),
1304        NULL));
1305    rend = gtk_cell_renderer_pixbuf_new();
1306    gtk_tree_view_column_pack_start( col, rend, FALSE );
1307    gtk_tree_view_column_add_attribute( col, rend, "stock-id", FC_STOCK );
1308    /* add text renderer */
1309    rend = gtk_cell_renderer_text_new();
1310    g_object_set( rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL );
1311    gtk_tree_view_column_pack_start( col, rend, TRUE );
1312    gtk_tree_view_column_add_attribute( col, rend, "markup", FC_LABEL );
1313    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
1314    /* add progress column */
1315    rend = gtk_cell_renderer_progress_new();
1316    col = gtk_tree_view_column_new_with_attributes (
1317      _("Progress"), rend, "value", FC_PROG, NULL);
1318    gtk_tree_view_append_column( GTK_TREE_VIEW( view ), col );
1319    /* set up view */
1320    sel = gtk_tree_view_get_selection( GTK_TREE_VIEW( view ) );
1321    gtk_tree_view_expand_all( GTK_TREE_VIEW( view ) );
1322    gtk_tree_view_set_search_column( GTK_TREE_VIEW( view ), FC_LABEL );
1323    g_signal_connect( sel, "changed", G_CALLBACK(fileSelectionChangedCB), NULL );
1324    fileSelectionChangedCB( sel, NULL );
1325
1326
1327    /* add priority column */
1328    model = priority_model_new ();
1329    col = gtk_tree_view_column_new ();
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  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.