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

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

(gtk) #687: more HIG fixes for Details dialog

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