source: trunk/gtk/tr-window.c @ 13891

Last change on this file since 13891 was 13891, checked in by jordan, 8 years ago

synchronize the Qt and GTK+ client's statusbars, including the new freespace indicator. Make the up/down speed ordering consistent between statusbar and torrent list.

  • Property svn:keywords set to Date Rev Author Id
File size: 32.8 KB
Line 
1/******************************************************************************
2 * $Id: tr-window.c 13891 2013-01-27 23:05:47Z jordan $
3 *
4 * Copyright (c) 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 <string.h>
26
27#include <gtk/gtk.h>
28#include <glib/gi18n.h>
29
30#include <libtransmission/transmission.h>
31#include <libtransmission/utils.h> /* tr_formatter_speed_KBps () */
32
33#include "actions.h"
34#include "conf.h"
35#include "filter.h"
36#include "hig.h"
37#include "torrent-cell-renderer.h"
38#include "tr-prefs.h"
39#include "tr-window.h"
40#include "util.h"
41
42typedef struct
43{
44    GtkWidget          * speedlimit_on_item[2];
45    GtkWidget          * speedlimit_off_item[2];
46    GtkWidget          * ratio_on_item;
47    GtkWidget          * ratio_off_item;
48    GtkWidget          * scroll;
49    GtkWidget          * view;
50    GtkWidget          * toolbar;
51    GtkWidget          * filter;
52    GtkWidget          * status;
53    GtkWidget          * status_menu;
54    GtkLabel           * ul_lb;
55    GtkLabel           * dl_lb;
56    GtkLabel           * stats_lb;
57    GtkLabel           * freespace_lb;
58    GtkWidget          * freespace_icon;
59    GtkLabel           * count_lb;
60    GtkWidget          * alt_speed_image;
61    GtkWidget          * alt_speed_button;
62    GtkWidget          * options_menu;
63    GtkTreeSelection   * selection;
64    GtkCellRenderer    * renderer;
65    GtkTreeViewColumn  * column;
66    GtkTreeModel       * filter_model;
67    TrCore             * core;
68    gulong               pref_handler_id;
69}
70PrivateData;
71
72static GQuark
73get_private_data_key (void)
74{
75  static GQuark q = 0;
76
77  if (!q)
78    q = g_quark_from_static_string ("private-data");
79
80  return q;
81}
82
83static PrivateData*
84get_private_data (GtkWindow * w)
85{
86  return g_object_get_qdata (G_OBJECT (w), get_private_data_key ());
87}
88
89/***
90****
91***/
92
93static void
94on_popup_menu (GtkWidget * self UNUSED,
95               GdkEventButton * event)
96{
97  GtkWidget * menu = gtr_action_get_widget ("/main-window-popup");
98
99  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
100                  (event ? event->button : 0),
101                  (event ? event->time : 0));
102}
103
104static void
105view_row_activated (GtkTreeView       * tree_view UNUSED,
106                    GtkTreePath       * path      UNUSED,
107                    GtkTreeViewColumn * column    UNUSED,
108                    gpointer            user_data UNUSED)
109{
110  gtr_action_activate ("show-torrent-properties");
111}
112
113static gboolean
114tree_view_search_equal_func (GtkTreeModel * model,
115                             gint           column UNUSED,
116                             const gchar  * key,
117                             GtkTreeIter  * iter,
118                             gpointer       search_data UNUSED)
119{
120    gboolean match;
121    char * lower;
122    const char * name = NULL;
123
124    lower = g_strstrip (g_utf8_strdown (key, -1));
125    gtk_tree_model_get (model, iter, MC_NAME_COLLATED, &name, -1);
126    match = strstr (name, lower) != NULL;
127    g_free (lower);
128
129    return !match;
130}
131
132static GtkWidget*
133makeview (PrivateData * p)
134{
135  GtkWidget * view;
136  GtkTreeViewColumn * col;
137  GtkTreeSelection * sel;
138  GtkCellRenderer * r;
139  GtkTreeView * tree_view;
140
141  view = gtk_tree_view_new ();
142  tree_view = GTK_TREE_VIEW (view);
143  gtk_tree_view_set_search_column (tree_view, MC_NAME_COLLATED);
144  gtk_tree_view_set_search_equal_func (tree_view,
145                                       tree_view_search_equal_func,
146                                       NULL, NULL);
147  gtk_tree_view_set_headers_visible (tree_view, FALSE);
148  gtk_tree_view_set_fixed_height_mode (tree_view, TRUE);
149
150  p->selection = gtk_tree_view_get_selection (tree_view);
151
152  p->column = col = GTK_TREE_VIEW_COLUMN (g_object_new (GTK_TYPE_TREE_VIEW_COLUMN,
153    "title", _("Torrent"),
154    "resizable", TRUE,
155    "sizing", GTK_TREE_VIEW_COLUMN_FIXED,
156    NULL));
157
158  p->renderer = r = torrent_cell_renderer_new ();
159  gtk_tree_view_column_pack_start (col, r, FALSE);
160  gtk_tree_view_column_add_attribute (col, r, "torrent", MC_TORRENT);
161  gtk_tree_view_column_add_attribute (col, r, "piece-upload-speed", MC_SPEED_UP);
162  gtk_tree_view_column_add_attribute (col, r, "piece-download-speed", MC_SPEED_DOWN);
163
164  gtk_tree_view_append_column (tree_view, col);
165  g_object_set (r, "xpad", GUI_PAD_SMALL, "ypad", GUI_PAD_SMALL, NULL);
166
167  gtk_tree_view_set_rules_hint (tree_view, TRUE);
168  sel = gtk_tree_view_get_selection (tree_view);
169  gtk_tree_selection_set_mode (GTK_TREE_SELECTION (sel),
170                               GTK_SELECTION_MULTIPLE);
171
172  g_signal_connect (view, "popup-menu",
173                    G_CALLBACK (on_popup_menu), NULL);
174  g_signal_connect (view, "button-press-event",
175                    G_CALLBACK (on_tree_view_button_pressed),
176                    (void *) on_popup_menu);
177  g_signal_connect (view, "button-release-event",
178                    G_CALLBACK (on_tree_view_button_released), NULL);
179  g_signal_connect (view, "row-activated",
180                    G_CALLBACK (view_row_activated), NULL);
181
182
183  gtk_tree_view_set_model (tree_view, p->filter_model);
184  g_object_unref (p->filter_model);
185
186  return view;
187}
188
189static void syncAltSpeedButton (PrivateData * p);
190
191static void
192prefsChanged (TrCore           * core UNUSED,
193              const tr_quark     key,
194              gpointer           wind)
195{
196  gboolean isEnabled;
197  PrivateData * p = get_private_data (GTK_WINDOW (wind));
198
199  switch (key)
200    {
201      case TR_KEY_compact_view:
202        g_object_set (p->renderer, "compact", gtr_pref_flag_get (key), NULL);
203        /* since the cell size has changed, we need gtktreeview to revalidate
204         * its fixed-height mode values. Unfortunately there's not an API call
205         * for that, but it *does* revalidate when it thinks the style's been tweaked */
206        g_signal_emit_by_name (p->view, "style-updated", NULL, NULL);
207        break;
208
209      case TR_KEY_show_statusbar:
210        isEnabled = gtr_pref_flag_get (key);
211        g_object_set (p->status, "visible", isEnabled, NULL);
212        break;
213
214      case TR_KEY_show_filterbar:
215        isEnabled = gtr_pref_flag_get (key);
216        g_object_set (p->filter, "visible", isEnabled, NULL);
217        break;
218
219      case TR_KEY_show_toolbar:
220        isEnabled = gtr_pref_flag_get (key);
221        g_object_set (p->toolbar, "visible", isEnabled, NULL);
222        break;
223
224      case TR_KEY_statusbar_stats:
225        gtr_window_refresh (wind);
226        break;
227
228      case TR_KEY_alt_speed_enabled:
229      case TR_KEY_alt_speed_up:
230      case TR_KEY_alt_speed_down:
231        syncAltSpeedButton (p);
232        break;
233
234      default:
235        break;
236    }
237}
238
239static void
240privateFree (gpointer vprivate)
241{
242  PrivateData * p = vprivate;
243  g_signal_handler_disconnect (p->core, p->pref_handler_id);
244  g_free (p);
245}
246
247static void
248onYinYangReleased (GtkWidget * w UNUSED, gpointer vprivate)
249{
250  PrivateData * p = vprivate;
251
252  gtk_menu_popup (GTK_MENU (p->status_menu),
253                  NULL, NULL, NULL, NULL, 0,
254                  gtk_get_current_event_time ());
255}
256
257#define STATS_MODE "stats-mode"
258
259static struct
260{
261    const char *  val, *i18n;
262} stats_modes[] = {
263    { "total-ratio",      N_("Total Ratio")                },
264    { "session-ratio",    N_("Session Ratio")              },
265    { "total-transfer",   N_("Total Transfer")             },
266    { "session-transfer", N_("Session Transfer")           }
267};
268
269static void
270status_menu_toggled_cb (GtkCheckMenuItem * menu_item,
271                        gpointer           vprivate)
272{
273  if (gtk_check_menu_item_get_active (menu_item))
274    {
275      PrivateData * p = vprivate;
276      const char *  val = g_object_get_data (G_OBJECT (menu_item), STATS_MODE);
277      gtr_core_set_pref (p->core, TR_KEY_statusbar_stats, val);
278    }
279}
280
281static void
282syncAltSpeedButton (PrivateData * p)
283{
284  char u[32];
285  char d[32];
286  char * str;
287  const char * fmt;
288  const gboolean b = gtr_pref_flag_get (TR_KEY_alt_speed_enabled);
289  const char * stock = b ? "alt-speed-on" : "alt-speed-off";
290  GtkWidget * w = p->alt_speed_button;
291
292  tr_formatter_speed_KBps (u, gtr_pref_int_get (TR_KEY_alt_speed_up), sizeof (u));
293  tr_formatter_speed_KBps (d, gtr_pref_int_get (TR_KEY_alt_speed_down), sizeof (d));
294  fmt = b ? _("Click to disable Alternative Speed Limits\n (%1$s down, %2$s up)")
295          : _("Click to enable Alternative Speed Limits\n (%1$s down, %2$s up)");
296  str = g_strdup_printf (fmt, d, u);
297
298  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), b);
299  gtk_image_set_from_stock (GTK_IMAGE (p->alt_speed_image), stock, -1);
300  gtk_button_set_alignment (GTK_BUTTON (w), 0.5, 0.5);
301  gtk_widget_set_tooltip_text (w, str);
302
303  g_free (str);
304}
305
306static void
307alt_speed_toggled_cb (GtkToggleButton * button, gpointer vprivate)
308{
309  PrivateData * p = vprivate;
310  const gboolean b = gtk_toggle_button_get_active (button);
311  gtr_core_set_pref_bool (p->core, TR_KEY_alt_speed_enabled,  b);
312}
313
314/***
315****  FILTER
316***/
317
318static void
319findMaxAnnounceTime (GtkTreeModel * model,
320                     GtkTreePath  * path UNUSED,
321                     GtkTreeIter  * iter,
322                     gpointer       gmaxTime)
323{
324  tr_torrent * tor;
325  const tr_stat * torStat;
326  time_t * maxTime = gmaxTime;
327
328  gtk_tree_model_get (model, iter, MC_TORRENT, &tor, -1);
329  torStat = tr_torrentStatCached (tor);
330  *maxTime = MAX (*maxTime, torStat->manualAnnounceTime);
331}
332
333static gboolean
334onAskTrackerQueryTooltip (GtkWidget   * widget UNUSED,
335                          gint          x UNUSED,
336                          gint          y UNUSED,
337                          gboolean      keyboard_tip UNUSED,
338                          GtkTooltip  * tooltip,
339                          gpointer      gdata)
340{
341  gboolean handled;
342  time_t maxTime = 0;
343  PrivateData * p = gdata;
344  const time_t now = time (NULL);
345
346  gtk_tree_selection_selected_foreach (p->selection,
347                                       findMaxAnnounceTime,
348                                       &maxTime);
349  if (maxTime <= now)
350    {
351      handled = FALSE;
352    }
353  else
354    {
355      char buf[512];
356      char timebuf[64];
357      const int seconds = maxTime - now;
358
359      tr_strltime (timebuf, seconds, sizeof (timebuf));
360      g_snprintf (buf, sizeof (buf),
361                  _("Tracker will allow requests in %s"), timebuf);
362      gtk_tooltip_set_text (tooltip, buf);
363      handled = TRUE;
364    }
365
366  return handled;
367}
368
369static gboolean
370onAltSpeedToggledIdle (gpointer vp)
371{
372  PrivateData * p = vp;
373  gboolean b = tr_sessionUsesAltSpeed (gtr_core_session (p->core));
374  gtr_core_set_pref_bool (p->core, TR_KEY_alt_speed_enabled, b);
375
376  return FALSE;
377}
378
379static void
380onAltSpeedToggled (tr_session * s UNUSED, bool isEnabled UNUSED, bool byUser UNUSED, void * p)
381{
382  gdk_threads_add_idle (onAltSpeedToggledIdle, p);
383}
384
385/***
386****  Speed limit menu
387***/
388
389#define DIRECTION_KEY "direction-key"
390#define ENABLED_KEY "enabled-key"
391#define SPEED_KEY "speed-key"
392
393static void
394onSpeedToggled (GtkCheckMenuItem * check, gpointer vp)
395{
396  PrivateData * p = vp;
397  GObject * o = G_OBJECT (check);
398  gboolean isEnabled = g_object_get_data (o, ENABLED_KEY) != 0;
399  tr_direction dir = GPOINTER_TO_INT (g_object_get_data (o, DIRECTION_KEY));
400  const tr_quark key = dir == TR_UP ? TR_KEY_speed_limit_up_enabled
401                                    : TR_KEY_speed_limit_down_enabled;
402
403  if (gtk_check_menu_item_get_active (check))
404    gtr_core_set_pref_bool (p->core, key, isEnabled);
405}
406
407static void
408onSpeedSet (GtkCheckMenuItem * check, gpointer vp)
409{
410  tr_quark key;
411  PrivateData * p = vp;
412  GObject * o = G_OBJECT (check);
413  const int KBps = GPOINTER_TO_INT (g_object_get_data (o, SPEED_KEY));
414  tr_direction dir = GPOINTER_TO_INT (g_object_get_data (o, DIRECTION_KEY));
415
416  key = dir==TR_UP ? TR_KEY_speed_limit_up : TR_KEY_speed_limit_down;
417  gtr_core_set_pref_int (p->core, key, KBps);
418
419  key = dir==TR_UP ? TR_KEY_speed_limit_up_enabled : TR_KEY_speed_limit_down_enabled;
420  gtr_core_set_pref_bool (p->core, key, TRUE);
421}
422
423static GtkWidget*
424createSpeedMenu (PrivateData * p, tr_direction dir)
425{
426  int i, n;
427  GObject * o;
428  GtkWidget * w;
429  GtkWidget * m;
430  GtkMenuShell * menu_shell;
431  const int speeds_KBps[] = { 5, 10, 20, 30, 40, 50, 75, 100, 150, 200, 250, 500, 750 };
432
433  m = gtk_menu_new ();
434  menu_shell = GTK_MENU_SHELL (m);
435
436  w = gtk_radio_menu_item_new_with_label (NULL, _("Unlimited"));
437  o = G_OBJECT (w);
438  p->speedlimit_off_item[dir] = w;
439  g_object_set_data (o, DIRECTION_KEY, GINT_TO_POINTER (dir));
440  g_object_set_data (o, ENABLED_KEY, GINT_TO_POINTER (FALSE));
441  g_signal_connect (w, "toggled", G_CALLBACK (onSpeedToggled), p);
442  gtk_menu_shell_append (menu_shell, w);
443
444  w = gtk_radio_menu_item_new_with_label_from_widget (GTK_RADIO_MENU_ITEM (w), "");
445  o = G_OBJECT (w);
446  p->speedlimit_on_item[dir] = w;
447  g_object_set_data (o, DIRECTION_KEY, GINT_TO_POINTER (dir));
448  g_object_set_data (o, ENABLED_KEY, GINT_TO_POINTER (TRUE));
449  g_signal_connect (w, "toggled", G_CALLBACK (onSpeedToggled), p);
450  gtk_menu_shell_append (menu_shell, w);
451
452  w = gtk_separator_menu_item_new ();
453  gtk_menu_shell_append (menu_shell, w);
454
455  for (i=0, n=G_N_ELEMENTS (speeds_KBps); i<n; ++i)
456    {
457      char buf[128];
458      tr_formatter_speed_KBps (buf, speeds_KBps[i], sizeof (buf));
459      w = gtk_menu_item_new_with_label (buf);
460      o = G_OBJECT (w);
461      g_object_set_data (o, DIRECTION_KEY, GINT_TO_POINTER (dir));
462      g_object_set_data (o, SPEED_KEY, GINT_TO_POINTER (speeds_KBps[i]));
463      g_signal_connect (w, "activate", G_CALLBACK (onSpeedSet), p);
464      gtk_menu_shell_append (menu_shell, w);
465    }
466
467  return m;
468}
469
470/***
471****  Speed limit menu
472***/
473
474#define RATIO_KEY "stock-ratio-index"
475
476static const double stockRatios[] = { 0.25, 0.5, 0.75, 1, 1.5, 2, 3 };
477
478static void
479onRatioToggled (GtkCheckMenuItem * check, gpointer vp)
480{
481  PrivateData * p = vp;
482
483  if (gtk_check_menu_item_get_active (check))
484    {
485      gboolean f = g_object_get_data (G_OBJECT (check), ENABLED_KEY) != 0;
486      gtr_core_set_pref_bool (p->core, TR_KEY_ratio_limit_enabled, f);
487    }
488}
489static void
490onRatioSet (GtkCheckMenuItem * check, gpointer vp)
491{
492  PrivateData * p = vp;
493  int i = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (check), RATIO_KEY));
494  const double ratio = stockRatios[i];
495  gtr_core_set_pref_double (p->core, TR_KEY_ratio_limit, ratio);
496  gtr_core_set_pref_bool (p->core, TR_KEY_ratio_limit_enabled, TRUE);
497}
498
499static GtkWidget*
500createRatioMenu (PrivateData * p)
501{
502  int i, n;
503  GtkWidget *m, *w;
504  GtkMenuShell * menu_shell;
505
506  m = gtk_menu_new ();
507  menu_shell = GTK_MENU_SHELL (m);
508
509  w = gtk_radio_menu_item_new_with_label (NULL, _("Seed Forever"));
510  p->ratio_off_item = w;
511  g_object_set_data (G_OBJECT (w), ENABLED_KEY, GINT_TO_POINTER (FALSE));
512  g_signal_connect (w, "toggled", G_CALLBACK (onRatioToggled), p);
513  gtk_menu_shell_append (menu_shell, w);
514
515  w = gtk_radio_menu_item_new_with_label_from_widget (GTK_RADIO_MENU_ITEM (w), "");
516  p->ratio_on_item = w;
517  g_object_set_data (G_OBJECT (w), ENABLED_KEY, GINT_TO_POINTER (TRUE));
518  g_signal_connect (w, "toggled", G_CALLBACK (onRatioToggled), p);
519  gtk_menu_shell_append (menu_shell, w);
520
521  w = gtk_separator_menu_item_new ();
522  gtk_menu_shell_append (menu_shell, w);
523
524  for (i=0, n=G_N_ELEMENTS (stockRatios); i<n; ++i)
525    {
526      char buf[128];
527      tr_strlratio (buf, stockRatios[i], sizeof (buf));
528      w = gtk_menu_item_new_with_label (buf);
529      g_object_set_data (G_OBJECT (w), RATIO_KEY, GINT_TO_POINTER (i));
530      g_signal_connect (w, "activate", G_CALLBACK (onRatioSet), p);
531      gtk_menu_shell_append (menu_shell, w);
532    }
533
534    return m;
535}
536
537/***
538****  Option menu
539***/
540
541static GtkWidget*
542createOptionsMenu (PrivateData * p)
543{
544  GtkWidget * m;
545  GtkWidget * top = gtk_menu_new ();
546  GtkMenuShell * menu_shell = GTK_MENU_SHELL (top);
547
548  m = gtk_menu_item_new_with_label (_("Limit Download Speed"));
549  gtk_menu_item_set_submenu (GTK_MENU_ITEM (m), createSpeedMenu (p, TR_DOWN));
550  gtk_menu_shell_append (menu_shell, m);
551
552  m = gtk_menu_item_new_with_label (_("Limit Upload Speed"));
553  gtk_menu_item_set_submenu (GTK_MENU_ITEM (m), createSpeedMenu (p, TR_UP));
554  gtk_menu_shell_append (menu_shell, m);
555
556  m = gtk_separator_menu_item_new ();
557  gtk_menu_shell_append (menu_shell, m);
558
559  m = gtk_menu_item_new_with_label (_("Stop Seeding at Ratio"));
560  gtk_menu_item_set_submenu (GTK_MENU_ITEM (m), createRatioMenu (p));
561  gtk_menu_shell_append (menu_shell, m);
562
563  gtk_widget_show_all (top);
564  return top;
565}
566
567static void
568onOptionsClicked (GtkButton * button UNUSED, gpointer vp)
569{
570  char buf1[512];
571  char buf2[512];
572  gboolean b;
573  GtkWidget * w;
574  PrivateData * p = vp;
575
576  w = p->speedlimit_on_item[TR_DOWN];
577  tr_formatter_speed_KBps (buf1, gtr_pref_int_get (TR_KEY_speed_limit_down), sizeof (buf1));
578  gtr_label_set_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN (w))), buf1);
579
580  b = gtr_pref_flag_get (TR_KEY_speed_limit_down_enabled);
581  w = b ? p->speedlimit_on_item[TR_DOWN] : p->speedlimit_off_item[TR_DOWN];
582  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), TRUE);
583
584  w = p->speedlimit_on_item[TR_UP];
585  tr_formatter_speed_KBps (buf1, gtr_pref_int_get (TR_KEY_speed_limit_up), sizeof (buf1));
586  gtr_label_set_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN (w))), buf1);
587
588  b = gtr_pref_flag_get (TR_KEY_speed_limit_up_enabled);
589  w = b ? p->speedlimit_on_item[TR_UP] : p->speedlimit_off_item[TR_UP];
590  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), TRUE);
591
592  tr_strlratio (buf1, gtr_pref_double_get (TR_KEY_ratio_limit), sizeof (buf1));
593  g_snprintf (buf2, sizeof (buf2), _("Stop at Ratio (%s)"), buf1);
594  gtr_label_set_text (GTK_LABEL (gtk_bin_get_child (GTK_BIN (p->ratio_on_item))), buf2);
595
596  b = gtr_pref_flag_get (TR_KEY_ratio_limit_enabled);
597  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (b ? p->ratio_on_item : p->ratio_off_item), TRUE);
598
599  gtk_menu_popup (GTK_MENU (p->options_menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time ());
600}
601
602/***
603****  PUBLIC
604***/
605
606GtkWidget *
607gtr_window_new (GtkApplication * app, GtkUIManager * ui_mgr, TrCore * core)
608{
609  int              i, n;
610  const char     * pch, * style;
611  PrivateData    * p;
612  GtkWidget      * sibling = NULL;
613  GtkWidget      * ul_lb, * dl_lb;
614  GtkWidget      * mainmenu, *toolbar, *filter, *list, *status;
615  GtkWidget      * vbox, *w, *self, *menu;
616  GtkWidget      * grid_w;
617  GtkWindow      * win;
618  GtkCssProvider * css_provider;
619  GSList         * l;
620  GtkGrid        * grid;
621
622  p = g_new0 (PrivateData, 1);
623
624  /* make the window */
625  self = gtk_application_window_new (app);
626  g_object_set_qdata_full (G_OBJECT (self), get_private_data_key (), p, privateFree);
627  win = GTK_WINDOW (self);
628  gtk_window_set_title (win, g_get_application_name ());
629  gtk_window_set_role (win, "tr-main");
630  gtk_window_set_default_size (win,
631                               gtr_pref_int_get (TR_KEY_main_window_width),
632                               gtr_pref_int_get (TR_KEY_main_window_height));
633  gtk_window_move (win, gtr_pref_int_get (TR_KEY_main_window_x),
634                        gtr_pref_int_get (TR_KEY_main_window_y));
635  if (gtr_pref_flag_get (TR_KEY_main_window_is_maximized))
636    gtk_window_maximize (win);
637  gtk_window_add_accel_group (win, gtk_ui_manager_get_accel_group (ui_mgr));
638  /* Add style provider to the window. */
639  /* Please move it to separate .css file if you’re adding more styles here. */
640  style = ".tr-workarea {border-width: 1px 0; border-style: solid; border-radius: 0;}";
641  css_provider = gtk_css_provider_new ();
642  gtk_css_provider_load_from_data (css_provider, style,
643                                   strlen (style), NULL);
644  gtk_style_context_add_provider_for_screen(gdk_screen_get_default (),
645                                            GTK_STYLE_PROVIDER (css_provider),
646                                            GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
647
648  /* window's main container */
649  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
650  gtk_container_add (GTK_CONTAINER (self), vbox);
651
652  /* main menu */
653  mainmenu = gtr_action_get_widget ("/main-window-menu");
654  w = gtr_action_get_widget ("/main-window-menu/torrent-menu/torrent-reannounce");
655  g_signal_connect (w, "query-tooltip", G_CALLBACK (onAskTrackerQueryTooltip), p);
656
657  /* toolbar */
658  toolbar = p->toolbar = gtr_action_get_widget ("/main-window-toolbar");
659  gtk_style_context_add_class (gtk_widget_get_style_context (toolbar),
660                               GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
661  gtr_action_set_important ("open-torrent-toolbar", TRUE);
662  gtr_action_set_important ("show-torrent-properties", TRUE);
663
664  /* filter */
665  w = filter = p->filter = gtr_filter_bar_new (gtr_core_session (core),
666                                               gtr_core_model (core),
667                                               &p->filter_model);
668  gtk_container_set_border_width (GTK_CONTAINER (w), GUI_PAD_SMALL);
669
670  /* status menu */
671  menu = p->status_menu = gtk_menu_new ();
672  l = NULL;
673  pch = gtr_pref_string_get (TR_KEY_statusbar_stats);
674  for (i=0, n=G_N_ELEMENTS (stats_modes); i<n; ++i)
675    {
676      const char * val = stats_modes[i].val;
677      w = gtk_radio_menu_item_new_with_label (l, _ (stats_modes[i].i18n));
678      l = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (w));
679      gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (w), !strcmp (val, pch));
680      g_object_set_data (G_OBJECT (w), STATS_MODE, (gpointer)stats_modes[i].val);
681      g_signal_connect (w, "toggled", G_CALLBACK (status_menu_toggled_cb), p);
682      gtk_menu_shell_append (GTK_MENU_SHELL (menu), w);
683      gtk_widget_show (w);
684    }
685
686  /**
687  *** Statusbar
688  **/
689
690  grid_w = status = p->status = gtk_grid_new ();
691  grid = GTK_GRID (grid_w);
692  gtk_container_set_border_width (GTK_CONTAINER (grid), GUI_PAD_SMALL);
693
694  /* gear */
695  w = gtk_button_new ();
696  gtk_container_add (GTK_CONTAINER (w), gtk_image_new_from_stock ("utilities", -1));
697  gtk_widget_set_tooltip_text (w, _("Options"));
698  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
699  gtk_button_set_relief (GTK_BUTTON (w), GTK_RELIEF_NONE);
700  p->options_menu = createOptionsMenu (p);
701  g_signal_connect (w, "clicked", G_CALLBACK (onOptionsClicked), p);
702  sibling = w;
703
704  /* turtle */
705  p->alt_speed_image = gtk_image_new ();
706  w = p->alt_speed_button = gtk_toggle_button_new ();
707  gtk_button_set_image (GTK_BUTTON (w), p->alt_speed_image);
708  gtk_button_set_relief (GTK_BUTTON (w), GTK_RELIEF_NONE);
709  g_signal_connect (w, "toggled", G_CALLBACK (alt_speed_toggled_cb), p);
710  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
711  sibling = w;
712
713  /* spacer */
714  w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
715  gtk_widget_set_hexpand (w, TRUE);
716  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
717  sibling = w;
718
719  /* torrent count */
720  w = gtk_label_new ("N Torrents");
721  p->count_lb = GTK_LABEL (w);
722  gtk_label_set_single_line_mode (p->count_lb, TRUE);
723  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
724  sibling = w;
725
726  /* freespace */
727  w = gtk_label_new (NULL);
728  g_object_set (G_OBJECT(w), "margin-left", GUI_PAD_BIG*2, NULL);
729  p->freespace_lb = GTK_LABEL (w);
730  gtk_label_set_single_line_mode (p->freespace_lb, TRUE);
731  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
732  sibling = w;
733  w = gtk_image_new_from_stock (GTK_STOCK_HARDDISK, GTK_ICON_SIZE_MENU);
734  p->freespace_icon = w;
735  g_object_set (G_OBJECT(w), "margin-left", GUI_PAD, NULL);
736  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
737  sibling = w;
738
739  /* spacer */
740  w = gtk_alignment_new (0.0f, 0.0f, 0.0f, 0.0f);
741  gtk_widget_set_hexpand (w, TRUE);
742  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
743  sibling = w;
744
745  /* ratio */
746  w = gtk_label_new (NULL);
747  p->stats_lb = GTK_LABEL (w);
748  gtk_label_set_single_line_mode (p->stats_lb, TRUE);
749  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
750  sibling = w;
751  w = gtk_button_new ();
752  gtk_widget_set_tooltip_text (w, _("Statistics"));
753  gtk_container_add (GTK_CONTAINER (w), gtk_image_new_from_stock ("ratio", -1));
754  gtk_button_set_relief (GTK_BUTTON (w), GTK_RELIEF_NONE);
755  g_signal_connect (w, "clicked", G_CALLBACK (onYinYangReleased), p);
756  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
757  sibling = w;
758
759  /* upload */
760  w = ul_lb = gtk_label_new (NULL);
761  p->ul_lb = GTK_LABEL (w);
762  gtk_label_set_single_line_mode (p->ul_lb, TRUE);
763  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
764  sibling = w;
765  w = gtk_image_new_from_stock (GTK_STOCK_GO_UP, GTK_ICON_SIZE_MENU);
766  g_object_set (G_OBJECT(w), "margin-left", GUI_PAD, NULL);
767  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
768  sibling = w;
769
770  /* download */
771  w = dl_lb = gtk_label_new (NULL);
772  p->dl_lb = GTK_LABEL (w);
773  gtk_label_set_single_line_mode (p->dl_lb, TRUE);
774  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
775  sibling = w;
776  w = gtk_image_new_from_stock (GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_MENU);
777  g_object_set (G_OBJECT(w), "margin-left", GUI_PAD, NULL);
778  gtk_grid_attach_next_to (grid, w, sibling, GTK_POS_RIGHT, 1, 1);
779  sibling = w;
780
781  /* workarea */
782  p->view = makeview (p);
783  w = list = p->scroll = gtk_scrolled_window_new (NULL, NULL);
784  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (w),
785                                  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
786  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (w),
787                                       GTK_SHADOW_OUT);
788  gtk_style_context_add_class (gtk_widget_get_style_context (w), "tr-workarea");
789  gtk_container_add (GTK_CONTAINER (w), p->view);
790
791  /* lay out the widgets */
792  gtk_box_pack_start (GTK_BOX (vbox), mainmenu, FALSE, FALSE, 0);
793  gtk_box_pack_start (GTK_BOX (vbox), toolbar, FALSE, FALSE, 0);
794  gtk_box_pack_start (GTK_BOX (vbox), filter, FALSE, FALSE, 0);
795  gtk_box_pack_start (GTK_BOX (vbox), list, TRUE, TRUE, 0);
796  gtk_box_pack_start (GTK_BOX (vbox), status, FALSE, FALSE, 0);
797
798  {
799    /* this is to determine the maximum width/height for the label */
800    int w=0, h=0;
801    PangoLayout * pango_layout;
802    pango_layout = gtk_widget_create_pango_layout (ul_lb, "999.99 kB/s");
803    pango_layout_get_pixel_size (pango_layout, &w, &h);
804    gtk_widget_set_size_request (ul_lb, w, h);
805    gtk_widget_set_size_request (dl_lb, w, h);
806    gtk_misc_set_alignment (GTK_MISC (ul_lb), 1.0, 0.5);
807    gtk_misc_set_alignment (GTK_MISC (dl_lb), 1.0, 0.5);
808    g_object_unref (G_OBJECT (pango_layout));
809  }
810
811  /* show all but the window */
812  gtk_widget_show_all (vbox);
813
814  /* listen for prefs changes that affect the window */
815  p->core = core;
816  prefsChanged (core, TR_KEY_compact_view, self);
817  prefsChanged (core, TR_KEY_show_filterbar, self);
818  prefsChanged (core, TR_KEY_show_statusbar, self);
819  prefsChanged (core, TR_KEY_statusbar_stats, self);
820  prefsChanged (core, TR_KEY_show_toolbar, self);
821  prefsChanged (core, TR_KEY_alt_speed_enabled, self);
822  p->pref_handler_id = g_signal_connect (core, "prefs-changed",
823                                         G_CALLBACK (prefsChanged), self);
824
825  tr_sessionSetAltSpeedFunc (gtr_core_session (core), onAltSpeedToggled, p);
826
827  return self;
828}
829
830static void
831updateTorrentCount (PrivateData * p)
832{
833  bool visible = false;
834
835  g_return_if_fail (p != NULL);
836
837  if (p->core != NULL)
838    {
839      const int torrentCount = gtk_tree_model_iter_n_children (gtr_core_model (p->core), NULL);
840      const int visibleCount = gtk_tree_model_iter_n_children (p->filter_model, NULL);
841
842      visible = torrentCount > 0;
843
844      if (visible)
845        {
846          char countStr[512];
847
848          if (torrentCount != visibleCount)
849            g_snprintf (countStr, sizeof (countStr),
850                        ngettext ("%1$'d of %2$'d Torrent",
851                                  "%1$'d of %2$'d Torrents",
852                                  torrentCount),
853                        visibleCount, torrentCount);
854          else
855            g_snprintf (countStr, sizeof (countStr),
856                        ngettext ("%'d Torrent", "%'d Torrents", torrentCount),
857                        torrentCount);
858
859          gtr_label_set_text (p->count_lb, countStr);
860        }
861    }
862
863  gtk_widget_set_visible (GTK_WIDGET(p->count_lb), visible);
864}
865
866static void
867updateFreeSpace (PrivateData * p)
868{
869  GtkWidget * w;
870  bool visible = false;
871
872  g_return_if_fail (p != NULL);
873
874  w = GTK_WIDGET (p->freespace_lb);
875
876  if (p->core != NULL)
877    {
878      tr_session * session = gtr_core_session (p->core);
879      const int64_t n = tr_sessionGetDownloadDirFreeSpace (session);
880      const char * downloadDir = tr_sessionGetDownloadDir (session);
881
882      visible = n >= 0;
883
884      if (visible)
885        {
886          char * tip;
887          char sizeStr[32];
888          tr_strlsize (sizeStr, n, sizeof(sizeStr));
889          gtk_label_set_text (p->freespace_lb, sizeStr);
890          tip = tr_strdup_printf (_("Download folder \"%1$s\" has %2$s free"), downloadDir, sizeStr);
891          gtk_widget_set_tooltip_text (w, tip);
892          g_free (tip);
893        }
894    }
895
896  gtk_widget_set_visible (w, visible);
897  gtk_widget_set_visible (p->freespace_icon, visible);
898}
899
900static void
901updateStats (PrivateData * p)
902{
903  const char * pch;
904  char up[32];
905  char down[32];
906  char ratio[32];
907  char buf[512];
908  struct tr_session_stats stats;
909  tr_session * session = gtr_core_session (p->core);
910
911  /* update the stats */
912  pch = gtr_pref_string_get (TR_KEY_statusbar_stats);
913  if (!strcmp (pch, "session-ratio"))
914    {
915      tr_sessionGetStats (session, &stats);
916      tr_strlratio (ratio, stats.ratio, sizeof (ratio));
917      g_snprintf (buf, sizeof (buf), _("Ratio: %s"), ratio);
918    }
919  else if (!strcmp (pch, "session-transfer"))
920    {
921      tr_sessionGetStats (session, &stats);
922      tr_strlsize (up, stats.uploadedBytes, sizeof (up));
923      tr_strlsize (down, stats.downloadedBytes, sizeof (down));
924      /* Translators: "size|" is here for disambiguation. Please remove it from your translation.
925         %1$s is the size of the data we've downloaded
926         %2$s is the size of the data we've uploaded */
927      g_snprintf (buf, sizeof (buf),
928                  Q_("Down: %1$s, Up: %2$s"), down, up);
929    }
930  else if (!strcmp (pch, "total-transfer"))
931    {
932      tr_sessionGetCumulativeStats (session, &stats);
933      tr_strlsize (up, stats.uploadedBytes, sizeof (up));
934      tr_strlsize (down, stats.downloadedBytes, sizeof (down));
935      /* Translators: "size|" is here for disambiguation. Please remove it from your translation.
936         %1$s is the size of the data we've downloaded
937         %2$s is the size of the data we've uploaded */
938      g_snprintf (buf, sizeof (buf),
939                  Q_( "size|Down: %1$s, Up: %2$s"), down, up);
940    }
941  else /* default is total-ratio */
942    {
943      tr_sessionGetCumulativeStats (session, &stats);
944      tr_strlratio (ratio, stats.ratio, sizeof (ratio));
945      g_snprintf (buf, sizeof (buf), _("Ratio: %s"), ratio);
946    }
947
948  gtr_label_set_text (p->stats_lb, buf);
949}
950
951static void
952updateSpeeds (PrivateData * p)
953{
954  tr_session * session = gtr_core_session (p->core);
955
956  if (session != NULL)
957    {
958      char buf[128];
959      double up=0, down=0;
960      GtkTreeIter iter;
961      GtkTreeModel * model = gtr_core_model (p->core);
962
963      if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do
964        {
965          double u, d;
966          gtk_tree_model_get (model, &iter, MC_SPEED_UP, &u,
967                                            MC_SPEED_DOWN, &d,
968                                            -1);
969          up += u;
970          down += d;
971        }
972      while (gtk_tree_model_iter_next (model, &iter));
973
974      tr_formatter_speed_KBps (buf, down, sizeof (buf));
975      gtr_label_set_text (p->dl_lb, buf);
976
977      tr_formatter_speed_KBps (buf, up, sizeof (buf));
978      gtr_label_set_text (p->ul_lb, buf);
979    }
980}
981
982void
983gtr_window_refresh (GtkWindow * self)
984{
985  PrivateData * p = get_private_data (self);
986
987  if (p && p->core && gtr_core_session (p->core))
988    {
989      updateSpeeds (p);
990      updateTorrentCount (p);
991      updateStats (p);
992      updateFreeSpace (p);
993    }
994}
995
996GtkTreeSelection*
997gtr_window_get_selection (GtkWindow * w)
998{
999    return get_private_data (w)->selection;
1000}
1001
1002void
1003gtr_window_set_busy (GtkWindow * win, gboolean isBusy)
1004{
1005  GtkWidget * w = GTK_WIDGET (win);
1006
1007  if (w && gtk_widget_get_realized (w))
1008    {
1009      GdkDisplay * display = gtk_widget_get_display (w);
1010      GdkCursor * cursor = isBusy ? gdk_cursor_new_for_display (display, GDK_WATCH) : NULL;
1011
1012      gdk_window_set_cursor (gtk_widget_get_window (w), cursor);
1013      gdk_display_flush (display);
1014
1015      g_clear_object (&cursor);
1016    }
1017}
Note: See TracBrowser for help on using the repository browser.